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, 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 move_to_end = MoveToEndOfLine {
1518 stop_at_soft_wraps: true,
1519 };
1520
1521 let editor = cx.add_window(|window, cx| {
1522 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1523 build_editor(buffer, window, cx)
1524 });
1525 _ = editor.update(cx, |editor, window, cx| {
1526 editor.change_selections(None, window, cx, |s| {
1527 s.select_display_ranges([
1528 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1529 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1530 ]);
1531 });
1532 });
1533
1534 _ = editor.update(cx, |editor, window, cx| {
1535 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1536 assert_eq!(
1537 editor.selections.display_ranges(cx),
1538 &[
1539 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1540 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1541 ]
1542 );
1543 });
1544
1545 _ = editor.update(cx, |editor, window, cx| {
1546 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1547 assert_eq!(
1548 editor.selections.display_ranges(cx),
1549 &[
1550 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1551 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1552 ]
1553 );
1554 });
1555
1556 _ = editor.update(cx, |editor, window, cx| {
1557 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1558 assert_eq!(
1559 editor.selections.display_ranges(cx),
1560 &[
1561 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1562 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1563 ]
1564 );
1565 });
1566
1567 _ = editor.update(cx, |editor, window, cx| {
1568 editor.move_to_end_of_line(&move_to_end, window, cx);
1569 assert_eq!(
1570 editor.selections.display_ranges(cx),
1571 &[
1572 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1573 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1574 ]
1575 );
1576 });
1577
1578 // Moving to the end of line again is a no-op.
1579 _ = editor.update(cx, |editor, window, cx| {
1580 editor.move_to_end_of_line(&move_to_end, window, cx);
1581 assert_eq!(
1582 editor.selections.display_ranges(cx),
1583 &[
1584 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1585 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1586 ]
1587 );
1588 });
1589
1590 _ = editor.update(cx, |editor, window, cx| {
1591 editor.move_left(&MoveLeft, window, cx);
1592 editor.select_to_beginning_of_line(
1593 &SelectToBeginningOfLine {
1594 stop_at_soft_wraps: true,
1595 stop_at_indent: true,
1596 },
1597 window,
1598 cx,
1599 );
1600 assert_eq!(
1601 editor.selections.display_ranges(cx),
1602 &[
1603 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1604 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1605 ]
1606 );
1607 });
1608
1609 _ = editor.update(cx, |editor, window, cx| {
1610 editor.select_to_beginning_of_line(
1611 &SelectToBeginningOfLine {
1612 stop_at_soft_wraps: true,
1613 stop_at_indent: true,
1614 },
1615 window,
1616 cx,
1617 );
1618 assert_eq!(
1619 editor.selections.display_ranges(cx),
1620 &[
1621 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1622 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1623 ]
1624 );
1625 });
1626
1627 _ = editor.update(cx, |editor, window, cx| {
1628 editor.select_to_beginning_of_line(
1629 &SelectToBeginningOfLine {
1630 stop_at_soft_wraps: true,
1631 stop_at_indent: true,
1632 },
1633 window,
1634 cx,
1635 );
1636 assert_eq!(
1637 editor.selections.display_ranges(cx),
1638 &[
1639 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1640 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1641 ]
1642 );
1643 });
1644
1645 _ = editor.update(cx, |editor, window, cx| {
1646 editor.select_to_end_of_line(
1647 &SelectToEndOfLine {
1648 stop_at_soft_wraps: true,
1649 },
1650 window,
1651 cx,
1652 );
1653 assert_eq!(
1654 editor.selections.display_ranges(cx),
1655 &[
1656 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1657 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1658 ]
1659 );
1660 });
1661
1662 _ = editor.update(cx, |editor, window, cx| {
1663 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1664 assert_eq!(editor.display_text(cx), "ab\n de");
1665 assert_eq!(
1666 editor.selections.display_ranges(cx),
1667 &[
1668 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1669 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1670 ]
1671 );
1672 });
1673
1674 _ = editor.update(cx, |editor, window, cx| {
1675 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
1676 assert_eq!(editor.display_text(cx), "\n");
1677 assert_eq!(
1678 editor.selections.display_ranges(cx),
1679 &[
1680 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1681 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1682 ]
1683 );
1684 });
1685}
1686
1687#[gpui::test]
1688fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1689 init_test(cx, |_| {});
1690 let move_to_beg = MoveToBeginningOfLine {
1691 stop_at_soft_wraps: false,
1692 stop_at_indent: false,
1693 };
1694
1695 let move_to_end = MoveToEndOfLine {
1696 stop_at_soft_wraps: false,
1697 };
1698
1699 let editor = cx.add_window(|window, cx| {
1700 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1701 build_editor(buffer, window, cx)
1702 });
1703
1704 _ = editor.update(cx, |editor, window, cx| {
1705 editor.set_wrap_width(Some(140.0.into()), cx);
1706
1707 // We expect the following lines after wrapping
1708 // ```
1709 // thequickbrownfox
1710 // jumpedoverthelazydo
1711 // gs
1712 // ```
1713 // The final `gs` was soft-wrapped onto a new line.
1714 assert_eq!(
1715 "thequickbrownfox\njumpedoverthelaz\nydogs",
1716 editor.display_text(cx),
1717 );
1718
1719 // First, let's assert behavior on the first line, that was not soft-wrapped.
1720 // Start the cursor at the `k` on the first line
1721 editor.change_selections(None, window, cx, |s| {
1722 s.select_display_ranges([
1723 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1724 ]);
1725 });
1726
1727 // Moving to the beginning of the line should put us at the beginning of the line.
1728 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1729 assert_eq!(
1730 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1731 editor.selections.display_ranges(cx)
1732 );
1733
1734 // Moving to the end of the line should put us at the end of the line.
1735 editor.move_to_end_of_line(&move_to_end, window, cx);
1736 assert_eq!(
1737 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1738 editor.selections.display_ranges(cx)
1739 );
1740
1741 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1742 // Start the cursor at the last line (`y` that was wrapped to a new line)
1743 editor.change_selections(None, window, cx, |s| {
1744 s.select_display_ranges([
1745 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1746 ]);
1747 });
1748
1749 // Moving to the beginning of the line should put us at the start of the second line of
1750 // display text, i.e., the `j`.
1751 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1752 assert_eq!(
1753 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1754 editor.selections.display_ranges(cx)
1755 );
1756
1757 // Moving to the beginning of the line again should be a no-op.
1758 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1759 assert_eq!(
1760 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1761 editor.selections.display_ranges(cx)
1762 );
1763
1764 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1765 // next display line.
1766 editor.move_to_end_of_line(&move_to_end, window, cx);
1767 assert_eq!(
1768 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1769 editor.selections.display_ranges(cx)
1770 );
1771
1772 // Moving to the end of the line again should be a no-op.
1773 editor.move_to_end_of_line(&move_to_end, window, cx);
1774 assert_eq!(
1775 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1776 editor.selections.display_ranges(cx)
1777 );
1778 });
1779}
1780
1781#[gpui::test]
1782fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1783 init_test(cx, |_| {});
1784
1785 let editor = cx.add_window(|window, cx| {
1786 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1787 build_editor(buffer, window, cx)
1788 });
1789 _ = editor.update(cx, |editor, window, cx| {
1790 editor.change_selections(None, window, cx, |s| {
1791 s.select_display_ranges([
1792 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1793 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1794 ])
1795 });
1796
1797 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1798 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1799
1800 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1801 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1802
1803 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1804 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1805
1806 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1807 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1808
1809 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1810 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1811
1812 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1813 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1814
1815 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1816 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1817
1818 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1819 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1820
1821 editor.move_right(&MoveRight, window, cx);
1822 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1823 assert_selection_ranges(
1824 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1825 editor,
1826 cx,
1827 );
1828
1829 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1830 assert_selection_ranges(
1831 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1832 editor,
1833 cx,
1834 );
1835
1836 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1837 assert_selection_ranges(
1838 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1839 editor,
1840 cx,
1841 );
1842 });
1843}
1844
1845#[gpui::test]
1846fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1847 init_test(cx, |_| {});
1848
1849 let editor = cx.add_window(|window, cx| {
1850 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1851 build_editor(buffer, window, cx)
1852 });
1853
1854 _ = editor.update(cx, |editor, window, cx| {
1855 editor.set_wrap_width(Some(140.0.into()), cx);
1856 assert_eq!(
1857 editor.display_text(cx),
1858 "use one::{\n two::three::\n four::five\n};"
1859 );
1860
1861 editor.change_selections(None, window, cx, |s| {
1862 s.select_display_ranges([
1863 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1864 ]);
1865 });
1866
1867 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1868 assert_eq!(
1869 editor.selections.display_ranges(cx),
1870 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1871 );
1872
1873 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1874 assert_eq!(
1875 editor.selections.display_ranges(cx),
1876 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1877 );
1878
1879 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1880 assert_eq!(
1881 editor.selections.display_ranges(cx),
1882 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1883 );
1884
1885 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1886 assert_eq!(
1887 editor.selections.display_ranges(cx),
1888 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1889 );
1890
1891 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1892 assert_eq!(
1893 editor.selections.display_ranges(cx),
1894 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1895 );
1896
1897 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1898 assert_eq!(
1899 editor.selections.display_ranges(cx),
1900 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1901 );
1902 });
1903}
1904
1905#[gpui::test]
1906async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
1907 init_test(cx, |_| {});
1908 let mut cx = EditorTestContext::new(cx).await;
1909
1910 let line_height = cx.editor(|editor, window, _| {
1911 editor
1912 .style()
1913 .unwrap()
1914 .text
1915 .line_height_in_pixels(window.rem_size())
1916 });
1917 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1918
1919 cx.set_state(
1920 &r#"ˇone
1921 two
1922
1923 three
1924 fourˇ
1925 five
1926
1927 six"#
1928 .unindent(),
1929 );
1930
1931 cx.update_editor(|editor, window, cx| {
1932 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1933 });
1934 cx.assert_editor_state(
1935 &r#"one
1936 two
1937 ˇ
1938 three
1939 four
1940 five
1941 ˇ
1942 six"#
1943 .unindent(),
1944 );
1945
1946 cx.update_editor(|editor, window, cx| {
1947 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1948 });
1949 cx.assert_editor_state(
1950 &r#"one
1951 two
1952
1953 three
1954 four
1955 five
1956 ˇ
1957 sixˇ"#
1958 .unindent(),
1959 );
1960
1961 cx.update_editor(|editor, window, cx| {
1962 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1963 });
1964 cx.assert_editor_state(
1965 &r#"one
1966 two
1967
1968 three
1969 four
1970 five
1971
1972 sixˇ"#
1973 .unindent(),
1974 );
1975
1976 cx.update_editor(|editor, window, cx| {
1977 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1978 });
1979 cx.assert_editor_state(
1980 &r#"one
1981 two
1982
1983 three
1984 four
1985 five
1986 ˇ
1987 six"#
1988 .unindent(),
1989 );
1990
1991 cx.update_editor(|editor, window, cx| {
1992 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1993 });
1994 cx.assert_editor_state(
1995 &r#"one
1996 two
1997 ˇ
1998 three
1999 four
2000 five
2001
2002 six"#
2003 .unindent(),
2004 );
2005
2006 cx.update_editor(|editor, window, cx| {
2007 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2008 });
2009 cx.assert_editor_state(
2010 &r#"ˇone
2011 two
2012
2013 three
2014 four
2015 five
2016
2017 six"#
2018 .unindent(),
2019 );
2020}
2021
2022#[gpui::test]
2023async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2024 init_test(cx, |_| {});
2025 let mut cx = EditorTestContext::new(cx).await;
2026 let line_height = cx.editor(|editor, window, _| {
2027 editor
2028 .style()
2029 .unwrap()
2030 .text
2031 .line_height_in_pixels(window.rem_size())
2032 });
2033 let window = cx.window;
2034 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2035
2036 cx.set_state(
2037 r#"ˇone
2038 two
2039 three
2040 four
2041 five
2042 six
2043 seven
2044 eight
2045 nine
2046 ten
2047 "#,
2048 );
2049
2050 cx.update_editor(|editor, window, cx| {
2051 assert_eq!(
2052 editor.snapshot(window, cx).scroll_position(),
2053 gpui::Point::new(0., 0.)
2054 );
2055 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2056 assert_eq!(
2057 editor.snapshot(window, cx).scroll_position(),
2058 gpui::Point::new(0., 3.)
2059 );
2060 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2061 assert_eq!(
2062 editor.snapshot(window, cx).scroll_position(),
2063 gpui::Point::new(0., 6.)
2064 );
2065 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2066 assert_eq!(
2067 editor.snapshot(window, cx).scroll_position(),
2068 gpui::Point::new(0., 3.)
2069 );
2070
2071 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2072 assert_eq!(
2073 editor.snapshot(window, cx).scroll_position(),
2074 gpui::Point::new(0., 1.)
2075 );
2076 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2077 assert_eq!(
2078 editor.snapshot(window, cx).scroll_position(),
2079 gpui::Point::new(0., 3.)
2080 );
2081 });
2082}
2083
2084#[gpui::test]
2085async fn test_autoscroll(cx: &mut TestAppContext) {
2086 init_test(cx, |_| {});
2087 let mut cx = EditorTestContext::new(cx).await;
2088
2089 let line_height = cx.update_editor(|editor, window, cx| {
2090 editor.set_vertical_scroll_margin(2, cx);
2091 editor
2092 .style()
2093 .unwrap()
2094 .text
2095 .line_height_in_pixels(window.rem_size())
2096 });
2097 let window = cx.window;
2098 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2099
2100 cx.set_state(
2101 r#"ˇone
2102 two
2103 three
2104 four
2105 five
2106 six
2107 seven
2108 eight
2109 nine
2110 ten
2111 "#,
2112 );
2113 cx.update_editor(|editor, window, cx| {
2114 assert_eq!(
2115 editor.snapshot(window, cx).scroll_position(),
2116 gpui::Point::new(0., 0.0)
2117 );
2118 });
2119
2120 // Add a cursor below the visible area. Since both cursors cannot fit
2121 // on screen, the editor autoscrolls to reveal the newest cursor, and
2122 // allows the vertical scroll margin below that cursor.
2123 cx.update_editor(|editor, window, cx| {
2124 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2125 selections.select_ranges([
2126 Point::new(0, 0)..Point::new(0, 0),
2127 Point::new(6, 0)..Point::new(6, 0),
2128 ]);
2129 })
2130 });
2131 cx.update_editor(|editor, window, cx| {
2132 assert_eq!(
2133 editor.snapshot(window, cx).scroll_position(),
2134 gpui::Point::new(0., 3.0)
2135 );
2136 });
2137
2138 // Move down. The editor cursor scrolls down to track the newest cursor.
2139 cx.update_editor(|editor, window, cx| {
2140 editor.move_down(&Default::default(), window, cx);
2141 });
2142 cx.update_editor(|editor, window, cx| {
2143 assert_eq!(
2144 editor.snapshot(window, cx).scroll_position(),
2145 gpui::Point::new(0., 4.0)
2146 );
2147 });
2148
2149 // Add a cursor above the visible area. Since both cursors fit on screen,
2150 // the editor scrolls to show both.
2151 cx.update_editor(|editor, window, cx| {
2152 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2153 selections.select_ranges([
2154 Point::new(1, 0)..Point::new(1, 0),
2155 Point::new(6, 0)..Point::new(6, 0),
2156 ]);
2157 })
2158 });
2159 cx.update_editor(|editor, window, cx| {
2160 assert_eq!(
2161 editor.snapshot(window, cx).scroll_position(),
2162 gpui::Point::new(0., 1.0)
2163 );
2164 });
2165}
2166
2167#[gpui::test]
2168async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2169 init_test(cx, |_| {});
2170 let mut cx = EditorTestContext::new(cx).await;
2171
2172 let line_height = cx.editor(|editor, window, _cx| {
2173 editor
2174 .style()
2175 .unwrap()
2176 .text
2177 .line_height_in_pixels(window.rem_size())
2178 });
2179 let window = cx.window;
2180 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2181 cx.set_state(
2182 &r#"
2183 ˇone
2184 two
2185 threeˇ
2186 four
2187 five
2188 six
2189 seven
2190 eight
2191 nine
2192 ten
2193 "#
2194 .unindent(),
2195 );
2196
2197 cx.update_editor(|editor, window, cx| {
2198 editor.move_page_down(&MovePageDown::default(), window, cx)
2199 });
2200 cx.assert_editor_state(
2201 &r#"
2202 one
2203 two
2204 three
2205 ˇfour
2206 five
2207 sixˇ
2208 seven
2209 eight
2210 nine
2211 ten
2212 "#
2213 .unindent(),
2214 );
2215
2216 cx.update_editor(|editor, window, cx| {
2217 editor.move_page_down(&MovePageDown::default(), window, cx)
2218 });
2219 cx.assert_editor_state(
2220 &r#"
2221 one
2222 two
2223 three
2224 four
2225 five
2226 six
2227 ˇseven
2228 eight
2229 nineˇ
2230 ten
2231 "#
2232 .unindent(),
2233 );
2234
2235 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2236 cx.assert_editor_state(
2237 &r#"
2238 one
2239 two
2240 three
2241 ˇfour
2242 five
2243 sixˇ
2244 seven
2245 eight
2246 nine
2247 ten
2248 "#
2249 .unindent(),
2250 );
2251
2252 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2253 cx.assert_editor_state(
2254 &r#"
2255 ˇone
2256 two
2257 threeˇ
2258 four
2259 five
2260 six
2261 seven
2262 eight
2263 nine
2264 ten
2265 "#
2266 .unindent(),
2267 );
2268
2269 // Test select collapsing
2270 cx.update_editor(|editor, window, cx| {
2271 editor.move_page_down(&MovePageDown::default(), window, cx);
2272 editor.move_page_down(&MovePageDown::default(), window, cx);
2273 editor.move_page_down(&MovePageDown::default(), window, cx);
2274 });
2275 cx.assert_editor_state(
2276 &r#"
2277 one
2278 two
2279 three
2280 four
2281 five
2282 six
2283 seven
2284 eight
2285 nine
2286 ˇten
2287 ˇ"#
2288 .unindent(),
2289 );
2290}
2291
2292#[gpui::test]
2293async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2294 init_test(cx, |_| {});
2295 let mut cx = EditorTestContext::new(cx).await;
2296 cx.set_state("one «two threeˇ» four");
2297 cx.update_editor(|editor, window, cx| {
2298 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
2299 assert_eq!(editor.text(cx), " four");
2300 });
2301}
2302
2303#[gpui::test]
2304fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2305 init_test(cx, |_| {});
2306
2307 let editor = cx.add_window(|window, cx| {
2308 let buffer = MultiBuffer::build_simple("one two three four", cx);
2309 build_editor(buffer.clone(), window, cx)
2310 });
2311
2312 _ = editor.update(cx, |editor, window, cx| {
2313 editor.change_selections(None, window, cx, |s| {
2314 s.select_display_ranges([
2315 // an empty selection - the preceding word fragment is deleted
2316 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2317 // characters selected - they are deleted
2318 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2319 ])
2320 });
2321 editor.delete_to_previous_word_start(
2322 &DeleteToPreviousWordStart {
2323 ignore_newlines: false,
2324 },
2325 window,
2326 cx,
2327 );
2328 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2329 });
2330
2331 _ = editor.update(cx, |editor, window, cx| {
2332 editor.change_selections(None, window, cx, |s| {
2333 s.select_display_ranges([
2334 // an empty selection - the following word fragment is deleted
2335 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2336 // characters selected - they are deleted
2337 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2338 ])
2339 });
2340 editor.delete_to_next_word_end(
2341 &DeleteToNextWordEnd {
2342 ignore_newlines: false,
2343 },
2344 window,
2345 cx,
2346 );
2347 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2348 });
2349}
2350
2351#[gpui::test]
2352fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2353 init_test(cx, |_| {});
2354
2355 let editor = cx.add_window(|window, cx| {
2356 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2357 build_editor(buffer.clone(), window, cx)
2358 });
2359 let del_to_prev_word_start = DeleteToPreviousWordStart {
2360 ignore_newlines: false,
2361 };
2362 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2363 ignore_newlines: true,
2364 };
2365
2366 _ = editor.update(cx, |editor, window, cx| {
2367 editor.change_selections(None, window, cx, |s| {
2368 s.select_display_ranges([
2369 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2370 ])
2371 });
2372 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2373 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2374 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2375 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2376 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2377 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2378 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2379 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2380 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2381 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2382 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2383 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2384 });
2385}
2386
2387#[gpui::test]
2388fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2389 init_test(cx, |_| {});
2390
2391 let editor = cx.add_window(|window, cx| {
2392 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2393 build_editor(buffer.clone(), window, cx)
2394 });
2395 let del_to_next_word_end = DeleteToNextWordEnd {
2396 ignore_newlines: false,
2397 };
2398 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2399 ignore_newlines: true,
2400 };
2401
2402 _ = editor.update(cx, |editor, window, cx| {
2403 editor.change_selections(None, window, cx, |s| {
2404 s.select_display_ranges([
2405 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2406 ])
2407 });
2408 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2409 assert_eq!(
2410 editor.buffer.read(cx).read(cx).text(),
2411 "one\n two\nthree\n four"
2412 );
2413 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2414 assert_eq!(
2415 editor.buffer.read(cx).read(cx).text(),
2416 "\n two\nthree\n four"
2417 );
2418 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2419 assert_eq!(
2420 editor.buffer.read(cx).read(cx).text(),
2421 "two\nthree\n four"
2422 );
2423 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2424 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2425 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2426 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2427 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2428 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2429 });
2430}
2431
2432#[gpui::test]
2433fn test_newline(cx: &mut TestAppContext) {
2434 init_test(cx, |_| {});
2435
2436 let editor = cx.add_window(|window, cx| {
2437 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2438 build_editor(buffer.clone(), window, cx)
2439 });
2440
2441 _ = editor.update(cx, |editor, window, cx| {
2442 editor.change_selections(None, window, cx, |s| {
2443 s.select_display_ranges([
2444 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2445 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2446 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2447 ])
2448 });
2449
2450 editor.newline(&Newline, window, cx);
2451 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2452 });
2453}
2454
2455#[gpui::test]
2456fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2457 init_test(cx, |_| {});
2458
2459 let editor = cx.add_window(|window, cx| {
2460 let buffer = MultiBuffer::build_simple(
2461 "
2462 a
2463 b(
2464 X
2465 )
2466 c(
2467 X
2468 )
2469 "
2470 .unindent()
2471 .as_str(),
2472 cx,
2473 );
2474 let mut editor = build_editor(buffer.clone(), window, cx);
2475 editor.change_selections(None, window, cx, |s| {
2476 s.select_ranges([
2477 Point::new(2, 4)..Point::new(2, 5),
2478 Point::new(5, 4)..Point::new(5, 5),
2479 ])
2480 });
2481 editor
2482 });
2483
2484 _ = editor.update(cx, |editor, window, cx| {
2485 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2486 editor.buffer.update(cx, |buffer, cx| {
2487 buffer.edit(
2488 [
2489 (Point::new(1, 2)..Point::new(3, 0), ""),
2490 (Point::new(4, 2)..Point::new(6, 0), ""),
2491 ],
2492 None,
2493 cx,
2494 );
2495 assert_eq!(
2496 buffer.read(cx).text(),
2497 "
2498 a
2499 b()
2500 c()
2501 "
2502 .unindent()
2503 );
2504 });
2505 assert_eq!(
2506 editor.selections.ranges(cx),
2507 &[
2508 Point::new(1, 2)..Point::new(1, 2),
2509 Point::new(2, 2)..Point::new(2, 2),
2510 ],
2511 );
2512
2513 editor.newline(&Newline, window, cx);
2514 assert_eq!(
2515 editor.text(cx),
2516 "
2517 a
2518 b(
2519 )
2520 c(
2521 )
2522 "
2523 .unindent()
2524 );
2525
2526 // The selections are moved after the inserted newlines
2527 assert_eq!(
2528 editor.selections.ranges(cx),
2529 &[
2530 Point::new(2, 0)..Point::new(2, 0),
2531 Point::new(4, 0)..Point::new(4, 0),
2532 ],
2533 );
2534 });
2535}
2536
2537#[gpui::test]
2538async fn test_newline_above(cx: &mut TestAppContext) {
2539 init_test(cx, |settings| {
2540 settings.defaults.tab_size = NonZeroU32::new(4)
2541 });
2542
2543 let language = Arc::new(
2544 Language::new(
2545 LanguageConfig::default(),
2546 Some(tree_sitter_rust::LANGUAGE.into()),
2547 )
2548 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2549 .unwrap(),
2550 );
2551
2552 let mut cx = EditorTestContext::new(cx).await;
2553 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2554 cx.set_state(indoc! {"
2555 const a: ˇA = (
2556 (ˇ
2557 «const_functionˇ»(ˇ),
2558 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2559 )ˇ
2560 ˇ);ˇ
2561 "});
2562
2563 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2564 cx.assert_editor_state(indoc! {"
2565 ˇ
2566 const a: A = (
2567 ˇ
2568 (
2569 ˇ
2570 ˇ
2571 const_function(),
2572 ˇ
2573 ˇ
2574 ˇ
2575 ˇ
2576 something_else,
2577 ˇ
2578 )
2579 ˇ
2580 ˇ
2581 );
2582 "});
2583}
2584
2585#[gpui::test]
2586async fn test_newline_below(cx: &mut TestAppContext) {
2587 init_test(cx, |settings| {
2588 settings.defaults.tab_size = NonZeroU32::new(4)
2589 });
2590
2591 let language = Arc::new(
2592 Language::new(
2593 LanguageConfig::default(),
2594 Some(tree_sitter_rust::LANGUAGE.into()),
2595 )
2596 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2597 .unwrap(),
2598 );
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2602 cx.set_state(indoc! {"
2603 const a: ˇA = (
2604 (ˇ
2605 «const_functionˇ»(ˇ),
2606 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2607 )ˇ
2608 ˇ);ˇ
2609 "});
2610
2611 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2612 cx.assert_editor_state(indoc! {"
2613 const a: A = (
2614 ˇ
2615 (
2616 ˇ
2617 const_function(),
2618 ˇ
2619 ˇ
2620 something_else,
2621 ˇ
2622 ˇ
2623 ˇ
2624 ˇ
2625 )
2626 ˇ
2627 );
2628 ˇ
2629 ˇ
2630 "});
2631}
2632
2633#[gpui::test]
2634async fn test_newline_comments(cx: &mut TestAppContext) {
2635 init_test(cx, |settings| {
2636 settings.defaults.tab_size = NonZeroU32::new(4)
2637 });
2638
2639 let language = Arc::new(Language::new(
2640 LanguageConfig {
2641 line_comments: vec!["//".into()],
2642 ..LanguageConfig::default()
2643 },
2644 None,
2645 ));
2646 {
2647 let mut cx = EditorTestContext::new(cx).await;
2648 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2649 cx.set_state(indoc! {"
2650 // Fooˇ
2651 "});
2652
2653 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2654 cx.assert_editor_state(indoc! {"
2655 // Foo
2656 //ˇ
2657 "});
2658 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2659 cx.set_state(indoc! {"
2660 ˇ// Foo
2661 "});
2662 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2663 cx.assert_editor_state(indoc! {"
2664
2665 ˇ// Foo
2666 "});
2667 }
2668 // Ensure that comment continuations can be disabled.
2669 update_test_language_settings(cx, |settings| {
2670 settings.defaults.extend_comment_on_newline = Some(false);
2671 });
2672 let mut cx = EditorTestContext::new(cx).await;
2673 cx.set_state(indoc! {"
2674 // Fooˇ
2675 "});
2676 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2677 cx.assert_editor_state(indoc! {"
2678 // Foo
2679 ˇ
2680 "});
2681}
2682
2683#[gpui::test]
2684fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2685 init_test(cx, |_| {});
2686
2687 let editor = cx.add_window(|window, cx| {
2688 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2689 let mut editor = build_editor(buffer.clone(), window, cx);
2690 editor.change_selections(None, window, cx, |s| {
2691 s.select_ranges([3..4, 11..12, 19..20])
2692 });
2693 editor
2694 });
2695
2696 _ = editor.update(cx, |editor, window, cx| {
2697 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2698 editor.buffer.update(cx, |buffer, cx| {
2699 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2700 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2701 });
2702 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2703
2704 editor.insert("Z", window, cx);
2705 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2706
2707 // The selections are moved after the inserted characters
2708 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2709 });
2710}
2711
2712#[gpui::test]
2713async fn test_tab(cx: &mut TestAppContext) {
2714 init_test(cx, |settings| {
2715 settings.defaults.tab_size = NonZeroU32::new(3)
2716 });
2717
2718 let mut cx = EditorTestContext::new(cx).await;
2719 cx.set_state(indoc! {"
2720 ˇabˇc
2721 ˇ🏀ˇ🏀ˇefg
2722 dˇ
2723 "});
2724 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2725 cx.assert_editor_state(indoc! {"
2726 ˇab ˇc
2727 ˇ🏀 ˇ🏀 ˇefg
2728 d ˇ
2729 "});
2730
2731 cx.set_state(indoc! {"
2732 a
2733 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2734 "});
2735 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2736 cx.assert_editor_state(indoc! {"
2737 a
2738 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2739 "});
2740}
2741
2742#[gpui::test]
2743async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2744 init_test(cx, |_| {});
2745
2746 let mut cx = EditorTestContext::new(cx).await;
2747 let language = Arc::new(
2748 Language::new(
2749 LanguageConfig::default(),
2750 Some(tree_sitter_rust::LANGUAGE.into()),
2751 )
2752 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2753 .unwrap(),
2754 );
2755 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2756
2757 // cursors that are already at the suggested indent level insert
2758 // a soft tab. cursors that are to the left of the suggested indent
2759 // auto-indent their line.
2760 cx.set_state(indoc! {"
2761 ˇ
2762 const a: B = (
2763 c(
2764 d(
2765 ˇ
2766 )
2767 ˇ
2768 ˇ )
2769 );
2770 "});
2771 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2772 cx.assert_editor_state(indoc! {"
2773 ˇ
2774 const a: B = (
2775 c(
2776 d(
2777 ˇ
2778 )
2779 ˇ
2780 ˇ)
2781 );
2782 "});
2783
2784 // handle auto-indent when there are multiple cursors on the same line
2785 cx.set_state(indoc! {"
2786 const a: B = (
2787 c(
2788 ˇ ˇ
2789 ˇ )
2790 );
2791 "});
2792 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2793 cx.assert_editor_state(indoc! {"
2794 const a: B = (
2795 c(
2796 ˇ
2797 ˇ)
2798 );
2799 "});
2800}
2801
2802#[gpui::test]
2803async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2804 init_test(cx, |settings| {
2805 settings.defaults.tab_size = NonZeroU32::new(4)
2806 });
2807
2808 let language = Arc::new(
2809 Language::new(
2810 LanguageConfig::default(),
2811 Some(tree_sitter_rust::LANGUAGE.into()),
2812 )
2813 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2814 .unwrap(),
2815 );
2816
2817 let mut cx = EditorTestContext::new(cx).await;
2818 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2819 cx.set_state(indoc! {"
2820 fn a() {
2821 if b {
2822 \t ˇc
2823 }
2824 }
2825 "});
2826
2827 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2828 cx.assert_editor_state(indoc! {"
2829 fn a() {
2830 if b {
2831 ˇc
2832 }
2833 }
2834 "});
2835}
2836
2837#[gpui::test]
2838async fn test_indent_outdent(cx: &mut TestAppContext) {
2839 init_test(cx, |settings| {
2840 settings.defaults.tab_size = NonZeroU32::new(4);
2841 });
2842
2843 let mut cx = EditorTestContext::new(cx).await;
2844
2845 cx.set_state(indoc! {"
2846 «oneˇ» «twoˇ»
2847 three
2848 four
2849 "});
2850 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2851 cx.assert_editor_state(indoc! {"
2852 «oneˇ» «twoˇ»
2853 three
2854 four
2855 "});
2856
2857 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ» four
2869 "});
2870 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 t«hree
2874 ˇ» four
2875 "});
2876
2877 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2878 cx.assert_editor_state(indoc! {"
2879 one two
2880 t«hree
2881 ˇ» four
2882 "});
2883
2884 // Ensure that indenting/outdenting works when the cursor is at column 0.
2885 cx.set_state(indoc! {"
2886 one two
2887 ˇthree
2888 four
2889 "});
2890 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2891 cx.assert_editor_state(indoc! {"
2892 one two
2893 ˇthree
2894 four
2895 "});
2896
2897 cx.set_state(indoc! {"
2898 one two
2899 ˇ three
2900 four
2901 "});
2902 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2903 cx.assert_editor_state(indoc! {"
2904 one two
2905 ˇthree
2906 four
2907 "});
2908}
2909
2910#[gpui::test]
2911async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
2912 init_test(cx, |settings| {
2913 settings.defaults.hard_tabs = Some(true);
2914 });
2915
2916 let mut cx = EditorTestContext::new(cx).await;
2917
2918 // select two ranges on one line
2919 cx.set_state(indoc! {"
2920 «oneˇ» «twoˇ»
2921 three
2922 four
2923 "});
2924 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2925 cx.assert_editor_state(indoc! {"
2926 \t«oneˇ» «twoˇ»
2927 three
2928 four
2929 "});
2930 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2931 cx.assert_editor_state(indoc! {"
2932 \t\t«oneˇ» «twoˇ»
2933 three
2934 four
2935 "});
2936 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2937 cx.assert_editor_state(indoc! {"
2938 \t«oneˇ» «twoˇ»
2939 three
2940 four
2941 "});
2942 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2943 cx.assert_editor_state(indoc! {"
2944 «oneˇ» «twoˇ»
2945 three
2946 four
2947 "});
2948
2949 // select across a line ending
2950 cx.set_state(indoc! {"
2951 one two
2952 t«hree
2953 ˇ»four
2954 "});
2955 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2956 cx.assert_editor_state(indoc! {"
2957 one two
2958 \tt«hree
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 \t\tt«hree
2965 ˇ»four
2966 "});
2967 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2968 cx.assert_editor_state(indoc! {"
2969 one two
2970 \tt«hree
2971 ˇ»four
2972 "});
2973 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2974 cx.assert_editor_state(indoc! {"
2975 one two
2976 t«hree
2977 ˇ»four
2978 "});
2979
2980 // Ensure that indenting/outdenting works when the cursor is at column 0.
2981 cx.set_state(indoc! {"
2982 one two
2983 ˇthree
2984 four
2985 "});
2986 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2987 cx.assert_editor_state(indoc! {"
2988 one two
2989 ˇthree
2990 four
2991 "});
2992 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2993 cx.assert_editor_state(indoc! {"
2994 one two
2995 \tˇthree
2996 four
2997 "});
2998 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2999 cx.assert_editor_state(indoc! {"
3000 one two
3001 ˇthree
3002 four
3003 "});
3004}
3005
3006#[gpui::test]
3007fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3008 init_test(cx, |settings| {
3009 settings.languages.extend([
3010 (
3011 "TOML".into(),
3012 LanguageSettingsContent {
3013 tab_size: NonZeroU32::new(2),
3014 ..Default::default()
3015 },
3016 ),
3017 (
3018 "Rust".into(),
3019 LanguageSettingsContent {
3020 tab_size: NonZeroU32::new(4),
3021 ..Default::default()
3022 },
3023 ),
3024 ]);
3025 });
3026
3027 let toml_language = Arc::new(Language::new(
3028 LanguageConfig {
3029 name: "TOML".into(),
3030 ..Default::default()
3031 },
3032 None,
3033 ));
3034 let rust_language = Arc::new(Language::new(
3035 LanguageConfig {
3036 name: "Rust".into(),
3037 ..Default::default()
3038 },
3039 None,
3040 ));
3041
3042 let toml_buffer =
3043 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3044 let rust_buffer =
3045 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3046 let multibuffer = cx.new(|cx| {
3047 let mut multibuffer = MultiBuffer::new(ReadWrite);
3048 multibuffer.push_excerpts(
3049 toml_buffer.clone(),
3050 [ExcerptRange {
3051 context: Point::new(0, 0)..Point::new(2, 0),
3052 primary: None,
3053 }],
3054 cx,
3055 );
3056 multibuffer.push_excerpts(
3057 rust_buffer.clone(),
3058 [ExcerptRange {
3059 context: Point::new(0, 0)..Point::new(1, 0),
3060 primary: None,
3061 }],
3062 cx,
3063 );
3064 multibuffer
3065 });
3066
3067 cx.add_window(|window, cx| {
3068 let mut editor = build_editor(multibuffer, window, cx);
3069
3070 assert_eq!(
3071 editor.text(cx),
3072 indoc! {"
3073 a = 1
3074 b = 2
3075
3076 const c: usize = 3;
3077 "}
3078 );
3079
3080 select_ranges(
3081 &mut editor,
3082 indoc! {"
3083 «aˇ» = 1
3084 b = 2
3085
3086 «const c:ˇ» usize = 3;
3087 "},
3088 window,
3089 cx,
3090 );
3091
3092 editor.tab(&Tab, window, cx);
3093 assert_text_with_selections(
3094 &mut editor,
3095 indoc! {"
3096 «aˇ» = 1
3097 b = 2
3098
3099 «const c:ˇ» usize = 3;
3100 "},
3101 cx,
3102 );
3103 editor.tab_prev(&TabPrev, window, cx);
3104 assert_text_with_selections(
3105 &mut editor,
3106 indoc! {"
3107 «aˇ» = 1
3108 b = 2
3109
3110 «const c:ˇ» usize = 3;
3111 "},
3112 cx,
3113 );
3114
3115 editor
3116 });
3117}
3118
3119#[gpui::test]
3120async fn test_backspace(cx: &mut TestAppContext) {
3121 init_test(cx, |_| {});
3122
3123 let mut cx = EditorTestContext::new(cx).await;
3124
3125 // Basic backspace
3126 cx.set_state(indoc! {"
3127 onˇe two three
3128 fou«rˇ» five six
3129 seven «ˇeight nine
3130 »ten
3131 "});
3132 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3133 cx.assert_editor_state(indoc! {"
3134 oˇe two three
3135 fouˇ five six
3136 seven ˇten
3137 "});
3138
3139 // Test backspace inside and around indents
3140 cx.set_state(indoc! {"
3141 zero
3142 ˇone
3143 ˇtwo
3144 ˇ ˇ ˇ three
3145 ˇ ˇ four
3146 "});
3147 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3148 cx.assert_editor_state(indoc! {"
3149 zero
3150 ˇone
3151 ˇtwo
3152 ˇ threeˇ four
3153 "});
3154
3155 // Test backspace with line_mode set to true
3156 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3157 cx.set_state(indoc! {"
3158 The ˇquick ˇbrown
3159 fox jumps over
3160 the lazy dog
3161 ˇThe qu«ick bˇ»rown"});
3162 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3163 cx.assert_editor_state(indoc! {"
3164 ˇfox jumps over
3165 the lazy dogˇ"});
3166}
3167
3168#[gpui::test]
3169async fn test_delete(cx: &mut TestAppContext) {
3170 init_test(cx, |_| {});
3171
3172 let mut cx = EditorTestContext::new(cx).await;
3173 cx.set_state(indoc! {"
3174 onˇe two three
3175 fou«rˇ» five six
3176 seven «ˇeight nine
3177 »ten
3178 "});
3179 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3180 cx.assert_editor_state(indoc! {"
3181 onˇ two three
3182 fouˇ five six
3183 seven ˇten
3184 "});
3185
3186 // Test backspace with line_mode set to true
3187 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3188 cx.set_state(indoc! {"
3189 The ˇquick ˇbrown
3190 fox «ˇjum»ps over
3191 the lazy dog
3192 ˇThe qu«ick bˇ»rown"});
3193 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3194 cx.assert_editor_state("ˇthe lazy dogˇ");
3195}
3196
3197#[gpui::test]
3198fn test_delete_line(cx: &mut TestAppContext) {
3199 init_test(cx, |_| {});
3200
3201 let editor = cx.add_window(|window, cx| {
3202 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3203 build_editor(buffer, window, cx)
3204 });
3205 _ = editor.update(cx, |editor, window, cx| {
3206 editor.change_selections(None, window, cx, |s| {
3207 s.select_display_ranges([
3208 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3209 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3210 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3211 ])
3212 });
3213 editor.delete_line(&DeleteLine, window, cx);
3214 assert_eq!(editor.display_text(cx), "ghi");
3215 assert_eq!(
3216 editor.selections.display_ranges(cx),
3217 vec![
3218 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3219 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3220 ]
3221 );
3222 });
3223
3224 let editor = cx.add_window(|window, cx| {
3225 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3226 build_editor(buffer, window, cx)
3227 });
3228 _ = editor.update(cx, |editor, window, cx| {
3229 editor.change_selections(None, window, cx, |s| {
3230 s.select_display_ranges([
3231 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3232 ])
3233 });
3234 editor.delete_line(&DeleteLine, window, cx);
3235 assert_eq!(editor.display_text(cx), "ghi\n");
3236 assert_eq!(
3237 editor.selections.display_ranges(cx),
3238 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3239 );
3240 });
3241}
3242
3243#[gpui::test]
3244fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3245 init_test(cx, |_| {});
3246
3247 cx.add_window(|window, cx| {
3248 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3249 let mut editor = build_editor(buffer.clone(), window, cx);
3250 let buffer = buffer.read(cx).as_singleton().unwrap();
3251
3252 assert_eq!(
3253 editor.selections.ranges::<Point>(cx),
3254 &[Point::new(0, 0)..Point::new(0, 0)]
3255 );
3256
3257 // When on single line, replace newline at end by space
3258 editor.join_lines(&JoinLines, window, cx);
3259 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3260 assert_eq!(
3261 editor.selections.ranges::<Point>(cx),
3262 &[Point::new(0, 3)..Point::new(0, 3)]
3263 );
3264
3265 // When multiple lines are selected, remove newlines that are spanned by the selection
3266 editor.change_selections(None, window, cx, |s| {
3267 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3268 });
3269 editor.join_lines(&JoinLines, window, cx);
3270 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3271 assert_eq!(
3272 editor.selections.ranges::<Point>(cx),
3273 &[Point::new(0, 11)..Point::new(0, 11)]
3274 );
3275
3276 // Undo should be transactional
3277 editor.undo(&Undo, window, cx);
3278 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3279 assert_eq!(
3280 editor.selections.ranges::<Point>(cx),
3281 &[Point::new(0, 5)..Point::new(2, 2)]
3282 );
3283
3284 // When joining an empty line don't insert a space
3285 editor.change_selections(None, window, cx, |s| {
3286 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3287 });
3288 editor.join_lines(&JoinLines, window, cx);
3289 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3290 assert_eq!(
3291 editor.selections.ranges::<Point>(cx),
3292 [Point::new(2, 3)..Point::new(2, 3)]
3293 );
3294
3295 // We can remove trailing newlines
3296 editor.join_lines(&JoinLines, window, cx);
3297 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3298 assert_eq!(
3299 editor.selections.ranges::<Point>(cx),
3300 [Point::new(2, 3)..Point::new(2, 3)]
3301 );
3302
3303 // We don't blow up on the last line
3304 editor.join_lines(&JoinLines, window, cx);
3305 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3306 assert_eq!(
3307 editor.selections.ranges::<Point>(cx),
3308 [Point::new(2, 3)..Point::new(2, 3)]
3309 );
3310
3311 // reset to test indentation
3312 editor.buffer.update(cx, |buffer, cx| {
3313 buffer.edit(
3314 [
3315 (Point::new(1, 0)..Point::new(1, 2), " "),
3316 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3317 ],
3318 None,
3319 cx,
3320 )
3321 });
3322
3323 // We remove any leading spaces
3324 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3325 editor.change_selections(None, window, cx, |s| {
3326 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3327 });
3328 editor.join_lines(&JoinLines, window, cx);
3329 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3330
3331 // We don't insert a space for a line containing only spaces
3332 editor.join_lines(&JoinLines, window, cx);
3333 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3334
3335 // We ignore any leading tabs
3336 editor.join_lines(&JoinLines, window, cx);
3337 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3338
3339 editor
3340 });
3341}
3342
3343#[gpui::test]
3344fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3345 init_test(cx, |_| {});
3346
3347 cx.add_window(|window, cx| {
3348 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3349 let mut editor = build_editor(buffer.clone(), window, cx);
3350 let buffer = buffer.read(cx).as_singleton().unwrap();
3351
3352 editor.change_selections(None, window, cx, |s| {
3353 s.select_ranges([
3354 Point::new(0, 2)..Point::new(1, 1),
3355 Point::new(1, 2)..Point::new(1, 2),
3356 Point::new(3, 1)..Point::new(3, 2),
3357 ])
3358 });
3359
3360 editor.join_lines(&JoinLines, window, cx);
3361 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3362
3363 assert_eq!(
3364 editor.selections.ranges::<Point>(cx),
3365 [
3366 Point::new(0, 7)..Point::new(0, 7),
3367 Point::new(1, 3)..Point::new(1, 3)
3368 ]
3369 );
3370 editor
3371 });
3372}
3373
3374#[gpui::test]
3375async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3376 init_test(cx, |_| {});
3377
3378 let mut cx = EditorTestContext::new(cx).await;
3379
3380 let diff_base = r#"
3381 Line 0
3382 Line 1
3383 Line 2
3384 Line 3
3385 "#
3386 .unindent();
3387
3388 cx.set_state(
3389 &r#"
3390 ˇLine 0
3391 Line 1
3392 Line 2
3393 Line 3
3394 "#
3395 .unindent(),
3396 );
3397
3398 cx.set_head_text(&diff_base);
3399 executor.run_until_parked();
3400
3401 // Join lines
3402 cx.update_editor(|editor, window, cx| {
3403 editor.join_lines(&JoinLines, window, cx);
3404 });
3405 executor.run_until_parked();
3406
3407 cx.assert_editor_state(
3408 &r#"
3409 Line 0ˇ Line 1
3410 Line 2
3411 Line 3
3412 "#
3413 .unindent(),
3414 );
3415 // Join again
3416 cx.update_editor(|editor, window, cx| {
3417 editor.join_lines(&JoinLines, window, cx);
3418 });
3419 executor.run_until_parked();
3420
3421 cx.assert_editor_state(
3422 &r#"
3423 Line 0 Line 1ˇ Line 2
3424 Line 3
3425 "#
3426 .unindent(),
3427 );
3428}
3429
3430#[gpui::test]
3431async fn test_custom_newlines_cause_no_false_positive_diffs(
3432 executor: BackgroundExecutor,
3433 cx: &mut TestAppContext,
3434) {
3435 init_test(cx, |_| {});
3436 let mut cx = EditorTestContext::new(cx).await;
3437 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3438 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3439 executor.run_until_parked();
3440
3441 cx.update_editor(|editor, window, cx| {
3442 let snapshot = editor.snapshot(window, cx);
3443 assert_eq!(
3444 snapshot
3445 .buffer_snapshot
3446 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3447 .collect::<Vec<_>>(),
3448 Vec::new(),
3449 "Should not have any diffs for files with custom newlines"
3450 );
3451 });
3452}
3453
3454#[gpui::test]
3455async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3456 init_test(cx, |_| {});
3457
3458 let mut cx = EditorTestContext::new(cx).await;
3459
3460 // Test sort_lines_case_insensitive()
3461 cx.set_state(indoc! {"
3462 «z
3463 y
3464 x
3465 Z
3466 Y
3467 Xˇ»
3468 "});
3469 cx.update_editor(|e, window, cx| {
3470 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3471 });
3472 cx.assert_editor_state(indoc! {"
3473 «x
3474 X
3475 y
3476 Y
3477 z
3478 Zˇ»
3479 "});
3480
3481 // Test reverse_lines()
3482 cx.set_state(indoc! {"
3483 «5
3484 4
3485 3
3486 2
3487 1ˇ»
3488 "});
3489 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3490 cx.assert_editor_state(indoc! {"
3491 «1
3492 2
3493 3
3494 4
3495 5ˇ»
3496 "});
3497
3498 // Skip testing shuffle_line()
3499
3500 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3501 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3502
3503 // Don't manipulate when cursor is on single line, but expand the selection
3504 cx.set_state(indoc! {"
3505 ddˇdd
3506 ccc
3507 bb
3508 a
3509 "});
3510 cx.update_editor(|e, window, cx| {
3511 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3512 });
3513 cx.assert_editor_state(indoc! {"
3514 «ddddˇ»
3515 ccc
3516 bb
3517 a
3518 "});
3519
3520 // Basic manipulate case
3521 // Start selection moves to column 0
3522 // End of selection shrinks to fit shorter line
3523 cx.set_state(indoc! {"
3524 dd«d
3525 ccc
3526 bb
3527 aaaaaˇ»
3528 "});
3529 cx.update_editor(|e, window, cx| {
3530 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3531 });
3532 cx.assert_editor_state(indoc! {"
3533 «aaaaa
3534 bb
3535 ccc
3536 dddˇ»
3537 "});
3538
3539 // Manipulate case with newlines
3540 cx.set_state(indoc! {"
3541 dd«d
3542 ccc
3543
3544 bb
3545 aaaaa
3546
3547 ˇ»
3548 "});
3549 cx.update_editor(|e, window, cx| {
3550 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3551 });
3552 cx.assert_editor_state(indoc! {"
3553 «
3554
3555 aaaaa
3556 bb
3557 ccc
3558 dddˇ»
3559
3560 "});
3561
3562 // Adding new line
3563 cx.set_state(indoc! {"
3564 aa«a
3565 bbˇ»b
3566 "});
3567 cx.update_editor(|e, window, cx| {
3568 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3569 });
3570 cx.assert_editor_state(indoc! {"
3571 «aaa
3572 bbb
3573 added_lineˇ»
3574 "});
3575
3576 // Removing line
3577 cx.set_state(indoc! {"
3578 aa«a
3579 bbbˇ»
3580 "});
3581 cx.update_editor(|e, window, cx| {
3582 e.manipulate_lines(window, cx, |lines| {
3583 lines.pop();
3584 })
3585 });
3586 cx.assert_editor_state(indoc! {"
3587 «aaaˇ»
3588 "});
3589
3590 // Removing all lines
3591 cx.set_state(indoc! {"
3592 aa«a
3593 bbbˇ»
3594 "});
3595 cx.update_editor(|e, window, cx| {
3596 e.manipulate_lines(window, cx, |lines| {
3597 lines.drain(..);
3598 })
3599 });
3600 cx.assert_editor_state(indoc! {"
3601 ˇ
3602 "});
3603}
3604
3605#[gpui::test]
3606async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3607 init_test(cx, |_| {});
3608
3609 let mut cx = EditorTestContext::new(cx).await;
3610
3611 // Consider continuous selection as single selection
3612 cx.set_state(indoc! {"
3613 Aaa«aa
3614 cˇ»c«c
3615 bb
3616 aaaˇ»aa
3617 "});
3618 cx.update_editor(|e, window, cx| {
3619 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3620 });
3621 cx.assert_editor_state(indoc! {"
3622 «Aaaaa
3623 ccc
3624 bb
3625 aaaaaˇ»
3626 "});
3627
3628 cx.set_state(indoc! {"
3629 Aaa«aa
3630 cˇ»c«c
3631 bb
3632 aaaˇ»aa
3633 "});
3634 cx.update_editor(|e, window, cx| {
3635 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3636 });
3637 cx.assert_editor_state(indoc! {"
3638 «Aaaaa
3639 ccc
3640 bbˇ»
3641 "});
3642
3643 // Consider non continuous selection as distinct dedup operations
3644 cx.set_state(indoc! {"
3645 «aaaaa
3646 bb
3647 aaaaa
3648 aaaaaˇ»
3649
3650 aaa«aaˇ»
3651 "});
3652 cx.update_editor(|e, window, cx| {
3653 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3654 });
3655 cx.assert_editor_state(indoc! {"
3656 «aaaaa
3657 bbˇ»
3658
3659 «aaaaaˇ»
3660 "});
3661}
3662
3663#[gpui::test]
3664async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3665 init_test(cx, |_| {});
3666
3667 let mut cx = EditorTestContext::new(cx).await;
3668
3669 cx.set_state(indoc! {"
3670 «Aaa
3671 aAa
3672 Aaaˇ»
3673 "});
3674 cx.update_editor(|e, window, cx| {
3675 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3676 });
3677 cx.assert_editor_state(indoc! {"
3678 «Aaa
3679 aAaˇ»
3680 "});
3681
3682 cx.set_state(indoc! {"
3683 «Aaa
3684 aAa
3685 aaAˇ»
3686 "});
3687 cx.update_editor(|e, window, cx| {
3688 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3689 });
3690 cx.assert_editor_state(indoc! {"
3691 «Aaaˇ»
3692 "});
3693}
3694
3695#[gpui::test]
3696async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3697 init_test(cx, |_| {});
3698
3699 let mut cx = EditorTestContext::new(cx).await;
3700
3701 // Manipulate with multiple selections on a single line
3702 cx.set_state(indoc! {"
3703 dd«dd
3704 cˇ»c«c
3705 bb
3706 aaaˇ»aa
3707 "});
3708 cx.update_editor(|e, window, cx| {
3709 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3710 });
3711 cx.assert_editor_state(indoc! {"
3712 «aaaaa
3713 bb
3714 ccc
3715 ddddˇ»
3716 "});
3717
3718 // Manipulate with multiple disjoin selections
3719 cx.set_state(indoc! {"
3720 5«
3721 4
3722 3
3723 2
3724 1ˇ»
3725
3726 dd«dd
3727 ccc
3728 bb
3729 aaaˇ»aa
3730 "});
3731 cx.update_editor(|e, window, cx| {
3732 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3733 });
3734 cx.assert_editor_state(indoc! {"
3735 «1
3736 2
3737 3
3738 4
3739 5ˇ»
3740
3741 «aaaaa
3742 bb
3743 ccc
3744 ddddˇ»
3745 "});
3746
3747 // Adding lines on each selection
3748 cx.set_state(indoc! {"
3749 2«
3750 1ˇ»
3751
3752 bb«bb
3753 aaaˇ»aa
3754 "});
3755 cx.update_editor(|e, window, cx| {
3756 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3757 });
3758 cx.assert_editor_state(indoc! {"
3759 «2
3760 1
3761 added lineˇ»
3762
3763 «bbbb
3764 aaaaa
3765 added lineˇ»
3766 "});
3767
3768 // Removing lines on each selection
3769 cx.set_state(indoc! {"
3770 2«
3771 1ˇ»
3772
3773 bb«bb
3774 aaaˇ»aa
3775 "});
3776 cx.update_editor(|e, window, cx| {
3777 e.manipulate_lines(window, cx, |lines| {
3778 lines.pop();
3779 })
3780 });
3781 cx.assert_editor_state(indoc! {"
3782 «2ˇ»
3783
3784 «bbbbˇ»
3785 "});
3786}
3787
3788#[gpui::test]
3789async fn test_manipulate_text(cx: &mut TestAppContext) {
3790 init_test(cx, |_| {});
3791
3792 let mut cx = EditorTestContext::new(cx).await;
3793
3794 // Test convert_to_upper_case()
3795 cx.set_state(indoc! {"
3796 «hello worldˇ»
3797 "});
3798 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3799 cx.assert_editor_state(indoc! {"
3800 «HELLO WORLDˇ»
3801 "});
3802
3803 // Test convert_to_lower_case()
3804 cx.set_state(indoc! {"
3805 «HELLO WORLDˇ»
3806 "});
3807 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3808 cx.assert_editor_state(indoc! {"
3809 «hello worldˇ»
3810 "});
3811
3812 // Test multiple line, single selection case
3813 cx.set_state(indoc! {"
3814 «The quick brown
3815 fox jumps over
3816 the lazy dogˇ»
3817 "});
3818 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3819 cx.assert_editor_state(indoc! {"
3820 «The Quick Brown
3821 Fox Jumps Over
3822 The Lazy Dogˇ»
3823 "});
3824
3825 // Test multiple line, single selection case
3826 cx.set_state(indoc! {"
3827 «The quick brown
3828 fox jumps over
3829 the lazy dogˇ»
3830 "});
3831 cx.update_editor(|e, window, cx| {
3832 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3833 });
3834 cx.assert_editor_state(indoc! {"
3835 «TheQuickBrown
3836 FoxJumpsOver
3837 TheLazyDogˇ»
3838 "});
3839
3840 // From here on out, test more complex cases of manipulate_text()
3841
3842 // Test no selection case - should affect words cursors are in
3843 // Cursor at beginning, middle, and end of word
3844 cx.set_state(indoc! {"
3845 ˇhello big beauˇtiful worldˇ
3846 "});
3847 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3848 cx.assert_editor_state(indoc! {"
3849 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3850 "});
3851
3852 // Test multiple selections on a single line and across multiple lines
3853 cx.set_state(indoc! {"
3854 «Theˇ» quick «brown
3855 foxˇ» jumps «overˇ»
3856 the «lazyˇ» dog
3857 "});
3858 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3859 cx.assert_editor_state(indoc! {"
3860 «THEˇ» quick «BROWN
3861 FOXˇ» jumps «OVERˇ»
3862 the «LAZYˇ» dog
3863 "});
3864
3865 // Test case where text length grows
3866 cx.set_state(indoc! {"
3867 «tschüߡ»
3868 "});
3869 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3870 cx.assert_editor_state(indoc! {"
3871 «TSCHÜSSˇ»
3872 "});
3873
3874 // Test to make sure we don't crash when text shrinks
3875 cx.set_state(indoc! {"
3876 aaa_bbbˇ
3877 "});
3878 cx.update_editor(|e, window, cx| {
3879 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3880 });
3881 cx.assert_editor_state(indoc! {"
3882 «aaaBbbˇ»
3883 "});
3884
3885 // Test to make sure we all aware of the fact that each word can grow and shrink
3886 // Final selections should be aware of this fact
3887 cx.set_state(indoc! {"
3888 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3889 "});
3890 cx.update_editor(|e, window, cx| {
3891 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3892 });
3893 cx.assert_editor_state(indoc! {"
3894 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3895 "});
3896
3897 cx.set_state(indoc! {"
3898 «hElLo, WoRld!ˇ»
3899 "});
3900 cx.update_editor(|e, window, cx| {
3901 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3902 });
3903 cx.assert_editor_state(indoc! {"
3904 «HeLlO, wOrLD!ˇ»
3905 "});
3906}
3907
3908#[gpui::test]
3909fn test_duplicate_line(cx: &mut TestAppContext) {
3910 init_test(cx, |_| {});
3911
3912 let editor = cx.add_window(|window, cx| {
3913 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3914 build_editor(buffer, window, cx)
3915 });
3916 _ = editor.update(cx, |editor, window, cx| {
3917 editor.change_selections(None, window, cx, |s| {
3918 s.select_display_ranges([
3919 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3920 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3921 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3922 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3923 ])
3924 });
3925 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3926 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3927 assert_eq!(
3928 editor.selections.display_ranges(cx),
3929 vec![
3930 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3931 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3932 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3933 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3934 ]
3935 );
3936 });
3937
3938 let editor = cx.add_window(|window, cx| {
3939 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3940 build_editor(buffer, window, cx)
3941 });
3942 _ = editor.update(cx, |editor, window, cx| {
3943 editor.change_selections(None, window, cx, |s| {
3944 s.select_display_ranges([
3945 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3946 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3947 ])
3948 });
3949 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3950 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3951 assert_eq!(
3952 editor.selections.display_ranges(cx),
3953 vec![
3954 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3955 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3956 ]
3957 );
3958 });
3959
3960 // With `move_upwards` the selections stay in place, except for
3961 // the lines inserted above them
3962 let editor = cx.add_window(|window, cx| {
3963 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3964 build_editor(buffer, window, cx)
3965 });
3966 _ = editor.update(cx, |editor, window, cx| {
3967 editor.change_selections(None, window, cx, |s| {
3968 s.select_display_ranges([
3969 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3970 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3971 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3972 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3973 ])
3974 });
3975 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3976 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3977 assert_eq!(
3978 editor.selections.display_ranges(cx),
3979 vec![
3980 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3981 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3982 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3983 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3984 ]
3985 );
3986 });
3987
3988 let editor = cx.add_window(|window, cx| {
3989 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3990 build_editor(buffer, window, cx)
3991 });
3992 _ = editor.update(cx, |editor, window, cx| {
3993 editor.change_selections(None, window, cx, |s| {
3994 s.select_display_ranges([
3995 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3996 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3997 ])
3998 });
3999 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4000 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4001 assert_eq!(
4002 editor.selections.display_ranges(cx),
4003 vec![
4004 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4005 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4006 ]
4007 );
4008 });
4009
4010 let editor = cx.add_window(|window, cx| {
4011 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4012 build_editor(buffer, window, cx)
4013 });
4014 _ = editor.update(cx, |editor, window, cx| {
4015 editor.change_selections(None, window, cx, |s| {
4016 s.select_display_ranges([
4017 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4018 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4019 ])
4020 });
4021 editor.duplicate_selection(&DuplicateSelection, window, cx);
4022 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4023 assert_eq!(
4024 editor.selections.display_ranges(cx),
4025 vec![
4026 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4027 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4028 ]
4029 );
4030 });
4031}
4032
4033#[gpui::test]
4034fn test_move_line_up_down(cx: &mut TestAppContext) {
4035 init_test(cx, |_| {});
4036
4037 let editor = cx.add_window(|window, cx| {
4038 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4039 build_editor(buffer, window, cx)
4040 });
4041 _ = editor.update(cx, |editor, window, cx| {
4042 editor.fold_creases(
4043 vec![
4044 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4045 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4046 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4047 ],
4048 true,
4049 window,
4050 cx,
4051 );
4052 editor.change_selections(None, window, cx, |s| {
4053 s.select_display_ranges([
4054 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4055 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4056 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4057 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4058 ])
4059 });
4060 assert_eq!(
4061 editor.display_text(cx),
4062 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4063 );
4064
4065 editor.move_line_up(&MoveLineUp, window, cx);
4066 assert_eq!(
4067 editor.display_text(cx),
4068 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4069 );
4070 assert_eq!(
4071 editor.selections.display_ranges(cx),
4072 vec![
4073 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4074 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4075 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4076 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4077 ]
4078 );
4079 });
4080
4081 _ = editor.update(cx, |editor, window, cx| {
4082 editor.move_line_down(&MoveLineDown, window, cx);
4083 assert_eq!(
4084 editor.display_text(cx),
4085 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4086 );
4087 assert_eq!(
4088 editor.selections.display_ranges(cx),
4089 vec![
4090 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4091 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4092 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4093 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4094 ]
4095 );
4096 });
4097
4098 _ = editor.update(cx, |editor, window, cx| {
4099 editor.move_line_down(&MoveLineDown, window, cx);
4100 assert_eq!(
4101 editor.display_text(cx),
4102 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4103 );
4104 assert_eq!(
4105 editor.selections.display_ranges(cx),
4106 vec![
4107 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4108 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4109 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4110 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4111 ]
4112 );
4113 });
4114
4115 _ = editor.update(cx, |editor, window, cx| {
4116 editor.move_line_up(&MoveLineUp, window, cx);
4117 assert_eq!(
4118 editor.display_text(cx),
4119 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4120 );
4121 assert_eq!(
4122 editor.selections.display_ranges(cx),
4123 vec![
4124 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4125 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4126 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4127 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4128 ]
4129 );
4130 });
4131}
4132
4133#[gpui::test]
4134fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4135 init_test(cx, |_| {});
4136
4137 let editor = cx.add_window(|window, cx| {
4138 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4139 build_editor(buffer, window, cx)
4140 });
4141 _ = editor.update(cx, |editor, window, cx| {
4142 let snapshot = editor.buffer.read(cx).snapshot(cx);
4143 editor.insert_blocks(
4144 [BlockProperties {
4145 style: BlockStyle::Fixed,
4146 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4147 height: 1,
4148 render: Arc::new(|_| div().into_any()),
4149 priority: 0,
4150 }],
4151 Some(Autoscroll::fit()),
4152 cx,
4153 );
4154 editor.change_selections(None, window, cx, |s| {
4155 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4156 });
4157 editor.move_line_down(&MoveLineDown, window, cx);
4158 });
4159}
4160
4161#[gpui::test]
4162async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4163 init_test(cx, |_| {});
4164
4165 let mut cx = EditorTestContext::new(cx).await;
4166 cx.set_state(
4167 &"
4168 ˇzero
4169 one
4170 two
4171 three
4172 four
4173 five
4174 "
4175 .unindent(),
4176 );
4177
4178 // Create a four-line block that replaces three lines of text.
4179 cx.update_editor(|editor, window, cx| {
4180 let snapshot = editor.snapshot(window, cx);
4181 let snapshot = &snapshot.buffer_snapshot;
4182 let placement = BlockPlacement::Replace(
4183 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4184 );
4185 editor.insert_blocks(
4186 [BlockProperties {
4187 placement,
4188 height: 4,
4189 style: BlockStyle::Sticky,
4190 render: Arc::new(|_| gpui::div().into_any_element()),
4191 priority: 0,
4192 }],
4193 None,
4194 cx,
4195 );
4196 });
4197
4198 // Move down so that the cursor touches the block.
4199 cx.update_editor(|editor, window, cx| {
4200 editor.move_down(&Default::default(), window, cx);
4201 });
4202 cx.assert_editor_state(
4203 &"
4204 zero
4205 «one
4206 two
4207 threeˇ»
4208 four
4209 five
4210 "
4211 .unindent(),
4212 );
4213
4214 // Move down past the block.
4215 cx.update_editor(|editor, window, cx| {
4216 editor.move_down(&Default::default(), window, cx);
4217 });
4218 cx.assert_editor_state(
4219 &"
4220 zero
4221 one
4222 two
4223 three
4224 ˇfour
4225 five
4226 "
4227 .unindent(),
4228 );
4229}
4230
4231#[gpui::test]
4232fn test_transpose(cx: &mut TestAppContext) {
4233 init_test(cx, |_| {});
4234
4235 _ = cx.add_window(|window, cx| {
4236 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4237 editor.set_style(EditorStyle::default(), window, cx);
4238 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4239 editor.transpose(&Default::default(), window, cx);
4240 assert_eq!(editor.text(cx), "bac");
4241 assert_eq!(editor.selections.ranges(cx), [2..2]);
4242
4243 editor.transpose(&Default::default(), window, cx);
4244 assert_eq!(editor.text(cx), "bca");
4245 assert_eq!(editor.selections.ranges(cx), [3..3]);
4246
4247 editor.transpose(&Default::default(), window, cx);
4248 assert_eq!(editor.text(cx), "bac");
4249 assert_eq!(editor.selections.ranges(cx), [3..3]);
4250
4251 editor
4252 });
4253
4254 _ = cx.add_window(|window, cx| {
4255 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4256 editor.set_style(EditorStyle::default(), window, cx);
4257 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4258 editor.transpose(&Default::default(), window, cx);
4259 assert_eq!(editor.text(cx), "acb\nde");
4260 assert_eq!(editor.selections.ranges(cx), [3..3]);
4261
4262 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4263 editor.transpose(&Default::default(), window, cx);
4264 assert_eq!(editor.text(cx), "acbd\ne");
4265 assert_eq!(editor.selections.ranges(cx), [5..5]);
4266
4267 editor.transpose(&Default::default(), window, cx);
4268 assert_eq!(editor.text(cx), "acbde\n");
4269 assert_eq!(editor.selections.ranges(cx), [6..6]);
4270
4271 editor.transpose(&Default::default(), window, cx);
4272 assert_eq!(editor.text(cx), "acbd\ne");
4273 assert_eq!(editor.selections.ranges(cx), [6..6]);
4274
4275 editor
4276 });
4277
4278 _ = cx.add_window(|window, cx| {
4279 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4280 editor.set_style(EditorStyle::default(), window, cx);
4281 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4282 editor.transpose(&Default::default(), window, cx);
4283 assert_eq!(editor.text(cx), "bacd\ne");
4284 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4285
4286 editor.transpose(&Default::default(), window, cx);
4287 assert_eq!(editor.text(cx), "bcade\n");
4288 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4289
4290 editor.transpose(&Default::default(), window, cx);
4291 assert_eq!(editor.text(cx), "bcda\ne");
4292 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4293
4294 editor.transpose(&Default::default(), window, cx);
4295 assert_eq!(editor.text(cx), "bcade\n");
4296 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4297
4298 editor.transpose(&Default::default(), window, cx);
4299 assert_eq!(editor.text(cx), "bcaed\n");
4300 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4301
4302 editor
4303 });
4304
4305 _ = cx.add_window(|window, cx| {
4306 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4307 editor.set_style(EditorStyle::default(), window, cx);
4308 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4309 editor.transpose(&Default::default(), window, cx);
4310 assert_eq!(editor.text(cx), "🏀🍐✋");
4311 assert_eq!(editor.selections.ranges(cx), [8..8]);
4312
4313 editor.transpose(&Default::default(), window, cx);
4314 assert_eq!(editor.text(cx), "🏀✋🍐");
4315 assert_eq!(editor.selections.ranges(cx), [11..11]);
4316
4317 editor.transpose(&Default::default(), window, cx);
4318 assert_eq!(editor.text(cx), "🏀🍐✋");
4319 assert_eq!(editor.selections.ranges(cx), [11..11]);
4320
4321 editor
4322 });
4323}
4324
4325#[gpui::test]
4326async fn test_rewrap(cx: &mut TestAppContext) {
4327 init_test(cx, |settings| {
4328 settings.languages.extend([
4329 (
4330 "Markdown".into(),
4331 LanguageSettingsContent {
4332 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4333 ..Default::default()
4334 },
4335 ),
4336 (
4337 "Plain Text".into(),
4338 LanguageSettingsContent {
4339 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4340 ..Default::default()
4341 },
4342 ),
4343 ])
4344 });
4345
4346 let mut cx = EditorTestContext::new(cx).await;
4347
4348 let language_with_c_comments = Arc::new(Language::new(
4349 LanguageConfig {
4350 line_comments: vec!["// ".into()],
4351 ..LanguageConfig::default()
4352 },
4353 None,
4354 ));
4355 let language_with_pound_comments = Arc::new(Language::new(
4356 LanguageConfig {
4357 line_comments: vec!["# ".into()],
4358 ..LanguageConfig::default()
4359 },
4360 None,
4361 ));
4362 let markdown_language = Arc::new(Language::new(
4363 LanguageConfig {
4364 name: "Markdown".into(),
4365 ..LanguageConfig::default()
4366 },
4367 None,
4368 ));
4369 let language_with_doc_comments = Arc::new(Language::new(
4370 LanguageConfig {
4371 line_comments: vec!["// ".into(), "/// ".into()],
4372 ..LanguageConfig::default()
4373 },
4374 Some(tree_sitter_rust::LANGUAGE.into()),
4375 ));
4376
4377 let plaintext_language = Arc::new(Language::new(
4378 LanguageConfig {
4379 name: "Plain Text".into(),
4380 ..LanguageConfig::default()
4381 },
4382 None,
4383 ));
4384
4385 assert_rewrap(
4386 indoc! {"
4387 // ˇ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.
4388 "},
4389 indoc! {"
4390 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4391 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4392 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4393 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4394 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4395 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4396 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4397 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4398 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4399 // porttitor id. Aliquam id accumsan eros.
4400 "},
4401 language_with_c_comments.clone(),
4402 &mut cx,
4403 );
4404
4405 // Test that rewrapping works inside of a selection
4406 assert_rewrap(
4407 indoc! {"
4408 «// 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.ˇ»
4409 "},
4410 indoc! {"
4411 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4412 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4413 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4414 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4415 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4416 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4417 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4418 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4419 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4420 // porttitor id. Aliquam id accumsan eros.ˇ»
4421 "},
4422 language_with_c_comments.clone(),
4423 &mut cx,
4424 );
4425
4426 // Test that cursors that expand to the same region are collapsed.
4427 assert_rewrap(
4428 indoc! {"
4429 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4430 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4431 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4432 // ˇ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.
4433 "},
4434 indoc! {"
4435 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4436 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4437 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4438 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4439 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4440 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4441 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4442 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4443 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4444 // porttitor id. Aliquam id accumsan eros.
4445 "},
4446 language_with_c_comments.clone(),
4447 &mut cx,
4448 );
4449
4450 // Test that non-contiguous selections are treated separately.
4451 assert_rewrap(
4452 indoc! {"
4453 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4454 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4455 //
4456 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4457 // ˇ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.
4458 "},
4459 indoc! {"
4460 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4461 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4462 // auctor, eu lacinia sapien scelerisque.
4463 //
4464 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4465 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4466 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4467 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4468 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4469 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4470 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4471 "},
4472 language_with_c_comments.clone(),
4473 &mut cx,
4474 );
4475
4476 // Test that different comment prefixes are supported.
4477 assert_rewrap(
4478 indoc! {"
4479 # ˇ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.
4480 "},
4481 indoc! {"
4482 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4483 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4484 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4485 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4486 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4487 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4488 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4489 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4490 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4491 # accumsan eros.
4492 "},
4493 language_with_pound_comments.clone(),
4494 &mut cx,
4495 );
4496
4497 // Test that rewrapping is ignored outside of comments in most languages.
4498 assert_rewrap(
4499 indoc! {"
4500 /// Adds two numbers.
4501 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4502 fn add(a: u32, b: u32) -> u32 {
4503 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ˇ
4504 }
4505 "},
4506 indoc! {"
4507 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4508 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4509 fn add(a: u32, b: u32) -> u32 {
4510 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ˇ
4511 }
4512 "},
4513 language_with_doc_comments.clone(),
4514 &mut cx,
4515 );
4516
4517 // Test that rewrapping works in Markdown and Plain Text languages.
4518 assert_rewrap(
4519 indoc! {"
4520 # Hello
4521
4522 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.
4523 "},
4524 indoc! {"
4525 # Hello
4526
4527 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4528 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4529 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4530 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4531 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4532 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4533 Integer sit amet scelerisque nisi.
4534 "},
4535 markdown_language,
4536 &mut cx,
4537 );
4538
4539 assert_rewrap(
4540 indoc! {"
4541 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.
4542 "},
4543 indoc! {"
4544 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4545 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4546 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4547 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4548 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4549 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4550 Integer sit amet scelerisque nisi.
4551 "},
4552 plaintext_language,
4553 &mut cx,
4554 );
4555
4556 // Test rewrapping unaligned comments in a selection.
4557 assert_rewrap(
4558 indoc! {"
4559 fn foo() {
4560 if true {
4561 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4562 // Praesent semper egestas tellus id dignissim.ˇ»
4563 do_something();
4564 } else {
4565 //
4566 }
4567 }
4568 "},
4569 indoc! {"
4570 fn foo() {
4571 if true {
4572 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4573 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4574 // egestas tellus id dignissim.ˇ»
4575 do_something();
4576 } else {
4577 //
4578 }
4579 }
4580 "},
4581 language_with_doc_comments.clone(),
4582 &mut cx,
4583 );
4584
4585 assert_rewrap(
4586 indoc! {"
4587 fn foo() {
4588 if true {
4589 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4590 // Praesent semper egestas tellus id dignissim.»
4591 do_something();
4592 } else {
4593 //
4594 }
4595
4596 }
4597 "},
4598 indoc! {"
4599 fn foo() {
4600 if true {
4601 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4602 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4603 // egestas tellus id dignissim.»
4604 do_something();
4605 } else {
4606 //
4607 }
4608
4609 }
4610 "},
4611 language_with_doc_comments.clone(),
4612 &mut cx,
4613 );
4614
4615 #[track_caller]
4616 fn assert_rewrap(
4617 unwrapped_text: &str,
4618 wrapped_text: &str,
4619 language: Arc<Language>,
4620 cx: &mut EditorTestContext,
4621 ) {
4622 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4623 cx.set_state(unwrapped_text);
4624 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4625 cx.assert_editor_state(wrapped_text);
4626 }
4627}
4628
4629#[gpui::test]
4630async fn test_clipboard(cx: &mut TestAppContext) {
4631 init_test(cx, |_| {});
4632
4633 let mut cx = EditorTestContext::new(cx).await;
4634
4635 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4636 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4637 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4638
4639 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4640 cx.set_state("two ˇfour ˇsix ˇ");
4641 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4642 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4643
4644 // Paste again but with only two cursors. Since the number of cursors doesn't
4645 // match the number of slices in the clipboard, the entire clipboard text
4646 // is pasted at each cursor.
4647 cx.set_state("ˇtwo one✅ four three six five ˇ");
4648 cx.update_editor(|e, window, cx| {
4649 e.handle_input("( ", window, cx);
4650 e.paste(&Paste, window, cx);
4651 e.handle_input(") ", window, cx);
4652 });
4653 cx.assert_editor_state(
4654 &([
4655 "( one✅ ",
4656 "three ",
4657 "five ) ˇtwo one✅ four three six five ( one✅ ",
4658 "three ",
4659 "five ) ˇ",
4660 ]
4661 .join("\n")),
4662 );
4663
4664 // Cut with three selections, one of which is full-line.
4665 cx.set_state(indoc! {"
4666 1«2ˇ»3
4667 4ˇ567
4668 «8ˇ»9"});
4669 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4670 cx.assert_editor_state(indoc! {"
4671 1ˇ3
4672 ˇ9"});
4673
4674 // Paste with three selections, noticing how the copied selection that was full-line
4675 // gets inserted before the second cursor.
4676 cx.set_state(indoc! {"
4677 1ˇ3
4678 9ˇ
4679 «oˇ»ne"});
4680 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4681 cx.assert_editor_state(indoc! {"
4682 12ˇ3
4683 4567
4684 9ˇ
4685 8ˇne"});
4686
4687 // Copy with a single cursor only, which writes the whole line into the clipboard.
4688 cx.set_state(indoc! {"
4689 The quick brown
4690 fox juˇmps over
4691 the lazy dog"});
4692 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4693 assert_eq!(
4694 cx.read_from_clipboard()
4695 .and_then(|item| item.text().as_deref().map(str::to_string)),
4696 Some("fox jumps over\n".to_string())
4697 );
4698
4699 // Paste with three selections, noticing how the copied full-line selection is inserted
4700 // before the empty selections but replaces the selection that is non-empty.
4701 cx.set_state(indoc! {"
4702 Tˇhe quick brown
4703 «foˇ»x jumps over
4704 tˇhe lazy dog"});
4705 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4706 cx.assert_editor_state(indoc! {"
4707 fox jumps over
4708 Tˇhe quick brown
4709 fox jumps over
4710 ˇx jumps over
4711 fox jumps over
4712 tˇhe lazy dog"});
4713}
4714
4715#[gpui::test]
4716async fn test_paste_multiline(cx: &mut TestAppContext) {
4717 init_test(cx, |_| {});
4718
4719 let mut cx = EditorTestContext::new(cx).await;
4720 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4721
4722 // Cut an indented block, without the leading whitespace.
4723 cx.set_state(indoc! {"
4724 const a: B = (
4725 c(),
4726 «d(
4727 e,
4728 f
4729 )ˇ»
4730 );
4731 "});
4732 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4733 cx.assert_editor_state(indoc! {"
4734 const a: B = (
4735 c(),
4736 ˇ
4737 );
4738 "});
4739
4740 // Paste it at the same position.
4741 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4742 cx.assert_editor_state(indoc! {"
4743 const a: B = (
4744 c(),
4745 d(
4746 e,
4747 f
4748 )ˇ
4749 );
4750 "});
4751
4752 // Paste it at a line with a lower indent level.
4753 cx.set_state(indoc! {"
4754 ˇ
4755 const a: B = (
4756 c(),
4757 );
4758 "});
4759 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4760 cx.assert_editor_state(indoc! {"
4761 d(
4762 e,
4763 f
4764 )ˇ
4765 const a: B = (
4766 c(),
4767 );
4768 "});
4769
4770 // Cut an indented block, with the leading whitespace.
4771 cx.set_state(indoc! {"
4772 const a: B = (
4773 c(),
4774 « d(
4775 e,
4776 f
4777 )
4778 ˇ»);
4779 "});
4780 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4781 cx.assert_editor_state(indoc! {"
4782 const a: B = (
4783 c(),
4784 ˇ);
4785 "});
4786
4787 // Paste it at the same position.
4788 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4789 cx.assert_editor_state(indoc! {"
4790 const a: B = (
4791 c(),
4792 d(
4793 e,
4794 f
4795 )
4796 ˇ);
4797 "});
4798
4799 // Paste it at a line with a higher indent level.
4800 cx.set_state(indoc! {"
4801 const a: B = (
4802 c(),
4803 d(
4804 e,
4805 fˇ
4806 )
4807 );
4808 "});
4809 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4810 cx.assert_editor_state(indoc! {"
4811 const a: B = (
4812 c(),
4813 d(
4814 e,
4815 f d(
4816 e,
4817 f
4818 )
4819 ˇ
4820 )
4821 );
4822 "});
4823}
4824
4825#[gpui::test]
4826async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4827 init_test(cx, |_| {});
4828
4829 cx.write_to_clipboard(ClipboardItem::new_string(
4830 " d(\n e\n );\n".into(),
4831 ));
4832
4833 let mut cx = EditorTestContext::new(cx).await;
4834 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4835
4836 cx.set_state(indoc! {"
4837 fn a() {
4838 b();
4839 if c() {
4840 ˇ
4841 }
4842 }
4843 "});
4844
4845 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4846 cx.assert_editor_state(indoc! {"
4847 fn a() {
4848 b();
4849 if c() {
4850 d(
4851 e
4852 );
4853 ˇ
4854 }
4855 }
4856 "});
4857
4858 cx.set_state(indoc! {"
4859 fn a() {
4860 b();
4861 ˇ
4862 }
4863 "});
4864
4865 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4866 cx.assert_editor_state(indoc! {"
4867 fn a() {
4868 b();
4869 d(
4870 e
4871 );
4872 ˇ
4873 }
4874 "});
4875}
4876
4877#[gpui::test]
4878fn test_select_all(cx: &mut TestAppContext) {
4879 init_test(cx, |_| {});
4880
4881 let editor = cx.add_window(|window, cx| {
4882 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4883 build_editor(buffer, window, cx)
4884 });
4885 _ = editor.update(cx, |editor, window, cx| {
4886 editor.select_all(&SelectAll, window, cx);
4887 assert_eq!(
4888 editor.selections.display_ranges(cx),
4889 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4890 );
4891 });
4892}
4893
4894#[gpui::test]
4895fn test_select_line(cx: &mut TestAppContext) {
4896 init_test(cx, |_| {});
4897
4898 let editor = cx.add_window(|window, cx| {
4899 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4900 build_editor(buffer, window, cx)
4901 });
4902 _ = editor.update(cx, |editor, window, cx| {
4903 editor.change_selections(None, window, cx, |s| {
4904 s.select_display_ranges([
4905 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4906 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4907 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4908 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4909 ])
4910 });
4911 editor.select_line(&SelectLine, window, cx);
4912 assert_eq!(
4913 editor.selections.display_ranges(cx),
4914 vec![
4915 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4916 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4917 ]
4918 );
4919 });
4920
4921 _ = editor.update(cx, |editor, window, cx| {
4922 editor.select_line(&SelectLine, window, cx);
4923 assert_eq!(
4924 editor.selections.display_ranges(cx),
4925 vec![
4926 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4927 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4928 ]
4929 );
4930 });
4931
4932 _ = editor.update(cx, |editor, window, cx| {
4933 editor.select_line(&SelectLine, window, cx);
4934 assert_eq!(
4935 editor.selections.display_ranges(cx),
4936 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4937 );
4938 });
4939}
4940
4941#[gpui::test]
4942async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4943 init_test(cx, |_| {});
4944 let mut cx = EditorTestContext::new(cx).await;
4945
4946 #[track_caller]
4947 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
4948 cx.set_state(initial_state);
4949 cx.update_editor(|e, window, cx| {
4950 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
4951 });
4952 cx.assert_editor_state(expected_state);
4953 }
4954
4955 // Selection starts and ends at the middle of lines, left-to-right
4956 test(
4957 &mut cx,
4958 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
4959 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4960 );
4961 // Same thing, right-to-left
4962 test(
4963 &mut cx,
4964 "aa\nb«b\ncc\ndd\neˇ»e\nff",
4965 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4966 );
4967
4968 // Whole buffer, left-to-right, last line *doesn't* end with newline
4969 test(
4970 &mut cx,
4971 "«ˇaa\nbb\ncc\ndd\nee\nff»",
4972 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4973 );
4974 // Same thing, right-to-left
4975 test(
4976 &mut cx,
4977 "«aa\nbb\ncc\ndd\nee\nffˇ»",
4978 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4979 );
4980
4981 // Whole buffer, left-to-right, last line ends with newline
4982 test(
4983 &mut cx,
4984 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
4985 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4986 );
4987 // Same thing, right-to-left
4988 test(
4989 &mut cx,
4990 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
4991 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4992 );
4993
4994 // Starts at the end of a line, ends at the start of another
4995 test(
4996 &mut cx,
4997 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
4998 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
4999 );
5000}
5001
5002#[gpui::test]
5003async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5004 init_test(cx, |_| {});
5005
5006 let editor = cx.add_window(|window, cx| {
5007 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5008 build_editor(buffer, window, cx)
5009 });
5010
5011 // setup
5012 _ = editor.update(cx, |editor, window, cx| {
5013 editor.fold_creases(
5014 vec![
5015 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5016 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5017 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5018 ],
5019 true,
5020 window,
5021 cx,
5022 );
5023 assert_eq!(
5024 editor.display_text(cx),
5025 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5026 );
5027 });
5028
5029 _ = editor.update(cx, |editor, window, cx| {
5030 editor.change_selections(None, window, cx, |s| {
5031 s.select_display_ranges([
5032 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5033 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5034 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5035 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5036 ])
5037 });
5038 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5039 assert_eq!(
5040 editor.display_text(cx),
5041 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5042 );
5043 });
5044 EditorTestContext::for_editor(editor, cx)
5045 .await
5046 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5047
5048 _ = editor.update(cx, |editor, window, cx| {
5049 editor.change_selections(None, window, cx, |s| {
5050 s.select_display_ranges([
5051 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5052 ])
5053 });
5054 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5055 assert_eq!(
5056 editor.display_text(cx),
5057 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5058 );
5059 assert_eq!(
5060 editor.selections.display_ranges(cx),
5061 [
5062 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5063 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5064 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5065 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5066 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5067 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5068 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5069 ]
5070 );
5071 });
5072 EditorTestContext::for_editor(editor, cx)
5073 .await
5074 .assert_editor_state(
5075 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5076 );
5077}
5078
5079#[gpui::test]
5080async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5081 init_test(cx, |_| {});
5082
5083 let mut cx = EditorTestContext::new(cx).await;
5084
5085 cx.set_state(indoc!(
5086 r#"abc
5087 defˇghi
5088
5089 jk
5090 nlmo
5091 "#
5092 ));
5093
5094 cx.update_editor(|editor, window, cx| {
5095 editor.add_selection_above(&Default::default(), window, cx);
5096 });
5097
5098 cx.assert_editor_state(indoc!(
5099 r#"abcˇ
5100 defˇghi
5101
5102 jk
5103 nlmo
5104 "#
5105 ));
5106
5107 cx.update_editor(|editor, window, cx| {
5108 editor.add_selection_above(&Default::default(), window, cx);
5109 });
5110
5111 cx.assert_editor_state(indoc!(
5112 r#"abcˇ
5113 defˇghi
5114
5115 jk
5116 nlmo
5117 "#
5118 ));
5119
5120 cx.update_editor(|editor, window, cx| {
5121 editor.add_selection_below(&Default::default(), window, cx);
5122 });
5123
5124 cx.assert_editor_state(indoc!(
5125 r#"abc
5126 defˇghi
5127
5128 jk
5129 nlmo
5130 "#
5131 ));
5132
5133 cx.update_editor(|editor, window, cx| {
5134 editor.undo_selection(&Default::default(), window, cx);
5135 });
5136
5137 cx.assert_editor_state(indoc!(
5138 r#"abcˇ
5139 defˇghi
5140
5141 jk
5142 nlmo
5143 "#
5144 ));
5145
5146 cx.update_editor(|editor, window, cx| {
5147 editor.redo_selection(&Default::default(), window, cx);
5148 });
5149
5150 cx.assert_editor_state(indoc!(
5151 r#"abc
5152 defˇghi
5153
5154 jk
5155 nlmo
5156 "#
5157 ));
5158
5159 cx.update_editor(|editor, window, cx| {
5160 editor.add_selection_below(&Default::default(), window, cx);
5161 });
5162
5163 cx.assert_editor_state(indoc!(
5164 r#"abc
5165 defˇghi
5166
5167 jk
5168 nlmˇo
5169 "#
5170 ));
5171
5172 cx.update_editor(|editor, window, cx| {
5173 editor.add_selection_below(&Default::default(), window, cx);
5174 });
5175
5176 cx.assert_editor_state(indoc!(
5177 r#"abc
5178 defˇghi
5179
5180 jk
5181 nlmˇo
5182 "#
5183 ));
5184
5185 // change selections
5186 cx.set_state(indoc!(
5187 r#"abc
5188 def«ˇg»hi
5189
5190 jk
5191 nlmo
5192 "#
5193 ));
5194
5195 cx.update_editor(|editor, window, cx| {
5196 editor.add_selection_below(&Default::default(), window, cx);
5197 });
5198
5199 cx.assert_editor_state(indoc!(
5200 r#"abc
5201 def«ˇg»hi
5202
5203 jk
5204 nlm«ˇo»
5205 "#
5206 ));
5207
5208 cx.update_editor(|editor, window, cx| {
5209 editor.add_selection_below(&Default::default(), window, cx);
5210 });
5211
5212 cx.assert_editor_state(indoc!(
5213 r#"abc
5214 def«ˇg»hi
5215
5216 jk
5217 nlm«ˇo»
5218 "#
5219 ));
5220
5221 cx.update_editor(|editor, window, cx| {
5222 editor.add_selection_above(&Default::default(), window, cx);
5223 });
5224
5225 cx.assert_editor_state(indoc!(
5226 r#"abc
5227 def«ˇg»hi
5228
5229 jk
5230 nlmo
5231 "#
5232 ));
5233
5234 cx.update_editor(|editor, window, cx| {
5235 editor.add_selection_above(&Default::default(), window, cx);
5236 });
5237
5238 cx.assert_editor_state(indoc!(
5239 r#"abc
5240 def«ˇg»hi
5241
5242 jk
5243 nlmo
5244 "#
5245 ));
5246
5247 // Change selections again
5248 cx.set_state(indoc!(
5249 r#"a«bc
5250 defgˇ»hi
5251
5252 jk
5253 nlmo
5254 "#
5255 ));
5256
5257 cx.update_editor(|editor, window, cx| {
5258 editor.add_selection_below(&Default::default(), window, cx);
5259 });
5260
5261 cx.assert_editor_state(indoc!(
5262 r#"a«bcˇ»
5263 d«efgˇ»hi
5264
5265 j«kˇ»
5266 nlmo
5267 "#
5268 ));
5269
5270 cx.update_editor(|editor, window, cx| {
5271 editor.add_selection_below(&Default::default(), window, cx);
5272 });
5273 cx.assert_editor_state(indoc!(
5274 r#"a«bcˇ»
5275 d«efgˇ»hi
5276
5277 j«kˇ»
5278 n«lmoˇ»
5279 "#
5280 ));
5281 cx.update_editor(|editor, window, cx| {
5282 editor.add_selection_above(&Default::default(), window, cx);
5283 });
5284
5285 cx.assert_editor_state(indoc!(
5286 r#"a«bcˇ»
5287 d«efgˇ»hi
5288
5289 j«kˇ»
5290 nlmo
5291 "#
5292 ));
5293
5294 // Change selections again
5295 cx.set_state(indoc!(
5296 r#"abc
5297 d«ˇefghi
5298
5299 jk
5300 nlm»o
5301 "#
5302 ));
5303
5304 cx.update_editor(|editor, window, cx| {
5305 editor.add_selection_above(&Default::default(), window, cx);
5306 });
5307
5308 cx.assert_editor_state(indoc!(
5309 r#"a«ˇbc»
5310 d«ˇef»ghi
5311
5312 j«ˇk»
5313 n«ˇlm»o
5314 "#
5315 ));
5316
5317 cx.update_editor(|editor, window, cx| {
5318 editor.add_selection_below(&Default::default(), window, cx);
5319 });
5320
5321 cx.assert_editor_state(indoc!(
5322 r#"abc
5323 d«ˇef»ghi
5324
5325 j«ˇk»
5326 n«ˇlm»o
5327 "#
5328 ));
5329}
5330
5331#[gpui::test]
5332async fn test_select_next(cx: &mut TestAppContext) {
5333 init_test(cx, |_| {});
5334
5335 let mut cx = EditorTestContext::new(cx).await;
5336 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5337
5338 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5339 .unwrap();
5340 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5341
5342 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5343 .unwrap();
5344 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5345
5346 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5347 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5348
5349 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5350 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5351
5352 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5353 .unwrap();
5354 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5355
5356 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5357 .unwrap();
5358 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5359}
5360
5361#[gpui::test]
5362async fn test_select_all_matches(cx: &mut TestAppContext) {
5363 init_test(cx, |_| {});
5364
5365 let mut cx = EditorTestContext::new(cx).await;
5366
5367 // Test caret-only selections
5368 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5369 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5370 .unwrap();
5371 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5372
5373 // Test left-to-right selections
5374 cx.set_state("abc\n«abcˇ»\nabc");
5375 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5376 .unwrap();
5377 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5378
5379 // Test right-to-left selections
5380 cx.set_state("abc\n«ˇabc»\nabc");
5381 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5382 .unwrap();
5383 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5384
5385 // Test selecting whitespace with caret selection
5386 cx.set_state("abc\nˇ abc\nabc");
5387 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5388 .unwrap();
5389 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5390
5391 // Test selecting whitespace with left-to-right selection
5392 cx.set_state("abc\n«ˇ »abc\nabc");
5393 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5394 .unwrap();
5395 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5396
5397 // Test no matches with right-to-left selection
5398 cx.set_state("abc\n« ˇ»abc\nabc");
5399 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5400 .unwrap();
5401 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5402}
5403
5404#[gpui::test]
5405async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5406 init_test(cx, |_| {});
5407
5408 let mut cx = EditorTestContext::new(cx).await;
5409 cx.set_state(
5410 r#"let foo = 2;
5411lˇet foo = 2;
5412let fooˇ = 2;
5413let foo = 2;
5414let foo = ˇ2;"#,
5415 );
5416
5417 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5418 .unwrap();
5419 cx.assert_editor_state(
5420 r#"let foo = 2;
5421«letˇ» foo = 2;
5422let «fooˇ» = 2;
5423let foo = 2;
5424let foo = «2ˇ»;"#,
5425 );
5426
5427 // noop for multiple selections with different contents
5428 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5429 .unwrap();
5430 cx.assert_editor_state(
5431 r#"let foo = 2;
5432«letˇ» foo = 2;
5433let «fooˇ» = 2;
5434let foo = 2;
5435let foo = «2ˇ»;"#,
5436 );
5437}
5438
5439#[gpui::test]
5440async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5441 init_test(cx, |_| {});
5442
5443 let mut cx =
5444 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5445
5446 cx.assert_editor_state(indoc! {"
5447 ˇbbb
5448 ccc
5449
5450 bbb
5451 ccc
5452 "});
5453 cx.dispatch_action(SelectPrevious::default());
5454 cx.assert_editor_state(indoc! {"
5455 «bbbˇ»
5456 ccc
5457
5458 bbb
5459 ccc
5460 "});
5461 cx.dispatch_action(SelectPrevious::default());
5462 cx.assert_editor_state(indoc! {"
5463 «bbbˇ»
5464 ccc
5465
5466 «bbbˇ»
5467 ccc
5468 "});
5469}
5470
5471#[gpui::test]
5472async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5473 init_test(cx, |_| {});
5474
5475 let mut cx = EditorTestContext::new(cx).await;
5476 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5477
5478 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5479 .unwrap();
5480 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5481
5482 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5483 .unwrap();
5484 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5485
5486 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5487 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5488
5489 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5490 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5491
5492 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5493 .unwrap();
5494 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5495
5496 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5497 .unwrap();
5498 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5499
5500 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5501 .unwrap();
5502 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5503}
5504
5505#[gpui::test]
5506async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5507 init_test(cx, |_| {});
5508
5509 let mut cx = EditorTestContext::new(cx).await;
5510 cx.set_state("aˇ");
5511
5512 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5513 .unwrap();
5514 cx.assert_editor_state("«aˇ»");
5515 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5516 .unwrap();
5517 cx.assert_editor_state("«aˇ»");
5518}
5519
5520#[gpui::test]
5521async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5522 init_test(cx, |_| {});
5523
5524 let mut cx = EditorTestContext::new(cx).await;
5525 cx.set_state(
5526 r#"let foo = 2;
5527lˇet foo = 2;
5528let fooˇ = 2;
5529let foo = 2;
5530let foo = ˇ2;"#,
5531 );
5532
5533 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5534 .unwrap();
5535 cx.assert_editor_state(
5536 r#"let foo = 2;
5537«letˇ» foo = 2;
5538let «fooˇ» = 2;
5539let foo = 2;
5540let foo = «2ˇ»;"#,
5541 );
5542
5543 // noop for multiple selections with different contents
5544 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5545 .unwrap();
5546 cx.assert_editor_state(
5547 r#"let foo = 2;
5548«letˇ» foo = 2;
5549let «fooˇ» = 2;
5550let foo = 2;
5551let foo = «2ˇ»;"#,
5552 );
5553}
5554
5555#[gpui::test]
5556async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5557 init_test(cx, |_| {});
5558
5559 let mut cx = EditorTestContext::new(cx).await;
5560 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5561
5562 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5563 .unwrap();
5564 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5565
5566 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5567 .unwrap();
5568 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5569
5570 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5571 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5572
5573 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5574 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5575
5576 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5577 .unwrap();
5578 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5579
5580 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5581 .unwrap();
5582 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5583}
5584
5585#[gpui::test]
5586async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5587 init_test(cx, |_| {});
5588
5589 let language = Arc::new(Language::new(
5590 LanguageConfig::default(),
5591 Some(tree_sitter_rust::LANGUAGE.into()),
5592 ));
5593
5594 let text = r#"
5595 use mod1::mod2::{mod3, mod4};
5596
5597 fn fn_1(param1: bool, param2: &str) {
5598 let var1 = "text";
5599 }
5600 "#
5601 .unindent();
5602
5603 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5604 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5605 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5606
5607 editor
5608 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5609 .await;
5610
5611 editor.update_in(cx, |editor, window, cx| {
5612 editor.change_selections(None, window, cx, |s| {
5613 s.select_display_ranges([
5614 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5615 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5616 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5617 ]);
5618 });
5619 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5620 });
5621 editor.update(cx, |editor, cx| {
5622 assert_text_with_selections(
5623 editor,
5624 indoc! {r#"
5625 use mod1::mod2::{mod3, «mod4ˇ»};
5626
5627 fn fn_1«ˇ(param1: bool, param2: &str)» {
5628 let var1 = "«textˇ»";
5629 }
5630 "#},
5631 cx,
5632 );
5633 });
5634
5635 editor.update_in(cx, |editor, window, cx| {
5636 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5637 });
5638 editor.update(cx, |editor, cx| {
5639 assert_text_with_selections(
5640 editor,
5641 indoc! {r#"
5642 use mod1::mod2::«{mod3, mod4}ˇ»;
5643
5644 «ˇfn fn_1(param1: bool, param2: &str) {
5645 let var1 = "text";
5646 }»
5647 "#},
5648 cx,
5649 );
5650 });
5651
5652 editor.update_in(cx, |editor, window, cx| {
5653 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5654 });
5655 assert_eq!(
5656 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5657 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5658 );
5659
5660 // Trying to expand the selected syntax node one more time has no effect.
5661 editor.update_in(cx, |editor, window, cx| {
5662 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5663 });
5664 assert_eq!(
5665 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5666 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5667 );
5668
5669 editor.update_in(cx, |editor, window, cx| {
5670 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5671 });
5672 editor.update(cx, |editor, cx| {
5673 assert_text_with_selections(
5674 editor,
5675 indoc! {r#"
5676 use mod1::mod2::«{mod3, mod4}ˇ»;
5677
5678 «ˇfn fn_1(param1: bool, param2: &str) {
5679 let var1 = "text";
5680 }»
5681 "#},
5682 cx,
5683 );
5684 });
5685
5686 editor.update_in(cx, |editor, window, cx| {
5687 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5688 });
5689 editor.update(cx, |editor, cx| {
5690 assert_text_with_selections(
5691 editor,
5692 indoc! {r#"
5693 use mod1::mod2::{mod3, «mod4ˇ»};
5694
5695 fn fn_1«ˇ(param1: bool, param2: &str)» {
5696 let var1 = "«textˇ»";
5697 }
5698 "#},
5699 cx,
5700 );
5701 });
5702
5703 editor.update_in(cx, |editor, window, cx| {
5704 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5705 });
5706 editor.update(cx, |editor, cx| {
5707 assert_text_with_selections(
5708 editor,
5709 indoc! {r#"
5710 use mod1::mod2::{mod3, mo«ˇ»d4};
5711
5712 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5713 let var1 = "te«ˇ»xt";
5714 }
5715 "#},
5716 cx,
5717 );
5718 });
5719
5720 // Trying to shrink the selected syntax node one more time has no effect.
5721 editor.update_in(cx, |editor, window, cx| {
5722 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5723 });
5724 editor.update_in(cx, |editor, _, cx| {
5725 assert_text_with_selections(
5726 editor,
5727 indoc! {r#"
5728 use mod1::mod2::{mod3, mo«ˇ»d4};
5729
5730 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5731 let var1 = "te«ˇ»xt";
5732 }
5733 "#},
5734 cx,
5735 );
5736 });
5737
5738 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5739 // a fold.
5740 editor.update_in(cx, |editor, window, cx| {
5741 editor.fold_creases(
5742 vec![
5743 Crease::simple(
5744 Point::new(0, 21)..Point::new(0, 24),
5745 FoldPlaceholder::test(),
5746 ),
5747 Crease::simple(
5748 Point::new(3, 20)..Point::new(3, 22),
5749 FoldPlaceholder::test(),
5750 ),
5751 ],
5752 true,
5753 window,
5754 cx,
5755 );
5756 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5757 });
5758 editor.update(cx, |editor, cx| {
5759 assert_text_with_selections(
5760 editor,
5761 indoc! {r#"
5762 use mod1::mod2::«{mod3, mod4}ˇ»;
5763
5764 fn fn_1«ˇ(param1: bool, param2: &str)» {
5765 «let var1 = "text";ˇ»
5766 }
5767 "#},
5768 cx,
5769 );
5770 });
5771}
5772
5773#[gpui::test]
5774async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5775 init_test(cx, |_| {});
5776
5777 let base_text = r#"
5778 impl A {
5779 // this is an uncommitted comment
5780
5781 fn b() {
5782 c();
5783 }
5784
5785 // this is another uncommitted comment
5786
5787 fn d() {
5788 // e
5789 // f
5790 }
5791 }
5792
5793 fn g() {
5794 // h
5795 }
5796 "#
5797 .unindent();
5798
5799 let text = r#"
5800 ˇimpl A {
5801
5802 fn b() {
5803 c();
5804 }
5805
5806 fn d() {
5807 // e
5808 // f
5809 }
5810 }
5811
5812 fn g() {
5813 // h
5814 }
5815 "#
5816 .unindent();
5817
5818 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5819 cx.set_state(&text);
5820 cx.set_head_text(&base_text);
5821 cx.update_editor(|editor, window, cx| {
5822 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5823 });
5824
5825 cx.assert_state_with_diff(
5826 "
5827 ˇimpl A {
5828 - // this is an uncommitted comment
5829
5830 fn b() {
5831 c();
5832 }
5833
5834 - // this is another uncommitted comment
5835 -
5836 fn d() {
5837 // e
5838 // f
5839 }
5840 }
5841
5842 fn g() {
5843 // h
5844 }
5845 "
5846 .unindent(),
5847 );
5848
5849 let expected_display_text = "
5850 impl A {
5851 // this is an uncommitted comment
5852
5853 fn b() {
5854 ⋯
5855 }
5856
5857 // this is another uncommitted comment
5858
5859 fn d() {
5860 ⋯
5861 }
5862 }
5863
5864 fn g() {
5865 ⋯
5866 }
5867 "
5868 .unindent();
5869
5870 cx.update_editor(|editor, window, cx| {
5871 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5872 assert_eq!(editor.display_text(cx), expected_display_text);
5873 });
5874}
5875
5876#[gpui::test]
5877async fn test_autoindent(cx: &mut TestAppContext) {
5878 init_test(cx, |_| {});
5879
5880 let language = Arc::new(
5881 Language::new(
5882 LanguageConfig {
5883 brackets: BracketPairConfig {
5884 pairs: vec![
5885 BracketPair {
5886 start: "{".to_string(),
5887 end: "}".to_string(),
5888 close: false,
5889 surround: false,
5890 newline: true,
5891 },
5892 BracketPair {
5893 start: "(".to_string(),
5894 end: ")".to_string(),
5895 close: false,
5896 surround: false,
5897 newline: true,
5898 },
5899 ],
5900 ..Default::default()
5901 },
5902 ..Default::default()
5903 },
5904 Some(tree_sitter_rust::LANGUAGE.into()),
5905 )
5906 .with_indents_query(
5907 r#"
5908 (_ "(" ")" @end) @indent
5909 (_ "{" "}" @end) @indent
5910 "#,
5911 )
5912 .unwrap(),
5913 );
5914
5915 let text = "fn a() {}";
5916
5917 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5918 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5919 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5920 editor
5921 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5922 .await;
5923
5924 editor.update_in(cx, |editor, window, cx| {
5925 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5926 editor.newline(&Newline, window, cx);
5927 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5928 assert_eq!(
5929 editor.selections.ranges(cx),
5930 &[
5931 Point::new(1, 4)..Point::new(1, 4),
5932 Point::new(3, 4)..Point::new(3, 4),
5933 Point::new(5, 0)..Point::new(5, 0)
5934 ]
5935 );
5936 });
5937}
5938
5939#[gpui::test]
5940async fn test_autoindent_selections(cx: &mut TestAppContext) {
5941 init_test(cx, |_| {});
5942
5943 {
5944 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5945 cx.set_state(indoc! {"
5946 impl A {
5947
5948 fn b() {}
5949
5950 «fn c() {
5951
5952 }ˇ»
5953 }
5954 "});
5955
5956 cx.update_editor(|editor, window, cx| {
5957 editor.autoindent(&Default::default(), window, cx);
5958 });
5959
5960 cx.assert_editor_state(indoc! {"
5961 impl A {
5962
5963 fn b() {}
5964
5965 «fn c() {
5966
5967 }ˇ»
5968 }
5969 "});
5970 }
5971
5972 {
5973 let mut cx = EditorTestContext::new_multibuffer(
5974 cx,
5975 [indoc! { "
5976 impl A {
5977 «
5978 // a
5979 fn b(){}
5980 »
5981 «
5982 }
5983 fn c(){}
5984 »
5985 "}],
5986 );
5987
5988 let buffer = cx.update_editor(|editor, _, cx| {
5989 let buffer = editor.buffer().update(cx, |buffer, _| {
5990 buffer.all_buffers().iter().next().unwrap().clone()
5991 });
5992 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5993 buffer
5994 });
5995
5996 cx.run_until_parked();
5997 cx.update_editor(|editor, window, cx| {
5998 editor.select_all(&Default::default(), window, cx);
5999 editor.autoindent(&Default::default(), window, cx)
6000 });
6001 cx.run_until_parked();
6002
6003 cx.update(|_, cx| {
6004 pretty_assertions::assert_eq!(
6005 buffer.read(cx).text(),
6006 indoc! { "
6007 impl A {
6008
6009 // a
6010 fn b(){}
6011
6012
6013 }
6014 fn c(){}
6015
6016 " }
6017 )
6018 });
6019 }
6020}
6021
6022#[gpui::test]
6023async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6024 init_test(cx, |_| {});
6025
6026 let mut cx = EditorTestContext::new(cx).await;
6027
6028 let language = Arc::new(Language::new(
6029 LanguageConfig {
6030 brackets: BracketPairConfig {
6031 pairs: vec![
6032 BracketPair {
6033 start: "{".to_string(),
6034 end: "}".to_string(),
6035 close: true,
6036 surround: true,
6037 newline: true,
6038 },
6039 BracketPair {
6040 start: "(".to_string(),
6041 end: ")".to_string(),
6042 close: true,
6043 surround: true,
6044 newline: true,
6045 },
6046 BracketPair {
6047 start: "/*".to_string(),
6048 end: " */".to_string(),
6049 close: true,
6050 surround: true,
6051 newline: true,
6052 },
6053 BracketPair {
6054 start: "[".to_string(),
6055 end: "]".to_string(),
6056 close: false,
6057 surround: false,
6058 newline: true,
6059 },
6060 BracketPair {
6061 start: "\"".to_string(),
6062 end: "\"".to_string(),
6063 close: true,
6064 surround: true,
6065 newline: false,
6066 },
6067 BracketPair {
6068 start: "<".to_string(),
6069 end: ">".to_string(),
6070 close: false,
6071 surround: true,
6072 newline: true,
6073 },
6074 ],
6075 ..Default::default()
6076 },
6077 autoclose_before: "})]".to_string(),
6078 ..Default::default()
6079 },
6080 Some(tree_sitter_rust::LANGUAGE.into()),
6081 ));
6082
6083 cx.language_registry().add(language.clone());
6084 cx.update_buffer(|buffer, cx| {
6085 buffer.set_language(Some(language), cx);
6086 });
6087
6088 cx.set_state(
6089 &r#"
6090 🏀ˇ
6091 εˇ
6092 ❤️ˇ
6093 "#
6094 .unindent(),
6095 );
6096
6097 // autoclose multiple nested brackets at multiple cursors
6098 cx.update_editor(|editor, window, cx| {
6099 editor.handle_input("{", window, cx);
6100 editor.handle_input("{", window, cx);
6101 editor.handle_input("{", window, cx);
6102 });
6103 cx.assert_editor_state(
6104 &"
6105 🏀{{{ˇ}}}
6106 ε{{{ˇ}}}
6107 ❤️{{{ˇ}}}
6108 "
6109 .unindent(),
6110 );
6111
6112 // insert a different closing bracket
6113 cx.update_editor(|editor, window, cx| {
6114 editor.handle_input(")", window, cx);
6115 });
6116 cx.assert_editor_state(
6117 &"
6118 🏀{{{)ˇ}}}
6119 ε{{{)ˇ}}}
6120 ❤️{{{)ˇ}}}
6121 "
6122 .unindent(),
6123 );
6124
6125 // skip over the auto-closed brackets when typing a closing bracket
6126 cx.update_editor(|editor, window, cx| {
6127 editor.move_right(&MoveRight, window, cx);
6128 editor.handle_input("}", window, cx);
6129 editor.handle_input("}", window, cx);
6130 editor.handle_input("}", window, cx);
6131 });
6132 cx.assert_editor_state(
6133 &"
6134 🏀{{{)}}}}ˇ
6135 ε{{{)}}}}ˇ
6136 ❤️{{{)}}}}ˇ
6137 "
6138 .unindent(),
6139 );
6140
6141 // autoclose multi-character pairs
6142 cx.set_state(
6143 &"
6144 ˇ
6145 ˇ
6146 "
6147 .unindent(),
6148 );
6149 cx.update_editor(|editor, window, cx| {
6150 editor.handle_input("/", window, cx);
6151 editor.handle_input("*", window, cx);
6152 });
6153 cx.assert_editor_state(
6154 &"
6155 /*ˇ */
6156 /*ˇ */
6157 "
6158 .unindent(),
6159 );
6160
6161 // one cursor autocloses a multi-character pair, one cursor
6162 // does not autoclose.
6163 cx.set_state(
6164 &"
6165 /ˇ
6166 ˇ
6167 "
6168 .unindent(),
6169 );
6170 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6171 cx.assert_editor_state(
6172 &"
6173 /*ˇ */
6174 *ˇ
6175 "
6176 .unindent(),
6177 );
6178
6179 // Don't autoclose if the next character isn't whitespace and isn't
6180 // listed in the language's "autoclose_before" section.
6181 cx.set_state("ˇa b");
6182 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6183 cx.assert_editor_state("{ˇa b");
6184
6185 // Don't autoclose if `close` is false for the bracket pair
6186 cx.set_state("ˇ");
6187 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6188 cx.assert_editor_state("[ˇ");
6189
6190 // Surround with brackets if text is selected
6191 cx.set_state("«aˇ» b");
6192 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6193 cx.assert_editor_state("{«aˇ»} b");
6194
6195 // Autclose pair where the start and end characters are the same
6196 cx.set_state("aˇ");
6197 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6198 cx.assert_editor_state("a\"ˇ\"");
6199 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6200 cx.assert_editor_state("a\"\"ˇ");
6201
6202 // Don't autoclose pair if autoclose is disabled
6203 cx.set_state("ˇ");
6204 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6205 cx.assert_editor_state("<ˇ");
6206
6207 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6208 cx.set_state("«aˇ» b");
6209 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6210 cx.assert_editor_state("<«aˇ»> b");
6211}
6212
6213#[gpui::test]
6214async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6215 init_test(cx, |settings| {
6216 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6217 });
6218
6219 let mut cx = EditorTestContext::new(cx).await;
6220
6221 let language = Arc::new(Language::new(
6222 LanguageConfig {
6223 brackets: BracketPairConfig {
6224 pairs: vec![
6225 BracketPair {
6226 start: "{".to_string(),
6227 end: "}".to_string(),
6228 close: true,
6229 surround: true,
6230 newline: true,
6231 },
6232 BracketPair {
6233 start: "(".to_string(),
6234 end: ")".to_string(),
6235 close: true,
6236 surround: true,
6237 newline: true,
6238 },
6239 BracketPair {
6240 start: "[".to_string(),
6241 end: "]".to_string(),
6242 close: false,
6243 surround: false,
6244 newline: true,
6245 },
6246 ],
6247 ..Default::default()
6248 },
6249 autoclose_before: "})]".to_string(),
6250 ..Default::default()
6251 },
6252 Some(tree_sitter_rust::LANGUAGE.into()),
6253 ));
6254
6255 cx.language_registry().add(language.clone());
6256 cx.update_buffer(|buffer, cx| {
6257 buffer.set_language(Some(language), cx);
6258 });
6259
6260 cx.set_state(
6261 &"
6262 ˇ
6263 ˇ
6264 ˇ
6265 "
6266 .unindent(),
6267 );
6268
6269 // ensure only matching closing brackets are skipped over
6270 cx.update_editor(|editor, window, cx| {
6271 editor.handle_input("}", window, cx);
6272 editor.move_left(&MoveLeft, window, cx);
6273 editor.handle_input(")", window, cx);
6274 editor.move_left(&MoveLeft, window, cx);
6275 });
6276 cx.assert_editor_state(
6277 &"
6278 ˇ)}
6279 ˇ)}
6280 ˇ)}
6281 "
6282 .unindent(),
6283 );
6284
6285 // skip-over closing brackets at multiple cursors
6286 cx.update_editor(|editor, window, cx| {
6287 editor.handle_input(")", window, cx);
6288 editor.handle_input("}", window, cx);
6289 });
6290 cx.assert_editor_state(
6291 &"
6292 )}ˇ
6293 )}ˇ
6294 )}ˇ
6295 "
6296 .unindent(),
6297 );
6298
6299 // ignore non-close brackets
6300 cx.update_editor(|editor, window, cx| {
6301 editor.handle_input("]", window, cx);
6302 editor.move_left(&MoveLeft, window, cx);
6303 editor.handle_input("]", window, cx);
6304 });
6305 cx.assert_editor_state(
6306 &"
6307 )}]ˇ]
6308 )}]ˇ]
6309 )}]ˇ]
6310 "
6311 .unindent(),
6312 );
6313}
6314
6315#[gpui::test]
6316async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6317 init_test(cx, |_| {});
6318
6319 let mut cx = EditorTestContext::new(cx).await;
6320
6321 let html_language = Arc::new(
6322 Language::new(
6323 LanguageConfig {
6324 name: "HTML".into(),
6325 brackets: BracketPairConfig {
6326 pairs: vec![
6327 BracketPair {
6328 start: "<".into(),
6329 end: ">".into(),
6330 close: true,
6331 ..Default::default()
6332 },
6333 BracketPair {
6334 start: "{".into(),
6335 end: "}".into(),
6336 close: true,
6337 ..Default::default()
6338 },
6339 BracketPair {
6340 start: "(".into(),
6341 end: ")".into(),
6342 close: true,
6343 ..Default::default()
6344 },
6345 ],
6346 ..Default::default()
6347 },
6348 autoclose_before: "})]>".into(),
6349 ..Default::default()
6350 },
6351 Some(tree_sitter_html::LANGUAGE.into()),
6352 )
6353 .with_injection_query(
6354 r#"
6355 (script_element
6356 (raw_text) @injection.content
6357 (#set! injection.language "javascript"))
6358 "#,
6359 )
6360 .unwrap(),
6361 );
6362
6363 let javascript_language = Arc::new(Language::new(
6364 LanguageConfig {
6365 name: "JavaScript".into(),
6366 brackets: BracketPairConfig {
6367 pairs: vec![
6368 BracketPair {
6369 start: "/*".into(),
6370 end: " */".into(),
6371 close: true,
6372 ..Default::default()
6373 },
6374 BracketPair {
6375 start: "{".into(),
6376 end: "}".into(),
6377 close: true,
6378 ..Default::default()
6379 },
6380 BracketPair {
6381 start: "(".into(),
6382 end: ")".into(),
6383 close: true,
6384 ..Default::default()
6385 },
6386 ],
6387 ..Default::default()
6388 },
6389 autoclose_before: "})]>".into(),
6390 ..Default::default()
6391 },
6392 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6393 ));
6394
6395 cx.language_registry().add(html_language.clone());
6396 cx.language_registry().add(javascript_language.clone());
6397
6398 cx.update_buffer(|buffer, cx| {
6399 buffer.set_language(Some(html_language), cx);
6400 });
6401
6402 cx.set_state(
6403 &r#"
6404 <body>ˇ
6405 <script>
6406 var x = 1;ˇ
6407 </script>
6408 </body>ˇ
6409 "#
6410 .unindent(),
6411 );
6412
6413 // Precondition: different languages are active at different locations.
6414 cx.update_editor(|editor, window, cx| {
6415 let snapshot = editor.snapshot(window, cx);
6416 let cursors = editor.selections.ranges::<usize>(cx);
6417 let languages = cursors
6418 .iter()
6419 .map(|c| snapshot.language_at(c.start).unwrap().name())
6420 .collect::<Vec<_>>();
6421 assert_eq!(
6422 languages,
6423 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6424 );
6425 });
6426
6427 // Angle brackets autoclose in HTML, but not JavaScript.
6428 cx.update_editor(|editor, window, cx| {
6429 editor.handle_input("<", window, cx);
6430 editor.handle_input("a", window, cx);
6431 });
6432 cx.assert_editor_state(
6433 &r#"
6434 <body><aˇ>
6435 <script>
6436 var x = 1;<aˇ
6437 </script>
6438 </body><aˇ>
6439 "#
6440 .unindent(),
6441 );
6442
6443 // Curly braces and parens autoclose in both HTML and JavaScript.
6444 cx.update_editor(|editor, window, cx| {
6445 editor.handle_input(" b=", window, cx);
6446 editor.handle_input("{", window, cx);
6447 editor.handle_input("c", window, cx);
6448 editor.handle_input("(", window, cx);
6449 });
6450 cx.assert_editor_state(
6451 &r#"
6452 <body><a b={c(ˇ)}>
6453 <script>
6454 var x = 1;<a b={c(ˇ)}
6455 </script>
6456 </body><a b={c(ˇ)}>
6457 "#
6458 .unindent(),
6459 );
6460
6461 // Brackets that were already autoclosed are skipped.
6462 cx.update_editor(|editor, window, cx| {
6463 editor.handle_input(")", window, cx);
6464 editor.handle_input("d", window, cx);
6465 editor.handle_input("}", window, cx);
6466 });
6467 cx.assert_editor_state(
6468 &r#"
6469 <body><a b={c()d}ˇ>
6470 <script>
6471 var x = 1;<a b={c()d}ˇ
6472 </script>
6473 </body><a b={c()d}ˇ>
6474 "#
6475 .unindent(),
6476 );
6477 cx.update_editor(|editor, window, cx| {
6478 editor.handle_input(">", window, cx);
6479 });
6480 cx.assert_editor_state(
6481 &r#"
6482 <body><a b={c()d}>ˇ
6483 <script>
6484 var x = 1;<a b={c()d}>ˇ
6485 </script>
6486 </body><a b={c()d}>ˇ
6487 "#
6488 .unindent(),
6489 );
6490
6491 // Reset
6492 cx.set_state(
6493 &r#"
6494 <body>ˇ
6495 <script>
6496 var x = 1;ˇ
6497 </script>
6498 </body>ˇ
6499 "#
6500 .unindent(),
6501 );
6502
6503 cx.update_editor(|editor, window, cx| {
6504 editor.handle_input("<", window, cx);
6505 });
6506 cx.assert_editor_state(
6507 &r#"
6508 <body><ˇ>
6509 <script>
6510 var x = 1;<ˇ
6511 </script>
6512 </body><ˇ>
6513 "#
6514 .unindent(),
6515 );
6516
6517 // When backspacing, the closing angle brackets are removed.
6518 cx.update_editor(|editor, window, cx| {
6519 editor.backspace(&Backspace, window, cx);
6520 });
6521 cx.assert_editor_state(
6522 &r#"
6523 <body>ˇ
6524 <script>
6525 var x = 1;ˇ
6526 </script>
6527 </body>ˇ
6528 "#
6529 .unindent(),
6530 );
6531
6532 // Block comments autoclose in JavaScript, but not HTML.
6533 cx.update_editor(|editor, window, cx| {
6534 editor.handle_input("/", window, cx);
6535 editor.handle_input("*", window, cx);
6536 });
6537 cx.assert_editor_state(
6538 &r#"
6539 <body>/*ˇ
6540 <script>
6541 var x = 1;/*ˇ */
6542 </script>
6543 </body>/*ˇ
6544 "#
6545 .unindent(),
6546 );
6547}
6548
6549#[gpui::test]
6550async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6551 init_test(cx, |_| {});
6552
6553 let mut cx = EditorTestContext::new(cx).await;
6554
6555 let rust_language = Arc::new(
6556 Language::new(
6557 LanguageConfig {
6558 name: "Rust".into(),
6559 brackets: serde_json::from_value(json!([
6560 { "start": "{", "end": "}", "close": true, "newline": true },
6561 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6562 ]))
6563 .unwrap(),
6564 autoclose_before: "})]>".into(),
6565 ..Default::default()
6566 },
6567 Some(tree_sitter_rust::LANGUAGE.into()),
6568 )
6569 .with_override_query("(string_literal) @string")
6570 .unwrap(),
6571 );
6572
6573 cx.language_registry().add(rust_language.clone());
6574 cx.update_buffer(|buffer, cx| {
6575 buffer.set_language(Some(rust_language), cx);
6576 });
6577
6578 cx.set_state(
6579 &r#"
6580 let x = ˇ
6581 "#
6582 .unindent(),
6583 );
6584
6585 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6586 cx.update_editor(|editor, window, cx| {
6587 editor.handle_input("\"", window, cx);
6588 });
6589 cx.assert_editor_state(
6590 &r#"
6591 let x = "ˇ"
6592 "#
6593 .unindent(),
6594 );
6595
6596 // Inserting another quotation mark. The cursor moves across the existing
6597 // automatically-inserted quotation mark.
6598 cx.update_editor(|editor, window, cx| {
6599 editor.handle_input("\"", window, cx);
6600 });
6601 cx.assert_editor_state(
6602 &r#"
6603 let x = ""ˇ
6604 "#
6605 .unindent(),
6606 );
6607
6608 // Reset
6609 cx.set_state(
6610 &r#"
6611 let x = ˇ
6612 "#
6613 .unindent(),
6614 );
6615
6616 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6617 cx.update_editor(|editor, window, cx| {
6618 editor.handle_input("\"", window, cx);
6619 editor.handle_input(" ", window, cx);
6620 editor.move_left(&Default::default(), window, cx);
6621 editor.handle_input("\\", window, cx);
6622 editor.handle_input("\"", window, cx);
6623 });
6624 cx.assert_editor_state(
6625 &r#"
6626 let x = "\"ˇ "
6627 "#
6628 .unindent(),
6629 );
6630
6631 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6632 // mark. Nothing is inserted.
6633 cx.update_editor(|editor, window, cx| {
6634 editor.move_right(&Default::default(), window, cx);
6635 editor.handle_input("\"", window, cx);
6636 });
6637 cx.assert_editor_state(
6638 &r#"
6639 let x = "\" "ˇ
6640 "#
6641 .unindent(),
6642 );
6643}
6644
6645#[gpui::test]
6646async fn test_surround_with_pair(cx: &mut TestAppContext) {
6647 init_test(cx, |_| {});
6648
6649 let language = Arc::new(Language::new(
6650 LanguageConfig {
6651 brackets: BracketPairConfig {
6652 pairs: vec![
6653 BracketPair {
6654 start: "{".to_string(),
6655 end: "}".to_string(),
6656 close: true,
6657 surround: true,
6658 newline: true,
6659 },
6660 BracketPair {
6661 start: "/* ".to_string(),
6662 end: "*/".to_string(),
6663 close: true,
6664 surround: true,
6665 ..Default::default()
6666 },
6667 ],
6668 ..Default::default()
6669 },
6670 ..Default::default()
6671 },
6672 Some(tree_sitter_rust::LANGUAGE.into()),
6673 ));
6674
6675 let text = r#"
6676 a
6677 b
6678 c
6679 "#
6680 .unindent();
6681
6682 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6684 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6685 editor
6686 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6687 .await;
6688
6689 editor.update_in(cx, |editor, window, cx| {
6690 editor.change_selections(None, window, cx, |s| {
6691 s.select_display_ranges([
6692 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6693 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6694 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6695 ])
6696 });
6697
6698 editor.handle_input("{", window, cx);
6699 editor.handle_input("{", window, cx);
6700 editor.handle_input("{", window, cx);
6701 assert_eq!(
6702 editor.text(cx),
6703 "
6704 {{{a}}}
6705 {{{b}}}
6706 {{{c}}}
6707 "
6708 .unindent()
6709 );
6710 assert_eq!(
6711 editor.selections.display_ranges(cx),
6712 [
6713 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6714 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6715 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6716 ]
6717 );
6718
6719 editor.undo(&Undo, window, cx);
6720 editor.undo(&Undo, window, cx);
6721 editor.undo(&Undo, window, cx);
6722 assert_eq!(
6723 editor.text(cx),
6724 "
6725 a
6726 b
6727 c
6728 "
6729 .unindent()
6730 );
6731 assert_eq!(
6732 editor.selections.display_ranges(cx),
6733 [
6734 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6735 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6736 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6737 ]
6738 );
6739
6740 // Ensure inserting the first character of a multi-byte bracket pair
6741 // doesn't surround the selections with the bracket.
6742 editor.handle_input("/", window, cx);
6743 assert_eq!(
6744 editor.text(cx),
6745 "
6746 /
6747 /
6748 /
6749 "
6750 .unindent()
6751 );
6752 assert_eq!(
6753 editor.selections.display_ranges(cx),
6754 [
6755 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6756 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6757 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6758 ]
6759 );
6760
6761 editor.undo(&Undo, window, cx);
6762 assert_eq!(
6763 editor.text(cx),
6764 "
6765 a
6766 b
6767 c
6768 "
6769 .unindent()
6770 );
6771 assert_eq!(
6772 editor.selections.display_ranges(cx),
6773 [
6774 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6775 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6776 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6777 ]
6778 );
6779
6780 // Ensure inserting the last character of a multi-byte bracket pair
6781 // doesn't surround the selections with the bracket.
6782 editor.handle_input("*", window, cx);
6783 assert_eq!(
6784 editor.text(cx),
6785 "
6786 *
6787 *
6788 *
6789 "
6790 .unindent()
6791 );
6792 assert_eq!(
6793 editor.selections.display_ranges(cx),
6794 [
6795 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6796 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6797 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6798 ]
6799 );
6800 });
6801}
6802
6803#[gpui::test]
6804async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6805 init_test(cx, |_| {});
6806
6807 let language = Arc::new(Language::new(
6808 LanguageConfig {
6809 brackets: BracketPairConfig {
6810 pairs: vec![BracketPair {
6811 start: "{".to_string(),
6812 end: "}".to_string(),
6813 close: true,
6814 surround: true,
6815 newline: true,
6816 }],
6817 ..Default::default()
6818 },
6819 autoclose_before: "}".to_string(),
6820 ..Default::default()
6821 },
6822 Some(tree_sitter_rust::LANGUAGE.into()),
6823 ));
6824
6825 let text = r#"
6826 a
6827 b
6828 c
6829 "#
6830 .unindent();
6831
6832 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6833 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6834 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6835 editor
6836 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6837 .await;
6838
6839 editor.update_in(cx, |editor, window, cx| {
6840 editor.change_selections(None, window, cx, |s| {
6841 s.select_ranges([
6842 Point::new(0, 1)..Point::new(0, 1),
6843 Point::new(1, 1)..Point::new(1, 1),
6844 Point::new(2, 1)..Point::new(2, 1),
6845 ])
6846 });
6847
6848 editor.handle_input("{", window, cx);
6849 editor.handle_input("{", window, cx);
6850 editor.handle_input("_", window, cx);
6851 assert_eq!(
6852 editor.text(cx),
6853 "
6854 a{{_}}
6855 b{{_}}
6856 c{{_}}
6857 "
6858 .unindent()
6859 );
6860 assert_eq!(
6861 editor.selections.ranges::<Point>(cx),
6862 [
6863 Point::new(0, 4)..Point::new(0, 4),
6864 Point::new(1, 4)..Point::new(1, 4),
6865 Point::new(2, 4)..Point::new(2, 4)
6866 ]
6867 );
6868
6869 editor.backspace(&Default::default(), window, cx);
6870 editor.backspace(&Default::default(), window, cx);
6871 assert_eq!(
6872 editor.text(cx),
6873 "
6874 a{}
6875 b{}
6876 c{}
6877 "
6878 .unindent()
6879 );
6880 assert_eq!(
6881 editor.selections.ranges::<Point>(cx),
6882 [
6883 Point::new(0, 2)..Point::new(0, 2),
6884 Point::new(1, 2)..Point::new(1, 2),
6885 Point::new(2, 2)..Point::new(2, 2)
6886 ]
6887 );
6888
6889 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6890 assert_eq!(
6891 editor.text(cx),
6892 "
6893 a
6894 b
6895 c
6896 "
6897 .unindent()
6898 );
6899 assert_eq!(
6900 editor.selections.ranges::<Point>(cx),
6901 [
6902 Point::new(0, 1)..Point::new(0, 1),
6903 Point::new(1, 1)..Point::new(1, 1),
6904 Point::new(2, 1)..Point::new(2, 1)
6905 ]
6906 );
6907 });
6908}
6909
6910#[gpui::test]
6911async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
6912 init_test(cx, |settings| {
6913 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6914 });
6915
6916 let mut cx = EditorTestContext::new(cx).await;
6917
6918 let language = Arc::new(Language::new(
6919 LanguageConfig {
6920 brackets: BracketPairConfig {
6921 pairs: vec![
6922 BracketPair {
6923 start: "{".to_string(),
6924 end: "}".to_string(),
6925 close: true,
6926 surround: true,
6927 newline: true,
6928 },
6929 BracketPair {
6930 start: "(".to_string(),
6931 end: ")".to_string(),
6932 close: true,
6933 surround: true,
6934 newline: true,
6935 },
6936 BracketPair {
6937 start: "[".to_string(),
6938 end: "]".to_string(),
6939 close: false,
6940 surround: true,
6941 newline: true,
6942 },
6943 ],
6944 ..Default::default()
6945 },
6946 autoclose_before: "})]".to_string(),
6947 ..Default::default()
6948 },
6949 Some(tree_sitter_rust::LANGUAGE.into()),
6950 ));
6951
6952 cx.language_registry().add(language.clone());
6953 cx.update_buffer(|buffer, cx| {
6954 buffer.set_language(Some(language), cx);
6955 });
6956
6957 cx.set_state(
6958 &"
6959 {(ˇ)}
6960 [[ˇ]]
6961 {(ˇ)}
6962 "
6963 .unindent(),
6964 );
6965
6966 cx.update_editor(|editor, window, cx| {
6967 editor.backspace(&Default::default(), window, cx);
6968 editor.backspace(&Default::default(), window, cx);
6969 });
6970
6971 cx.assert_editor_state(
6972 &"
6973 ˇ
6974 ˇ]]
6975 ˇ
6976 "
6977 .unindent(),
6978 );
6979
6980 cx.update_editor(|editor, window, cx| {
6981 editor.handle_input("{", window, cx);
6982 editor.handle_input("{", window, cx);
6983 editor.move_right(&MoveRight, window, cx);
6984 editor.move_right(&MoveRight, window, cx);
6985 editor.move_left(&MoveLeft, window, cx);
6986 editor.move_left(&MoveLeft, window, cx);
6987 editor.backspace(&Default::default(), window, cx);
6988 });
6989
6990 cx.assert_editor_state(
6991 &"
6992 {ˇ}
6993 {ˇ}]]
6994 {ˇ}
6995 "
6996 .unindent(),
6997 );
6998
6999 cx.update_editor(|editor, window, cx| {
7000 editor.backspace(&Default::default(), window, cx);
7001 });
7002
7003 cx.assert_editor_state(
7004 &"
7005 ˇ
7006 ˇ]]
7007 ˇ
7008 "
7009 .unindent(),
7010 );
7011}
7012
7013#[gpui::test]
7014async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7015 init_test(cx, |_| {});
7016
7017 let language = Arc::new(Language::new(
7018 LanguageConfig::default(),
7019 Some(tree_sitter_rust::LANGUAGE.into()),
7020 ));
7021
7022 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7023 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7024 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7025 editor
7026 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7027 .await;
7028
7029 editor.update_in(cx, |editor, window, cx| {
7030 editor.set_auto_replace_emoji_shortcode(true);
7031
7032 editor.handle_input("Hello ", window, cx);
7033 editor.handle_input(":wave", window, cx);
7034 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7035
7036 editor.handle_input(":", window, cx);
7037 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7038
7039 editor.handle_input(" :smile", window, cx);
7040 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7041
7042 editor.handle_input(":", window, cx);
7043 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7044
7045 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7046 editor.handle_input(":wave", window, cx);
7047 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7048
7049 editor.handle_input(":", window, cx);
7050 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7051
7052 editor.handle_input(":1", window, cx);
7053 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7054
7055 editor.handle_input(":", window, cx);
7056 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7057
7058 // Ensure shortcode does not get replaced when it is part of a word
7059 editor.handle_input(" Test:wave", window, cx);
7060 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7061
7062 editor.handle_input(":", window, cx);
7063 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7064
7065 editor.set_auto_replace_emoji_shortcode(false);
7066
7067 // Ensure shortcode does not get replaced when auto replace is off
7068 editor.handle_input(" :wave", window, cx);
7069 assert_eq!(
7070 editor.text(cx),
7071 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7072 );
7073
7074 editor.handle_input(":", window, cx);
7075 assert_eq!(
7076 editor.text(cx),
7077 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7078 );
7079 });
7080}
7081
7082#[gpui::test]
7083async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7084 init_test(cx, |_| {});
7085
7086 let (text, insertion_ranges) = marked_text_ranges(
7087 indoc! {"
7088 ˇ
7089 "},
7090 false,
7091 );
7092
7093 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7094 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7095
7096 _ = editor.update_in(cx, |editor, window, cx| {
7097 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7098
7099 editor
7100 .insert_snippet(&insertion_ranges, snippet, window, cx)
7101 .unwrap();
7102
7103 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7104 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7105 assert_eq!(editor.text(cx), expected_text);
7106 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7107 }
7108
7109 assert(
7110 editor,
7111 cx,
7112 indoc! {"
7113 type «» =•
7114 "},
7115 );
7116
7117 assert!(editor.context_menu_visible(), "There should be a matches");
7118 });
7119}
7120
7121#[gpui::test]
7122async fn test_snippets(cx: &mut TestAppContext) {
7123 init_test(cx, |_| {});
7124
7125 let (text, insertion_ranges) = marked_text_ranges(
7126 indoc! {"
7127 a.ˇ b
7128 a.ˇ b
7129 a.ˇ b
7130 "},
7131 false,
7132 );
7133
7134 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7135 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7136
7137 editor.update_in(cx, |editor, window, cx| {
7138 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7139
7140 editor
7141 .insert_snippet(&insertion_ranges, snippet, window, cx)
7142 .unwrap();
7143
7144 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7145 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7146 assert_eq!(editor.text(cx), expected_text);
7147 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7148 }
7149
7150 assert(
7151 editor,
7152 cx,
7153 indoc! {"
7154 a.f(«one», two, «three») b
7155 a.f(«one», two, «three») b
7156 a.f(«one», two, «three») b
7157 "},
7158 );
7159
7160 // Can't move earlier than the first tab stop
7161 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7162 assert(
7163 editor,
7164 cx,
7165 indoc! {"
7166 a.f(«one», two, «three») b
7167 a.f(«one», two, «three») b
7168 a.f(«one», two, «three») b
7169 "},
7170 );
7171
7172 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7173 assert(
7174 editor,
7175 cx,
7176 indoc! {"
7177 a.f(one, «two», three) b
7178 a.f(one, «two», three) b
7179 a.f(one, «two», three) b
7180 "},
7181 );
7182
7183 editor.move_to_prev_snippet_tabstop(window, cx);
7184 assert(
7185 editor,
7186 cx,
7187 indoc! {"
7188 a.f(«one», two, «three») b
7189 a.f(«one», two, «three») b
7190 a.f(«one», two, «three») b
7191 "},
7192 );
7193
7194 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7195 assert(
7196 editor,
7197 cx,
7198 indoc! {"
7199 a.f(one, «two», three) b
7200 a.f(one, «two», three) b
7201 a.f(one, «two», three) b
7202 "},
7203 );
7204 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7205 assert(
7206 editor,
7207 cx,
7208 indoc! {"
7209 a.f(one, two, three)ˇ b
7210 a.f(one, two, three)ˇ b
7211 a.f(one, two, three)ˇ b
7212 "},
7213 );
7214
7215 // As soon as the last tab stop is reached, snippet state is gone
7216 editor.move_to_prev_snippet_tabstop(window, cx);
7217 assert(
7218 editor,
7219 cx,
7220 indoc! {"
7221 a.f(one, two, three)ˇ b
7222 a.f(one, two, three)ˇ b
7223 a.f(one, two, three)ˇ b
7224 "},
7225 );
7226 });
7227}
7228
7229#[gpui::test]
7230async fn test_document_format_during_save(cx: &mut TestAppContext) {
7231 init_test(cx, |_| {});
7232
7233 let fs = FakeFs::new(cx.executor());
7234 fs.insert_file(path!("/file.rs"), Default::default()).await;
7235
7236 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7237
7238 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7239 language_registry.add(rust_lang());
7240 let mut fake_servers = language_registry.register_fake_lsp(
7241 "Rust",
7242 FakeLspAdapter {
7243 capabilities: lsp::ServerCapabilities {
7244 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7245 ..Default::default()
7246 },
7247 ..Default::default()
7248 },
7249 );
7250
7251 let buffer = project
7252 .update(cx, |project, cx| {
7253 project.open_local_buffer(path!("/file.rs"), cx)
7254 })
7255 .await
7256 .unwrap();
7257
7258 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7259 let (editor, cx) = cx.add_window_view(|window, cx| {
7260 build_editor_with_project(project.clone(), buffer, window, cx)
7261 });
7262 editor.update_in(cx, |editor, window, cx| {
7263 editor.set_text("one\ntwo\nthree\n", window, cx)
7264 });
7265 assert!(cx.read(|cx| editor.is_dirty(cx)));
7266
7267 cx.executor().start_waiting();
7268 let fake_server = fake_servers.next().await.unwrap();
7269
7270 let save = editor
7271 .update_in(cx, |editor, window, cx| {
7272 editor.save(true, project.clone(), window, cx)
7273 })
7274 .unwrap();
7275 fake_server
7276 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7277 assert_eq!(
7278 params.text_document.uri,
7279 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7280 );
7281 assert_eq!(params.options.tab_size, 4);
7282 Ok(Some(vec![lsp::TextEdit::new(
7283 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7284 ", ".to_string(),
7285 )]))
7286 })
7287 .next()
7288 .await;
7289 cx.executor().start_waiting();
7290 save.await;
7291
7292 assert_eq!(
7293 editor.update(cx, |editor, cx| editor.text(cx)),
7294 "one, two\nthree\n"
7295 );
7296 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7297
7298 editor.update_in(cx, |editor, window, cx| {
7299 editor.set_text("one\ntwo\nthree\n", window, cx)
7300 });
7301 assert!(cx.read(|cx| editor.is_dirty(cx)));
7302
7303 // Ensure we can still save even if formatting hangs.
7304 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7305 assert_eq!(
7306 params.text_document.uri,
7307 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7308 );
7309 futures::future::pending::<()>().await;
7310 unreachable!()
7311 });
7312 let save = editor
7313 .update_in(cx, |editor, window, cx| {
7314 editor.save(true, project.clone(), window, cx)
7315 })
7316 .unwrap();
7317 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7318 cx.executor().start_waiting();
7319 save.await;
7320 assert_eq!(
7321 editor.update(cx, |editor, cx| editor.text(cx)),
7322 "one\ntwo\nthree\n"
7323 );
7324 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7325
7326 // For non-dirty buffer, no formatting request should be sent
7327 let save = editor
7328 .update_in(cx, |editor, window, cx| {
7329 editor.save(true, project.clone(), window, cx)
7330 })
7331 .unwrap();
7332 let _pending_format_request = fake_server
7333 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7334 panic!("Should not be invoked on non-dirty buffer");
7335 })
7336 .next();
7337 cx.executor().start_waiting();
7338 save.await;
7339
7340 // Set rust language override and assert overridden tabsize is sent to language server
7341 update_test_language_settings(cx, |settings| {
7342 settings.languages.insert(
7343 "Rust".into(),
7344 LanguageSettingsContent {
7345 tab_size: NonZeroU32::new(8),
7346 ..Default::default()
7347 },
7348 );
7349 });
7350
7351 editor.update_in(cx, |editor, window, cx| {
7352 editor.set_text("somehting_new\n", window, cx)
7353 });
7354 assert!(cx.read(|cx| editor.is_dirty(cx)));
7355 let save = editor
7356 .update_in(cx, |editor, window, cx| {
7357 editor.save(true, project.clone(), window, cx)
7358 })
7359 .unwrap();
7360 fake_server
7361 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7362 assert_eq!(
7363 params.text_document.uri,
7364 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7365 );
7366 assert_eq!(params.options.tab_size, 8);
7367 Ok(Some(vec![]))
7368 })
7369 .next()
7370 .await;
7371 cx.executor().start_waiting();
7372 save.await;
7373}
7374
7375#[gpui::test]
7376async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7377 init_test(cx, |_| {});
7378
7379 let cols = 4;
7380 let rows = 10;
7381 let sample_text_1 = sample_text(rows, cols, 'a');
7382 assert_eq!(
7383 sample_text_1,
7384 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7385 );
7386 let sample_text_2 = sample_text(rows, cols, 'l');
7387 assert_eq!(
7388 sample_text_2,
7389 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7390 );
7391 let sample_text_3 = sample_text(rows, cols, 'v');
7392 assert_eq!(
7393 sample_text_3,
7394 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7395 );
7396
7397 let fs = FakeFs::new(cx.executor());
7398 fs.insert_tree(
7399 path!("/a"),
7400 json!({
7401 "main.rs": sample_text_1,
7402 "other.rs": sample_text_2,
7403 "lib.rs": sample_text_3,
7404 }),
7405 )
7406 .await;
7407
7408 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7409 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7410 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7411
7412 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7413 language_registry.add(rust_lang());
7414 let mut fake_servers = language_registry.register_fake_lsp(
7415 "Rust",
7416 FakeLspAdapter {
7417 capabilities: lsp::ServerCapabilities {
7418 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7419 ..Default::default()
7420 },
7421 ..Default::default()
7422 },
7423 );
7424
7425 let worktree = project.update(cx, |project, cx| {
7426 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7427 assert_eq!(worktrees.len(), 1);
7428 worktrees.pop().unwrap()
7429 });
7430 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7431
7432 let buffer_1 = project
7433 .update(cx, |project, cx| {
7434 project.open_buffer((worktree_id, "main.rs"), cx)
7435 })
7436 .await
7437 .unwrap();
7438 let buffer_2 = project
7439 .update(cx, |project, cx| {
7440 project.open_buffer((worktree_id, "other.rs"), cx)
7441 })
7442 .await
7443 .unwrap();
7444 let buffer_3 = project
7445 .update(cx, |project, cx| {
7446 project.open_buffer((worktree_id, "lib.rs"), cx)
7447 })
7448 .await
7449 .unwrap();
7450
7451 let multi_buffer = cx.new(|cx| {
7452 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7453 multi_buffer.push_excerpts(
7454 buffer_1.clone(),
7455 [
7456 ExcerptRange {
7457 context: Point::new(0, 0)..Point::new(3, 0),
7458 primary: None,
7459 },
7460 ExcerptRange {
7461 context: Point::new(5, 0)..Point::new(7, 0),
7462 primary: None,
7463 },
7464 ExcerptRange {
7465 context: Point::new(9, 0)..Point::new(10, 4),
7466 primary: None,
7467 },
7468 ],
7469 cx,
7470 );
7471 multi_buffer.push_excerpts(
7472 buffer_2.clone(),
7473 [
7474 ExcerptRange {
7475 context: Point::new(0, 0)..Point::new(3, 0),
7476 primary: None,
7477 },
7478 ExcerptRange {
7479 context: Point::new(5, 0)..Point::new(7, 0),
7480 primary: None,
7481 },
7482 ExcerptRange {
7483 context: Point::new(9, 0)..Point::new(10, 4),
7484 primary: None,
7485 },
7486 ],
7487 cx,
7488 );
7489 multi_buffer.push_excerpts(
7490 buffer_3.clone(),
7491 [
7492 ExcerptRange {
7493 context: Point::new(0, 0)..Point::new(3, 0),
7494 primary: None,
7495 },
7496 ExcerptRange {
7497 context: Point::new(5, 0)..Point::new(7, 0),
7498 primary: None,
7499 },
7500 ExcerptRange {
7501 context: Point::new(9, 0)..Point::new(10, 4),
7502 primary: None,
7503 },
7504 ],
7505 cx,
7506 );
7507 multi_buffer
7508 });
7509 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7510 Editor::new(
7511 EditorMode::Full,
7512 multi_buffer,
7513 Some(project.clone()),
7514 true,
7515 window,
7516 cx,
7517 )
7518 });
7519
7520 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7521 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7522 s.select_ranges(Some(1..2))
7523 });
7524 editor.insert("|one|two|three|", window, cx);
7525 });
7526 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7527 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7528 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7529 s.select_ranges(Some(60..70))
7530 });
7531 editor.insert("|four|five|six|", window, cx);
7532 });
7533 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7534
7535 // First two buffers should be edited, but not the third one.
7536 assert_eq!(
7537 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7538 "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}",
7539 );
7540 buffer_1.update(cx, |buffer, _| {
7541 assert!(buffer.is_dirty());
7542 assert_eq!(
7543 buffer.text(),
7544 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7545 )
7546 });
7547 buffer_2.update(cx, |buffer, _| {
7548 assert!(buffer.is_dirty());
7549 assert_eq!(
7550 buffer.text(),
7551 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7552 )
7553 });
7554 buffer_3.update(cx, |buffer, _| {
7555 assert!(!buffer.is_dirty());
7556 assert_eq!(buffer.text(), sample_text_3,)
7557 });
7558 cx.executor().run_until_parked();
7559
7560 cx.executor().start_waiting();
7561 let save = multi_buffer_editor
7562 .update_in(cx, |editor, window, cx| {
7563 editor.save(true, project.clone(), window, cx)
7564 })
7565 .unwrap();
7566
7567 let fake_server = fake_servers.next().await.unwrap();
7568 fake_server
7569 .server
7570 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7571 Ok(Some(vec![lsp::TextEdit::new(
7572 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7573 format!("[{} formatted]", params.text_document.uri),
7574 )]))
7575 })
7576 .detach();
7577 save.await;
7578
7579 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7580 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7581 assert_eq!(
7582 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7583 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}"),
7584 );
7585 buffer_1.update(cx, |buffer, _| {
7586 assert!(!buffer.is_dirty());
7587 assert_eq!(
7588 buffer.text(),
7589 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7590 )
7591 });
7592 buffer_2.update(cx, |buffer, _| {
7593 assert!(!buffer.is_dirty());
7594 assert_eq!(
7595 buffer.text(),
7596 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7597 )
7598 });
7599 buffer_3.update(cx, |buffer, _| {
7600 assert!(!buffer.is_dirty());
7601 assert_eq!(buffer.text(), sample_text_3,)
7602 });
7603}
7604
7605#[gpui::test]
7606async fn test_range_format_during_save(cx: &mut TestAppContext) {
7607 init_test(cx, |_| {});
7608
7609 let fs = FakeFs::new(cx.executor());
7610 fs.insert_file(path!("/file.rs"), Default::default()).await;
7611
7612 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7613
7614 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7615 language_registry.add(rust_lang());
7616 let mut fake_servers = language_registry.register_fake_lsp(
7617 "Rust",
7618 FakeLspAdapter {
7619 capabilities: lsp::ServerCapabilities {
7620 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7621 ..Default::default()
7622 },
7623 ..Default::default()
7624 },
7625 );
7626
7627 let buffer = project
7628 .update(cx, |project, cx| {
7629 project.open_local_buffer(path!("/file.rs"), cx)
7630 })
7631 .await
7632 .unwrap();
7633
7634 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7635 let (editor, cx) = cx.add_window_view(|window, cx| {
7636 build_editor_with_project(project.clone(), buffer, window, cx)
7637 });
7638 editor.update_in(cx, |editor, window, cx| {
7639 editor.set_text("one\ntwo\nthree\n", window, cx)
7640 });
7641 assert!(cx.read(|cx| editor.is_dirty(cx)));
7642
7643 cx.executor().start_waiting();
7644 let fake_server = fake_servers.next().await.unwrap();
7645
7646 let save = editor
7647 .update_in(cx, |editor, window, cx| {
7648 editor.save(true, project.clone(), window, cx)
7649 })
7650 .unwrap();
7651 fake_server
7652 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7653 assert_eq!(
7654 params.text_document.uri,
7655 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7656 );
7657 assert_eq!(params.options.tab_size, 4);
7658 Ok(Some(vec![lsp::TextEdit::new(
7659 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7660 ", ".to_string(),
7661 )]))
7662 })
7663 .next()
7664 .await;
7665 cx.executor().start_waiting();
7666 save.await;
7667 assert_eq!(
7668 editor.update(cx, |editor, cx| editor.text(cx)),
7669 "one, two\nthree\n"
7670 );
7671 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7672
7673 editor.update_in(cx, |editor, window, cx| {
7674 editor.set_text("one\ntwo\nthree\n", window, cx)
7675 });
7676 assert!(cx.read(|cx| editor.is_dirty(cx)));
7677
7678 // Ensure we can still save even if formatting hangs.
7679 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7680 move |params, _| async move {
7681 assert_eq!(
7682 params.text_document.uri,
7683 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7684 );
7685 futures::future::pending::<()>().await;
7686 unreachable!()
7687 },
7688 );
7689 let save = editor
7690 .update_in(cx, |editor, window, cx| {
7691 editor.save(true, project.clone(), window, cx)
7692 })
7693 .unwrap();
7694 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7695 cx.executor().start_waiting();
7696 save.await;
7697 assert_eq!(
7698 editor.update(cx, |editor, cx| editor.text(cx)),
7699 "one\ntwo\nthree\n"
7700 );
7701 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7702
7703 // For non-dirty buffer, no formatting request should be sent
7704 let save = editor
7705 .update_in(cx, |editor, window, cx| {
7706 editor.save(true, project.clone(), window, cx)
7707 })
7708 .unwrap();
7709 let _pending_format_request = fake_server
7710 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7711 panic!("Should not be invoked on non-dirty buffer");
7712 })
7713 .next();
7714 cx.executor().start_waiting();
7715 save.await;
7716
7717 // Set Rust language override and assert overridden tabsize is sent to language server
7718 update_test_language_settings(cx, |settings| {
7719 settings.languages.insert(
7720 "Rust".into(),
7721 LanguageSettingsContent {
7722 tab_size: NonZeroU32::new(8),
7723 ..Default::default()
7724 },
7725 );
7726 });
7727
7728 editor.update_in(cx, |editor, window, cx| {
7729 editor.set_text("somehting_new\n", window, cx)
7730 });
7731 assert!(cx.read(|cx| editor.is_dirty(cx)));
7732 let save = editor
7733 .update_in(cx, |editor, window, cx| {
7734 editor.save(true, project.clone(), window, cx)
7735 })
7736 .unwrap();
7737 fake_server
7738 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7739 assert_eq!(
7740 params.text_document.uri,
7741 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7742 );
7743 assert_eq!(params.options.tab_size, 8);
7744 Ok(Some(vec![]))
7745 })
7746 .next()
7747 .await;
7748 cx.executor().start_waiting();
7749 save.await;
7750}
7751
7752#[gpui::test]
7753async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7754 init_test(cx, |settings| {
7755 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7756 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7757 ))
7758 });
7759
7760 let fs = FakeFs::new(cx.executor());
7761 fs.insert_file(path!("/file.rs"), Default::default()).await;
7762
7763 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7764
7765 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7766 language_registry.add(Arc::new(Language::new(
7767 LanguageConfig {
7768 name: "Rust".into(),
7769 matcher: LanguageMatcher {
7770 path_suffixes: vec!["rs".to_string()],
7771 ..Default::default()
7772 },
7773 ..LanguageConfig::default()
7774 },
7775 Some(tree_sitter_rust::LANGUAGE.into()),
7776 )));
7777 update_test_language_settings(cx, |settings| {
7778 // Enable Prettier formatting for the same buffer, and ensure
7779 // LSP is called instead of Prettier.
7780 settings.defaults.prettier = Some(PrettierSettings {
7781 allowed: true,
7782 ..PrettierSettings::default()
7783 });
7784 });
7785 let mut fake_servers = language_registry.register_fake_lsp(
7786 "Rust",
7787 FakeLspAdapter {
7788 capabilities: lsp::ServerCapabilities {
7789 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7790 ..Default::default()
7791 },
7792 ..Default::default()
7793 },
7794 );
7795
7796 let buffer = project
7797 .update(cx, |project, cx| {
7798 project.open_local_buffer(path!("/file.rs"), cx)
7799 })
7800 .await
7801 .unwrap();
7802
7803 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7804 let (editor, cx) = cx.add_window_view(|window, cx| {
7805 build_editor_with_project(project.clone(), buffer, window, cx)
7806 });
7807 editor.update_in(cx, |editor, window, cx| {
7808 editor.set_text("one\ntwo\nthree\n", window, cx)
7809 });
7810
7811 cx.executor().start_waiting();
7812 let fake_server = fake_servers.next().await.unwrap();
7813
7814 let format = editor
7815 .update_in(cx, |editor, window, cx| {
7816 editor.perform_format(
7817 project.clone(),
7818 FormatTrigger::Manual,
7819 FormatTarget::Buffers,
7820 window,
7821 cx,
7822 )
7823 })
7824 .unwrap();
7825 fake_server
7826 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7827 assert_eq!(
7828 params.text_document.uri,
7829 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7830 );
7831 assert_eq!(params.options.tab_size, 4);
7832 Ok(Some(vec![lsp::TextEdit::new(
7833 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7834 ", ".to_string(),
7835 )]))
7836 })
7837 .next()
7838 .await;
7839 cx.executor().start_waiting();
7840 format.await;
7841 assert_eq!(
7842 editor.update(cx, |editor, cx| editor.text(cx)),
7843 "one, two\nthree\n"
7844 );
7845
7846 editor.update_in(cx, |editor, window, cx| {
7847 editor.set_text("one\ntwo\nthree\n", window, cx)
7848 });
7849 // Ensure we don't lock if formatting hangs.
7850 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7851 assert_eq!(
7852 params.text_document.uri,
7853 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7854 );
7855 futures::future::pending::<()>().await;
7856 unreachable!()
7857 });
7858 let format = editor
7859 .update_in(cx, |editor, window, cx| {
7860 editor.perform_format(
7861 project,
7862 FormatTrigger::Manual,
7863 FormatTarget::Buffers,
7864 window,
7865 cx,
7866 )
7867 })
7868 .unwrap();
7869 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7870 cx.executor().start_waiting();
7871 format.await;
7872 assert_eq!(
7873 editor.update(cx, |editor, cx| editor.text(cx)),
7874 "one\ntwo\nthree\n"
7875 );
7876}
7877
7878#[gpui::test]
7879async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
7880 init_test(cx, |_| {});
7881
7882 let mut cx = EditorLspTestContext::new_rust(
7883 lsp::ServerCapabilities {
7884 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7885 ..Default::default()
7886 },
7887 cx,
7888 )
7889 .await;
7890
7891 cx.set_state(indoc! {"
7892 one.twoˇ
7893 "});
7894
7895 // The format request takes a long time. When it completes, it inserts
7896 // a newline and an indent before the `.`
7897 cx.lsp
7898 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7899 let executor = cx.background_executor().clone();
7900 async move {
7901 executor.timer(Duration::from_millis(100)).await;
7902 Ok(Some(vec![lsp::TextEdit {
7903 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7904 new_text: "\n ".into(),
7905 }]))
7906 }
7907 });
7908
7909 // Submit a format request.
7910 let format_1 = cx
7911 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7912 .unwrap();
7913 cx.executor().run_until_parked();
7914
7915 // Submit a second format request.
7916 let format_2 = cx
7917 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7918 .unwrap();
7919 cx.executor().run_until_parked();
7920
7921 // Wait for both format requests to complete
7922 cx.executor().advance_clock(Duration::from_millis(200));
7923 cx.executor().start_waiting();
7924 format_1.await.unwrap();
7925 cx.executor().start_waiting();
7926 format_2.await.unwrap();
7927
7928 // The formatting edits only happens once.
7929 cx.assert_editor_state(indoc! {"
7930 one
7931 .twoˇ
7932 "});
7933}
7934
7935#[gpui::test]
7936async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
7937 init_test(cx, |settings| {
7938 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7939 });
7940
7941 let mut cx = EditorLspTestContext::new_rust(
7942 lsp::ServerCapabilities {
7943 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7944 ..Default::default()
7945 },
7946 cx,
7947 )
7948 .await;
7949
7950 // Set up a buffer white some trailing whitespace and no trailing newline.
7951 cx.set_state(
7952 &[
7953 "one ", //
7954 "twoˇ", //
7955 "three ", //
7956 "four", //
7957 ]
7958 .join("\n"),
7959 );
7960
7961 // Submit a format request.
7962 let format = cx
7963 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7964 .unwrap();
7965
7966 // Record which buffer changes have been sent to the language server
7967 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7968 cx.lsp
7969 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7970 let buffer_changes = buffer_changes.clone();
7971 move |params, _| {
7972 buffer_changes.lock().extend(
7973 params
7974 .content_changes
7975 .into_iter()
7976 .map(|e| (e.range.unwrap(), e.text)),
7977 );
7978 }
7979 });
7980
7981 // Handle formatting requests to the language server.
7982 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7983 let buffer_changes = buffer_changes.clone();
7984 move |_, _| {
7985 // When formatting is requested, trailing whitespace has already been stripped,
7986 // and the trailing newline has already been added.
7987 assert_eq!(
7988 &buffer_changes.lock()[1..],
7989 &[
7990 (
7991 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7992 "".into()
7993 ),
7994 (
7995 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7996 "".into()
7997 ),
7998 (
7999 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8000 "\n".into()
8001 ),
8002 ]
8003 );
8004
8005 // Insert blank lines between each line of the buffer.
8006 async move {
8007 Ok(Some(vec![
8008 lsp::TextEdit {
8009 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8010 new_text: "\n".into(),
8011 },
8012 lsp::TextEdit {
8013 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8014 new_text: "\n".into(),
8015 },
8016 ]))
8017 }
8018 }
8019 });
8020
8021 // After formatting the buffer, the trailing whitespace is stripped,
8022 // a newline is appended, and the edits provided by the language server
8023 // have been applied.
8024 format.await.unwrap();
8025 cx.assert_editor_state(
8026 &[
8027 "one", //
8028 "", //
8029 "twoˇ", //
8030 "", //
8031 "three", //
8032 "four", //
8033 "", //
8034 ]
8035 .join("\n"),
8036 );
8037
8038 // Undoing the formatting undoes the trailing whitespace removal, the
8039 // trailing newline, and the LSP edits.
8040 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8041 cx.assert_editor_state(
8042 &[
8043 "one ", //
8044 "twoˇ", //
8045 "three ", //
8046 "four", //
8047 ]
8048 .join("\n"),
8049 );
8050}
8051
8052#[gpui::test]
8053async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8054 cx: &mut TestAppContext,
8055) {
8056 init_test(cx, |_| {});
8057
8058 cx.update(|cx| {
8059 cx.update_global::<SettingsStore, _>(|settings, cx| {
8060 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8061 settings.auto_signature_help = Some(true);
8062 });
8063 });
8064 });
8065
8066 let mut cx = EditorLspTestContext::new_rust(
8067 lsp::ServerCapabilities {
8068 signature_help_provider: Some(lsp::SignatureHelpOptions {
8069 ..Default::default()
8070 }),
8071 ..Default::default()
8072 },
8073 cx,
8074 )
8075 .await;
8076
8077 let language = Language::new(
8078 LanguageConfig {
8079 name: "Rust".into(),
8080 brackets: BracketPairConfig {
8081 pairs: vec![
8082 BracketPair {
8083 start: "{".to_string(),
8084 end: "}".to_string(),
8085 close: true,
8086 surround: true,
8087 newline: true,
8088 },
8089 BracketPair {
8090 start: "(".to_string(),
8091 end: ")".to_string(),
8092 close: true,
8093 surround: true,
8094 newline: true,
8095 },
8096 BracketPair {
8097 start: "/*".to_string(),
8098 end: " */".to_string(),
8099 close: true,
8100 surround: true,
8101 newline: true,
8102 },
8103 BracketPair {
8104 start: "[".to_string(),
8105 end: "]".to_string(),
8106 close: false,
8107 surround: false,
8108 newline: true,
8109 },
8110 BracketPair {
8111 start: "\"".to_string(),
8112 end: "\"".to_string(),
8113 close: true,
8114 surround: true,
8115 newline: false,
8116 },
8117 BracketPair {
8118 start: "<".to_string(),
8119 end: ">".to_string(),
8120 close: false,
8121 surround: true,
8122 newline: true,
8123 },
8124 ],
8125 ..Default::default()
8126 },
8127 autoclose_before: "})]".to_string(),
8128 ..Default::default()
8129 },
8130 Some(tree_sitter_rust::LANGUAGE.into()),
8131 );
8132 let language = Arc::new(language);
8133
8134 cx.language_registry().add(language.clone());
8135 cx.update_buffer(|buffer, cx| {
8136 buffer.set_language(Some(language), cx);
8137 });
8138
8139 cx.set_state(
8140 &r#"
8141 fn main() {
8142 sampleˇ
8143 }
8144 "#
8145 .unindent(),
8146 );
8147
8148 cx.update_editor(|editor, window, cx| {
8149 editor.handle_input("(", window, cx);
8150 });
8151 cx.assert_editor_state(
8152 &"
8153 fn main() {
8154 sample(ˇ)
8155 }
8156 "
8157 .unindent(),
8158 );
8159
8160 let mocked_response = lsp::SignatureHelp {
8161 signatures: vec![lsp::SignatureInformation {
8162 label: "fn sample(param1: u8, param2: u8)".to_string(),
8163 documentation: None,
8164 parameters: Some(vec![
8165 lsp::ParameterInformation {
8166 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8167 documentation: None,
8168 },
8169 lsp::ParameterInformation {
8170 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8171 documentation: None,
8172 },
8173 ]),
8174 active_parameter: None,
8175 }],
8176 active_signature: Some(0),
8177 active_parameter: Some(0),
8178 };
8179 handle_signature_help_request(&mut cx, mocked_response).await;
8180
8181 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8182 .await;
8183
8184 cx.editor(|editor, _, _| {
8185 let signature_help_state = editor.signature_help_state.popover().cloned();
8186 assert_eq!(
8187 signature_help_state.unwrap().label,
8188 "param1: u8, param2: u8"
8189 );
8190 });
8191}
8192
8193#[gpui::test]
8194async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8195 init_test(cx, |_| {});
8196
8197 cx.update(|cx| {
8198 cx.update_global::<SettingsStore, _>(|settings, cx| {
8199 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8200 settings.auto_signature_help = Some(false);
8201 settings.show_signature_help_after_edits = Some(false);
8202 });
8203 });
8204 });
8205
8206 let mut cx = EditorLspTestContext::new_rust(
8207 lsp::ServerCapabilities {
8208 signature_help_provider: Some(lsp::SignatureHelpOptions {
8209 ..Default::default()
8210 }),
8211 ..Default::default()
8212 },
8213 cx,
8214 )
8215 .await;
8216
8217 let language = Language::new(
8218 LanguageConfig {
8219 name: "Rust".into(),
8220 brackets: BracketPairConfig {
8221 pairs: vec![
8222 BracketPair {
8223 start: "{".to_string(),
8224 end: "}".to_string(),
8225 close: true,
8226 surround: true,
8227 newline: true,
8228 },
8229 BracketPair {
8230 start: "(".to_string(),
8231 end: ")".to_string(),
8232 close: true,
8233 surround: true,
8234 newline: true,
8235 },
8236 BracketPair {
8237 start: "/*".to_string(),
8238 end: " */".to_string(),
8239 close: true,
8240 surround: true,
8241 newline: true,
8242 },
8243 BracketPair {
8244 start: "[".to_string(),
8245 end: "]".to_string(),
8246 close: false,
8247 surround: false,
8248 newline: true,
8249 },
8250 BracketPair {
8251 start: "\"".to_string(),
8252 end: "\"".to_string(),
8253 close: true,
8254 surround: true,
8255 newline: false,
8256 },
8257 BracketPair {
8258 start: "<".to_string(),
8259 end: ">".to_string(),
8260 close: false,
8261 surround: true,
8262 newline: true,
8263 },
8264 ],
8265 ..Default::default()
8266 },
8267 autoclose_before: "})]".to_string(),
8268 ..Default::default()
8269 },
8270 Some(tree_sitter_rust::LANGUAGE.into()),
8271 );
8272 let language = Arc::new(language);
8273
8274 cx.language_registry().add(language.clone());
8275 cx.update_buffer(|buffer, cx| {
8276 buffer.set_language(Some(language), cx);
8277 });
8278
8279 // Ensure that signature_help is not called when no signature help is enabled.
8280 cx.set_state(
8281 &r#"
8282 fn main() {
8283 sampleˇ
8284 }
8285 "#
8286 .unindent(),
8287 );
8288 cx.update_editor(|editor, window, cx| {
8289 editor.handle_input("(", window, cx);
8290 });
8291 cx.assert_editor_state(
8292 &"
8293 fn main() {
8294 sample(ˇ)
8295 }
8296 "
8297 .unindent(),
8298 );
8299 cx.editor(|editor, _, _| {
8300 assert!(editor.signature_help_state.task().is_none());
8301 });
8302
8303 let mocked_response = lsp::SignatureHelp {
8304 signatures: vec![lsp::SignatureInformation {
8305 label: "fn sample(param1: u8, param2: u8)".to_string(),
8306 documentation: None,
8307 parameters: Some(vec![
8308 lsp::ParameterInformation {
8309 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8310 documentation: None,
8311 },
8312 lsp::ParameterInformation {
8313 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8314 documentation: None,
8315 },
8316 ]),
8317 active_parameter: None,
8318 }],
8319 active_signature: Some(0),
8320 active_parameter: Some(0),
8321 };
8322
8323 // Ensure that signature_help is called when enabled afte edits
8324 cx.update(|_, cx| {
8325 cx.update_global::<SettingsStore, _>(|settings, cx| {
8326 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8327 settings.auto_signature_help = Some(false);
8328 settings.show_signature_help_after_edits = Some(true);
8329 });
8330 });
8331 });
8332 cx.set_state(
8333 &r#"
8334 fn main() {
8335 sampleˇ
8336 }
8337 "#
8338 .unindent(),
8339 );
8340 cx.update_editor(|editor, window, cx| {
8341 editor.handle_input("(", window, cx);
8342 });
8343 cx.assert_editor_state(
8344 &"
8345 fn main() {
8346 sample(ˇ)
8347 }
8348 "
8349 .unindent(),
8350 );
8351 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8352 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8353 .await;
8354 cx.update_editor(|editor, _, _| {
8355 let signature_help_state = editor.signature_help_state.popover().cloned();
8356 assert!(signature_help_state.is_some());
8357 assert_eq!(
8358 signature_help_state.unwrap().label,
8359 "param1: u8, param2: u8"
8360 );
8361 editor.signature_help_state = SignatureHelpState::default();
8362 });
8363
8364 // Ensure that signature_help is called when auto signature help override is enabled
8365 cx.update(|_, cx| {
8366 cx.update_global::<SettingsStore, _>(|settings, cx| {
8367 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8368 settings.auto_signature_help = Some(true);
8369 settings.show_signature_help_after_edits = Some(false);
8370 });
8371 });
8372 });
8373 cx.set_state(
8374 &r#"
8375 fn main() {
8376 sampleˇ
8377 }
8378 "#
8379 .unindent(),
8380 );
8381 cx.update_editor(|editor, window, cx| {
8382 editor.handle_input("(", window, cx);
8383 });
8384 cx.assert_editor_state(
8385 &"
8386 fn main() {
8387 sample(ˇ)
8388 }
8389 "
8390 .unindent(),
8391 );
8392 handle_signature_help_request(&mut cx, mocked_response).await;
8393 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8394 .await;
8395 cx.editor(|editor, _, _| {
8396 let signature_help_state = editor.signature_help_state.popover().cloned();
8397 assert!(signature_help_state.is_some());
8398 assert_eq!(
8399 signature_help_state.unwrap().label,
8400 "param1: u8, param2: u8"
8401 );
8402 });
8403}
8404
8405#[gpui::test]
8406async fn test_signature_help(cx: &mut TestAppContext) {
8407 init_test(cx, |_| {});
8408 cx.update(|cx| {
8409 cx.update_global::<SettingsStore, _>(|settings, cx| {
8410 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8411 settings.auto_signature_help = Some(true);
8412 });
8413 });
8414 });
8415
8416 let mut cx = EditorLspTestContext::new_rust(
8417 lsp::ServerCapabilities {
8418 signature_help_provider: Some(lsp::SignatureHelpOptions {
8419 ..Default::default()
8420 }),
8421 ..Default::default()
8422 },
8423 cx,
8424 )
8425 .await;
8426
8427 // A test that directly calls `show_signature_help`
8428 cx.update_editor(|editor, window, cx| {
8429 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8430 });
8431
8432 let mocked_response = lsp::SignatureHelp {
8433 signatures: vec![lsp::SignatureInformation {
8434 label: "fn sample(param1: u8, param2: u8)".to_string(),
8435 documentation: None,
8436 parameters: Some(vec![
8437 lsp::ParameterInformation {
8438 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8439 documentation: None,
8440 },
8441 lsp::ParameterInformation {
8442 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8443 documentation: None,
8444 },
8445 ]),
8446 active_parameter: None,
8447 }],
8448 active_signature: Some(0),
8449 active_parameter: Some(0),
8450 };
8451 handle_signature_help_request(&mut cx, mocked_response).await;
8452
8453 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8454 .await;
8455
8456 cx.editor(|editor, _, _| {
8457 let signature_help_state = editor.signature_help_state.popover().cloned();
8458 assert!(signature_help_state.is_some());
8459 assert_eq!(
8460 signature_help_state.unwrap().label,
8461 "param1: u8, param2: u8"
8462 );
8463 });
8464
8465 // When exiting outside from inside the brackets, `signature_help` is closed.
8466 cx.set_state(indoc! {"
8467 fn main() {
8468 sample(ˇ);
8469 }
8470
8471 fn sample(param1: u8, param2: u8) {}
8472 "});
8473
8474 cx.update_editor(|editor, window, cx| {
8475 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8476 });
8477
8478 let mocked_response = lsp::SignatureHelp {
8479 signatures: Vec::new(),
8480 active_signature: None,
8481 active_parameter: None,
8482 };
8483 handle_signature_help_request(&mut cx, mocked_response).await;
8484
8485 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8486 .await;
8487
8488 cx.editor(|editor, _, _| {
8489 assert!(!editor.signature_help_state.is_shown());
8490 });
8491
8492 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8493 cx.set_state(indoc! {"
8494 fn main() {
8495 sample(ˇ);
8496 }
8497
8498 fn sample(param1: u8, param2: u8) {}
8499 "});
8500
8501 let mocked_response = lsp::SignatureHelp {
8502 signatures: vec![lsp::SignatureInformation {
8503 label: "fn sample(param1: u8, param2: u8)".to_string(),
8504 documentation: None,
8505 parameters: Some(vec![
8506 lsp::ParameterInformation {
8507 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8508 documentation: None,
8509 },
8510 lsp::ParameterInformation {
8511 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8512 documentation: None,
8513 },
8514 ]),
8515 active_parameter: None,
8516 }],
8517 active_signature: Some(0),
8518 active_parameter: Some(0),
8519 };
8520 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8521 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8522 .await;
8523 cx.editor(|editor, _, _| {
8524 assert!(editor.signature_help_state.is_shown());
8525 });
8526
8527 // Restore the popover with more parameter input
8528 cx.set_state(indoc! {"
8529 fn main() {
8530 sample(param1, param2ˇ);
8531 }
8532
8533 fn sample(param1: u8, param2: u8) {}
8534 "});
8535
8536 let mocked_response = lsp::SignatureHelp {
8537 signatures: vec![lsp::SignatureInformation {
8538 label: "fn sample(param1: u8, param2: u8)".to_string(),
8539 documentation: None,
8540 parameters: Some(vec![
8541 lsp::ParameterInformation {
8542 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8543 documentation: None,
8544 },
8545 lsp::ParameterInformation {
8546 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8547 documentation: None,
8548 },
8549 ]),
8550 active_parameter: None,
8551 }],
8552 active_signature: Some(0),
8553 active_parameter: Some(1),
8554 };
8555 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8556 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8557 .await;
8558
8559 // When selecting a range, the popover is gone.
8560 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8561 cx.update_editor(|editor, window, cx| {
8562 editor.change_selections(None, window, cx, |s| {
8563 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8564 })
8565 });
8566 cx.assert_editor_state(indoc! {"
8567 fn main() {
8568 sample(param1, «ˇparam2»);
8569 }
8570
8571 fn sample(param1: u8, param2: u8) {}
8572 "});
8573 cx.editor(|editor, _, _| {
8574 assert!(!editor.signature_help_state.is_shown());
8575 });
8576
8577 // When unselecting again, the popover is back if within the brackets.
8578 cx.update_editor(|editor, window, cx| {
8579 editor.change_selections(None, window, cx, |s| {
8580 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8581 })
8582 });
8583 cx.assert_editor_state(indoc! {"
8584 fn main() {
8585 sample(param1, ˇparam2);
8586 }
8587
8588 fn sample(param1: u8, param2: u8) {}
8589 "});
8590 handle_signature_help_request(&mut cx, mocked_response).await;
8591 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8592 .await;
8593 cx.editor(|editor, _, _| {
8594 assert!(editor.signature_help_state.is_shown());
8595 });
8596
8597 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8598 cx.update_editor(|editor, window, cx| {
8599 editor.change_selections(None, window, cx, |s| {
8600 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8601 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8602 })
8603 });
8604 cx.assert_editor_state(indoc! {"
8605 fn main() {
8606 sample(param1, ˇparam2);
8607 }
8608
8609 fn sample(param1: u8, param2: u8) {}
8610 "});
8611
8612 let mocked_response = lsp::SignatureHelp {
8613 signatures: vec![lsp::SignatureInformation {
8614 label: "fn sample(param1: u8, param2: u8)".to_string(),
8615 documentation: None,
8616 parameters: Some(vec![
8617 lsp::ParameterInformation {
8618 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8619 documentation: None,
8620 },
8621 lsp::ParameterInformation {
8622 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8623 documentation: None,
8624 },
8625 ]),
8626 active_parameter: None,
8627 }],
8628 active_signature: Some(0),
8629 active_parameter: Some(1),
8630 };
8631 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8632 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8633 .await;
8634 cx.update_editor(|editor, _, cx| {
8635 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8636 });
8637 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8638 .await;
8639 cx.update_editor(|editor, window, cx| {
8640 editor.change_selections(None, window, cx, |s| {
8641 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8642 })
8643 });
8644 cx.assert_editor_state(indoc! {"
8645 fn main() {
8646 sample(param1, «ˇparam2»);
8647 }
8648
8649 fn sample(param1: u8, param2: u8) {}
8650 "});
8651 cx.update_editor(|editor, window, cx| {
8652 editor.change_selections(None, window, cx, |s| {
8653 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8654 })
8655 });
8656 cx.assert_editor_state(indoc! {"
8657 fn main() {
8658 sample(param1, ˇparam2);
8659 }
8660
8661 fn sample(param1: u8, param2: u8) {}
8662 "});
8663 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8664 .await;
8665}
8666
8667#[gpui::test]
8668async fn test_completion(cx: &mut TestAppContext) {
8669 init_test(cx, |_| {});
8670
8671 let mut cx = EditorLspTestContext::new_rust(
8672 lsp::ServerCapabilities {
8673 completion_provider: Some(lsp::CompletionOptions {
8674 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8675 resolve_provider: Some(true),
8676 ..Default::default()
8677 }),
8678 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8679 ..Default::default()
8680 },
8681 cx,
8682 )
8683 .await;
8684 let counter = Arc::new(AtomicUsize::new(0));
8685
8686 cx.set_state(indoc! {"
8687 oneˇ
8688 two
8689 three
8690 "});
8691 cx.simulate_keystroke(".");
8692 handle_completion_request(
8693 &mut cx,
8694 indoc! {"
8695 one.|<>
8696 two
8697 three
8698 "},
8699 vec!["first_completion", "second_completion"],
8700 counter.clone(),
8701 )
8702 .await;
8703 cx.condition(|editor, _| editor.context_menu_visible())
8704 .await;
8705 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8706
8707 let _handler = handle_signature_help_request(
8708 &mut cx,
8709 lsp::SignatureHelp {
8710 signatures: vec![lsp::SignatureInformation {
8711 label: "test signature".to_string(),
8712 documentation: None,
8713 parameters: Some(vec![lsp::ParameterInformation {
8714 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8715 documentation: None,
8716 }]),
8717 active_parameter: None,
8718 }],
8719 active_signature: None,
8720 active_parameter: None,
8721 },
8722 );
8723 cx.update_editor(|editor, window, cx| {
8724 assert!(
8725 !editor.signature_help_state.is_shown(),
8726 "No signature help was called for"
8727 );
8728 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8729 });
8730 cx.run_until_parked();
8731 cx.update_editor(|editor, _, _| {
8732 assert!(
8733 !editor.signature_help_state.is_shown(),
8734 "No signature help should be shown when completions menu is open"
8735 );
8736 });
8737
8738 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8739 editor.context_menu_next(&Default::default(), window, cx);
8740 editor
8741 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8742 .unwrap()
8743 });
8744 cx.assert_editor_state(indoc! {"
8745 one.second_completionˇ
8746 two
8747 three
8748 "});
8749
8750 handle_resolve_completion_request(
8751 &mut cx,
8752 Some(vec![
8753 (
8754 //This overlaps with the primary completion edit which is
8755 //misbehavior from the LSP spec, test that we filter it out
8756 indoc! {"
8757 one.second_ˇcompletion
8758 two
8759 threeˇ
8760 "},
8761 "overlapping additional edit",
8762 ),
8763 (
8764 indoc! {"
8765 one.second_completion
8766 two
8767 threeˇ
8768 "},
8769 "\nadditional edit",
8770 ),
8771 ]),
8772 )
8773 .await;
8774 apply_additional_edits.await.unwrap();
8775 cx.assert_editor_state(indoc! {"
8776 one.second_completionˇ
8777 two
8778 three
8779 additional edit
8780 "});
8781
8782 cx.set_state(indoc! {"
8783 one.second_completion
8784 twoˇ
8785 threeˇ
8786 additional edit
8787 "});
8788 cx.simulate_keystroke(" ");
8789 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8790 cx.simulate_keystroke("s");
8791 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8792
8793 cx.assert_editor_state(indoc! {"
8794 one.second_completion
8795 two sˇ
8796 three sˇ
8797 additional edit
8798 "});
8799 handle_completion_request(
8800 &mut cx,
8801 indoc! {"
8802 one.second_completion
8803 two s
8804 three <s|>
8805 additional edit
8806 "},
8807 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8808 counter.clone(),
8809 )
8810 .await;
8811 cx.condition(|editor, _| editor.context_menu_visible())
8812 .await;
8813 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8814
8815 cx.simulate_keystroke("i");
8816
8817 handle_completion_request(
8818 &mut cx,
8819 indoc! {"
8820 one.second_completion
8821 two si
8822 three <si|>
8823 additional edit
8824 "},
8825 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8826 counter.clone(),
8827 )
8828 .await;
8829 cx.condition(|editor, _| editor.context_menu_visible())
8830 .await;
8831 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8832
8833 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8834 editor
8835 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8836 .unwrap()
8837 });
8838 cx.assert_editor_state(indoc! {"
8839 one.second_completion
8840 two sixth_completionˇ
8841 three sixth_completionˇ
8842 additional edit
8843 "});
8844
8845 apply_additional_edits.await.unwrap();
8846
8847 update_test_language_settings(&mut cx, |settings| {
8848 settings.defaults.show_completions_on_input = Some(false);
8849 });
8850 cx.set_state("editorˇ");
8851 cx.simulate_keystroke(".");
8852 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8853 cx.simulate_keystroke("c");
8854 cx.simulate_keystroke("l");
8855 cx.simulate_keystroke("o");
8856 cx.assert_editor_state("editor.cloˇ");
8857 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8858 cx.update_editor(|editor, window, cx| {
8859 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8860 });
8861 handle_completion_request(
8862 &mut cx,
8863 "editor.<clo|>",
8864 vec!["close", "clobber"],
8865 counter.clone(),
8866 )
8867 .await;
8868 cx.condition(|editor, _| editor.context_menu_visible())
8869 .await;
8870 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8871
8872 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8873 editor
8874 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8875 .unwrap()
8876 });
8877 cx.assert_editor_state("editor.closeˇ");
8878 handle_resolve_completion_request(&mut cx, None).await;
8879 apply_additional_edits.await.unwrap();
8880}
8881
8882#[gpui::test]
8883async fn test_multiline_completion(cx: &mut TestAppContext) {
8884 init_test(cx, |_| {});
8885
8886 let fs = FakeFs::new(cx.executor());
8887 fs.insert_tree(
8888 path!("/a"),
8889 json!({
8890 "main.ts": "a",
8891 }),
8892 )
8893 .await;
8894
8895 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8896 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8897 let typescript_language = Arc::new(Language::new(
8898 LanguageConfig {
8899 name: "TypeScript".into(),
8900 matcher: LanguageMatcher {
8901 path_suffixes: vec!["ts".to_string()],
8902 ..LanguageMatcher::default()
8903 },
8904 line_comments: vec!["// ".into()],
8905 ..LanguageConfig::default()
8906 },
8907 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8908 ));
8909 language_registry.add(typescript_language.clone());
8910 let mut fake_servers = language_registry.register_fake_lsp(
8911 "TypeScript",
8912 FakeLspAdapter {
8913 capabilities: lsp::ServerCapabilities {
8914 completion_provider: Some(lsp::CompletionOptions {
8915 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8916 ..lsp::CompletionOptions::default()
8917 }),
8918 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8919 ..lsp::ServerCapabilities::default()
8920 },
8921 // Emulate vtsls label generation
8922 label_for_completion: Some(Box::new(|item, _| {
8923 let text = if let Some(description) = item
8924 .label_details
8925 .as_ref()
8926 .and_then(|label_details| label_details.description.as_ref())
8927 {
8928 format!("{} {}", item.label, description)
8929 } else if let Some(detail) = &item.detail {
8930 format!("{} {}", item.label, detail)
8931 } else {
8932 item.label.clone()
8933 };
8934 let len = text.len();
8935 Some(language::CodeLabel {
8936 text,
8937 runs: Vec::new(),
8938 filter_range: 0..len,
8939 })
8940 })),
8941 ..FakeLspAdapter::default()
8942 },
8943 );
8944 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8945 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8946 let worktree_id = workspace
8947 .update(cx, |workspace, _window, cx| {
8948 workspace.project().update(cx, |project, cx| {
8949 project.worktrees(cx).next().unwrap().read(cx).id()
8950 })
8951 })
8952 .unwrap();
8953 let _buffer = project
8954 .update(cx, |project, cx| {
8955 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
8956 })
8957 .await
8958 .unwrap();
8959 let editor = workspace
8960 .update(cx, |workspace, window, cx| {
8961 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8962 })
8963 .unwrap()
8964 .await
8965 .unwrap()
8966 .downcast::<Editor>()
8967 .unwrap();
8968 let fake_server = fake_servers.next().await.unwrap();
8969
8970 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8971 let multiline_label_2 = "a\nb\nc\n";
8972 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8973 let multiline_description = "d\ne\nf\n";
8974 let multiline_detail_2 = "g\nh\ni\n";
8975
8976 let mut completion_handle =
8977 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8978 Ok(Some(lsp::CompletionResponse::Array(vec![
8979 lsp::CompletionItem {
8980 label: multiline_label.to_string(),
8981 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8982 range: lsp::Range {
8983 start: lsp::Position {
8984 line: params.text_document_position.position.line,
8985 character: params.text_document_position.position.character,
8986 },
8987 end: lsp::Position {
8988 line: params.text_document_position.position.line,
8989 character: params.text_document_position.position.character,
8990 },
8991 },
8992 new_text: "new_text_1".to_string(),
8993 })),
8994 ..lsp::CompletionItem::default()
8995 },
8996 lsp::CompletionItem {
8997 label: "single line label 1".to_string(),
8998 detail: Some(multiline_detail.to_string()),
8999 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9000 range: lsp::Range {
9001 start: lsp::Position {
9002 line: params.text_document_position.position.line,
9003 character: params.text_document_position.position.character,
9004 },
9005 end: lsp::Position {
9006 line: params.text_document_position.position.line,
9007 character: params.text_document_position.position.character,
9008 },
9009 },
9010 new_text: "new_text_2".to_string(),
9011 })),
9012 ..lsp::CompletionItem::default()
9013 },
9014 lsp::CompletionItem {
9015 label: "single line label 2".to_string(),
9016 label_details: Some(lsp::CompletionItemLabelDetails {
9017 description: Some(multiline_description.to_string()),
9018 detail: None,
9019 }),
9020 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9021 range: lsp::Range {
9022 start: lsp::Position {
9023 line: params.text_document_position.position.line,
9024 character: params.text_document_position.position.character,
9025 },
9026 end: lsp::Position {
9027 line: params.text_document_position.position.line,
9028 character: params.text_document_position.position.character,
9029 },
9030 },
9031 new_text: "new_text_2".to_string(),
9032 })),
9033 ..lsp::CompletionItem::default()
9034 },
9035 lsp::CompletionItem {
9036 label: multiline_label_2.to_string(),
9037 detail: Some(multiline_detail_2.to_string()),
9038 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9039 range: lsp::Range {
9040 start: lsp::Position {
9041 line: params.text_document_position.position.line,
9042 character: params.text_document_position.position.character,
9043 },
9044 end: lsp::Position {
9045 line: params.text_document_position.position.line,
9046 character: params.text_document_position.position.character,
9047 },
9048 },
9049 new_text: "new_text_3".to_string(),
9050 })),
9051 ..lsp::CompletionItem::default()
9052 },
9053 lsp::CompletionItem {
9054 label: "Label with many spaces and \t but without newlines".to_string(),
9055 detail: Some(
9056 "Details with many spaces and \t but without newlines".to_string(),
9057 ),
9058 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9059 range: lsp::Range {
9060 start: lsp::Position {
9061 line: params.text_document_position.position.line,
9062 character: params.text_document_position.position.character,
9063 },
9064 end: lsp::Position {
9065 line: params.text_document_position.position.line,
9066 character: params.text_document_position.position.character,
9067 },
9068 },
9069 new_text: "new_text_4".to_string(),
9070 })),
9071 ..lsp::CompletionItem::default()
9072 },
9073 ])))
9074 });
9075
9076 editor.update_in(cx, |editor, window, cx| {
9077 cx.focus_self(window);
9078 editor.move_to_end(&MoveToEnd, window, cx);
9079 editor.handle_input(".", window, cx);
9080 });
9081 cx.run_until_parked();
9082 completion_handle.next().await.unwrap();
9083
9084 editor.update(cx, |editor, _| {
9085 assert!(editor.context_menu_visible());
9086 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9087 {
9088 let completion_labels = menu
9089 .completions
9090 .borrow()
9091 .iter()
9092 .map(|c| c.label.text.clone())
9093 .collect::<Vec<_>>();
9094 assert_eq!(
9095 completion_labels,
9096 &[
9097 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9098 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9099 "single line label 2 d e f ",
9100 "a b c g h i ",
9101 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9102 ],
9103 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9104 );
9105
9106 for completion in menu
9107 .completions
9108 .borrow()
9109 .iter() {
9110 assert_eq!(
9111 completion.label.filter_range,
9112 0..completion.label.text.len(),
9113 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9114 );
9115 }
9116
9117 } else {
9118 panic!("expected completion menu to be open");
9119 }
9120 });
9121}
9122
9123#[gpui::test]
9124async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9125 init_test(cx, |_| {});
9126 let mut cx = EditorLspTestContext::new_rust(
9127 lsp::ServerCapabilities {
9128 completion_provider: Some(lsp::CompletionOptions {
9129 trigger_characters: Some(vec![".".to_string()]),
9130 ..Default::default()
9131 }),
9132 ..Default::default()
9133 },
9134 cx,
9135 )
9136 .await;
9137 cx.lsp
9138 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9139 Ok(Some(lsp::CompletionResponse::Array(vec![
9140 lsp::CompletionItem {
9141 label: "first".into(),
9142 ..Default::default()
9143 },
9144 lsp::CompletionItem {
9145 label: "last".into(),
9146 ..Default::default()
9147 },
9148 ])))
9149 });
9150 cx.set_state("variableˇ");
9151 cx.simulate_keystroke(".");
9152 cx.executor().run_until_parked();
9153
9154 cx.update_editor(|editor, _, _| {
9155 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9156 {
9157 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9158 } else {
9159 panic!("expected completion menu to be open");
9160 }
9161 });
9162
9163 cx.update_editor(|editor, window, cx| {
9164 editor.move_page_down(&MovePageDown::default(), window, cx);
9165 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9166 {
9167 assert!(
9168 menu.selected_item == 1,
9169 "expected PageDown to select the last item from the context menu"
9170 );
9171 } else {
9172 panic!("expected completion menu to stay open after PageDown");
9173 }
9174 });
9175
9176 cx.update_editor(|editor, window, cx| {
9177 editor.move_page_up(&MovePageUp::default(), window, cx);
9178 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9179 {
9180 assert!(
9181 menu.selected_item == 0,
9182 "expected PageUp to select the first item from the context menu"
9183 );
9184 } else {
9185 panic!("expected completion menu to stay open after PageUp");
9186 }
9187 });
9188}
9189
9190#[gpui::test]
9191async fn test_completion_sort(cx: &mut TestAppContext) {
9192 init_test(cx, |_| {});
9193 let mut cx = EditorLspTestContext::new_rust(
9194 lsp::ServerCapabilities {
9195 completion_provider: Some(lsp::CompletionOptions {
9196 trigger_characters: Some(vec![".".to_string()]),
9197 ..Default::default()
9198 }),
9199 ..Default::default()
9200 },
9201 cx,
9202 )
9203 .await;
9204 cx.lsp
9205 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9206 Ok(Some(lsp::CompletionResponse::Array(vec![
9207 lsp::CompletionItem {
9208 label: "Range".into(),
9209 sort_text: Some("a".into()),
9210 ..Default::default()
9211 },
9212 lsp::CompletionItem {
9213 label: "r".into(),
9214 sort_text: Some("b".into()),
9215 ..Default::default()
9216 },
9217 lsp::CompletionItem {
9218 label: "ret".into(),
9219 sort_text: Some("c".into()),
9220 ..Default::default()
9221 },
9222 lsp::CompletionItem {
9223 label: "return".into(),
9224 sort_text: Some("d".into()),
9225 ..Default::default()
9226 },
9227 lsp::CompletionItem {
9228 label: "slice".into(),
9229 sort_text: Some("d".into()),
9230 ..Default::default()
9231 },
9232 ])))
9233 });
9234 cx.set_state("rˇ");
9235 cx.executor().run_until_parked();
9236 cx.update_editor(|editor, window, cx| {
9237 editor.show_completions(
9238 &ShowCompletions {
9239 trigger: Some("r".into()),
9240 },
9241 window,
9242 cx,
9243 );
9244 });
9245 cx.executor().run_until_parked();
9246
9247 cx.update_editor(|editor, _, _| {
9248 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9249 {
9250 assert_eq!(
9251 completion_menu_entries(&menu),
9252 &["r", "ret", "Range", "return"]
9253 );
9254 } else {
9255 panic!("expected completion menu to be open");
9256 }
9257 });
9258}
9259
9260#[gpui::test]
9261async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9262 init_test(cx, |_| {});
9263
9264 let mut cx = EditorLspTestContext::new_rust(
9265 lsp::ServerCapabilities {
9266 completion_provider: Some(lsp::CompletionOptions {
9267 trigger_characters: Some(vec![".".to_string()]),
9268 resolve_provider: Some(true),
9269 ..Default::default()
9270 }),
9271 ..Default::default()
9272 },
9273 cx,
9274 )
9275 .await;
9276
9277 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9278 cx.simulate_keystroke(".");
9279 let completion_item = lsp::CompletionItem {
9280 label: "Some".into(),
9281 kind: Some(lsp::CompletionItemKind::SNIPPET),
9282 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9283 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9284 kind: lsp::MarkupKind::Markdown,
9285 value: "```rust\nSome(2)\n```".to_string(),
9286 })),
9287 deprecated: Some(false),
9288 sort_text: Some("Some".to_string()),
9289 filter_text: Some("Some".to_string()),
9290 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9291 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9292 range: lsp::Range {
9293 start: lsp::Position {
9294 line: 0,
9295 character: 22,
9296 },
9297 end: lsp::Position {
9298 line: 0,
9299 character: 22,
9300 },
9301 },
9302 new_text: "Some(2)".to_string(),
9303 })),
9304 additional_text_edits: Some(vec![lsp::TextEdit {
9305 range: lsp::Range {
9306 start: lsp::Position {
9307 line: 0,
9308 character: 20,
9309 },
9310 end: lsp::Position {
9311 line: 0,
9312 character: 22,
9313 },
9314 },
9315 new_text: "".to_string(),
9316 }]),
9317 ..Default::default()
9318 };
9319
9320 let closure_completion_item = completion_item.clone();
9321 let counter = Arc::new(AtomicUsize::new(0));
9322 let counter_clone = counter.clone();
9323 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9324 let task_completion_item = closure_completion_item.clone();
9325 counter_clone.fetch_add(1, atomic::Ordering::Release);
9326 async move {
9327 Ok(Some(lsp::CompletionResponse::Array(vec![
9328 task_completion_item,
9329 ])))
9330 }
9331 });
9332
9333 cx.condition(|editor, _| editor.context_menu_visible())
9334 .await;
9335 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9336 assert!(request.next().await.is_some());
9337 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9338
9339 cx.simulate_keystroke("S");
9340 cx.simulate_keystroke("o");
9341 cx.simulate_keystroke("m");
9342 cx.condition(|editor, _| editor.context_menu_visible())
9343 .await;
9344 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9345 assert!(request.next().await.is_some());
9346 assert!(request.next().await.is_some());
9347 assert!(request.next().await.is_some());
9348 request.close();
9349 assert!(request.next().await.is_none());
9350 assert_eq!(
9351 counter.load(atomic::Ordering::Acquire),
9352 4,
9353 "With the completions menu open, only one LSP request should happen per input"
9354 );
9355}
9356
9357#[gpui::test]
9358async fn test_toggle_comment(cx: &mut TestAppContext) {
9359 init_test(cx, |_| {});
9360 let mut cx = EditorTestContext::new(cx).await;
9361 let language = Arc::new(Language::new(
9362 LanguageConfig {
9363 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9364 ..Default::default()
9365 },
9366 Some(tree_sitter_rust::LANGUAGE.into()),
9367 ));
9368 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9369
9370 // If multiple selections intersect a line, the line is only toggled once.
9371 cx.set_state(indoc! {"
9372 fn a() {
9373 «//b();
9374 ˇ»// «c();
9375 //ˇ» d();
9376 }
9377 "});
9378
9379 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9380
9381 cx.assert_editor_state(indoc! {"
9382 fn a() {
9383 «b();
9384 c();
9385 ˇ» d();
9386 }
9387 "});
9388
9389 // The comment prefix is inserted at the same column for every line in a
9390 // selection.
9391 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9392
9393 cx.assert_editor_state(indoc! {"
9394 fn a() {
9395 // «b();
9396 // c();
9397 ˇ»// d();
9398 }
9399 "});
9400
9401 // If a selection ends at the beginning of a line, that line is not toggled.
9402 cx.set_selections_state(indoc! {"
9403 fn a() {
9404 // b();
9405 «// c();
9406 ˇ» // d();
9407 }
9408 "});
9409
9410 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9411
9412 cx.assert_editor_state(indoc! {"
9413 fn a() {
9414 // b();
9415 «c();
9416 ˇ» // d();
9417 }
9418 "});
9419
9420 // If a selection span a single line and is empty, the line is toggled.
9421 cx.set_state(indoc! {"
9422 fn a() {
9423 a();
9424 b();
9425 ˇ
9426 }
9427 "});
9428
9429 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9430
9431 cx.assert_editor_state(indoc! {"
9432 fn a() {
9433 a();
9434 b();
9435 //•ˇ
9436 }
9437 "});
9438
9439 // If a selection span multiple lines, empty lines are not toggled.
9440 cx.set_state(indoc! {"
9441 fn a() {
9442 «a();
9443
9444 c();ˇ»
9445 }
9446 "});
9447
9448 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9449
9450 cx.assert_editor_state(indoc! {"
9451 fn a() {
9452 // «a();
9453
9454 // c();ˇ»
9455 }
9456 "});
9457
9458 // If a selection includes multiple comment prefixes, all lines are uncommented.
9459 cx.set_state(indoc! {"
9460 fn a() {
9461 «// a();
9462 /// b();
9463 //! c();ˇ»
9464 }
9465 "});
9466
9467 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9468
9469 cx.assert_editor_state(indoc! {"
9470 fn a() {
9471 «a();
9472 b();
9473 c();ˇ»
9474 }
9475 "});
9476}
9477
9478#[gpui::test]
9479async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9480 init_test(cx, |_| {});
9481 let mut cx = EditorTestContext::new(cx).await;
9482 let language = Arc::new(Language::new(
9483 LanguageConfig {
9484 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9485 ..Default::default()
9486 },
9487 Some(tree_sitter_rust::LANGUAGE.into()),
9488 ));
9489 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9490
9491 let toggle_comments = &ToggleComments {
9492 advance_downwards: false,
9493 ignore_indent: true,
9494 };
9495
9496 // If multiple selections intersect a line, the line is only toggled once.
9497 cx.set_state(indoc! {"
9498 fn a() {
9499 // «b();
9500 // c();
9501 // ˇ» d();
9502 }
9503 "});
9504
9505 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9506
9507 cx.assert_editor_state(indoc! {"
9508 fn a() {
9509 «b();
9510 c();
9511 ˇ» d();
9512 }
9513 "});
9514
9515 // The comment prefix is inserted at the beginning of each line
9516 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9517
9518 cx.assert_editor_state(indoc! {"
9519 fn a() {
9520 // «b();
9521 // c();
9522 // ˇ» d();
9523 }
9524 "});
9525
9526 // If a selection ends at the beginning of a line, that line is not toggled.
9527 cx.set_selections_state(indoc! {"
9528 fn a() {
9529 // b();
9530 // «c();
9531 ˇ»// d();
9532 }
9533 "});
9534
9535 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9536
9537 cx.assert_editor_state(indoc! {"
9538 fn a() {
9539 // b();
9540 «c();
9541 ˇ»// d();
9542 }
9543 "});
9544
9545 // If a selection span a single line and is empty, the line is toggled.
9546 cx.set_state(indoc! {"
9547 fn a() {
9548 a();
9549 b();
9550 ˇ
9551 }
9552 "});
9553
9554 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9555
9556 cx.assert_editor_state(indoc! {"
9557 fn a() {
9558 a();
9559 b();
9560 //ˇ
9561 }
9562 "});
9563
9564 // If a selection span multiple lines, empty lines are not toggled.
9565 cx.set_state(indoc! {"
9566 fn a() {
9567 «a();
9568
9569 c();ˇ»
9570 }
9571 "});
9572
9573 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9574
9575 cx.assert_editor_state(indoc! {"
9576 fn a() {
9577 // «a();
9578
9579 // c();ˇ»
9580 }
9581 "});
9582
9583 // If a selection includes multiple comment prefixes, all lines are uncommented.
9584 cx.set_state(indoc! {"
9585 fn a() {
9586 // «a();
9587 /// b();
9588 //! c();ˇ»
9589 }
9590 "});
9591
9592 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9593
9594 cx.assert_editor_state(indoc! {"
9595 fn a() {
9596 «a();
9597 b();
9598 c();ˇ»
9599 }
9600 "});
9601}
9602
9603#[gpui::test]
9604async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9605 init_test(cx, |_| {});
9606
9607 let language = Arc::new(Language::new(
9608 LanguageConfig {
9609 line_comments: vec!["// ".into()],
9610 ..Default::default()
9611 },
9612 Some(tree_sitter_rust::LANGUAGE.into()),
9613 ));
9614
9615 let mut cx = EditorTestContext::new(cx).await;
9616
9617 cx.language_registry().add(language.clone());
9618 cx.update_buffer(|buffer, cx| {
9619 buffer.set_language(Some(language), cx);
9620 });
9621
9622 let toggle_comments = &ToggleComments {
9623 advance_downwards: true,
9624 ignore_indent: false,
9625 };
9626
9627 // Single cursor on one line -> advance
9628 // Cursor moves horizontally 3 characters as well on non-blank line
9629 cx.set_state(indoc!(
9630 "fn a() {
9631 ˇdog();
9632 cat();
9633 }"
9634 ));
9635 cx.update_editor(|editor, window, cx| {
9636 editor.toggle_comments(toggle_comments, window, cx);
9637 });
9638 cx.assert_editor_state(indoc!(
9639 "fn a() {
9640 // dog();
9641 catˇ();
9642 }"
9643 ));
9644
9645 // Single selection on one line -> don't advance
9646 cx.set_state(indoc!(
9647 "fn a() {
9648 «dog()ˇ»;
9649 cat();
9650 }"
9651 ));
9652 cx.update_editor(|editor, window, cx| {
9653 editor.toggle_comments(toggle_comments, window, cx);
9654 });
9655 cx.assert_editor_state(indoc!(
9656 "fn a() {
9657 // «dog()ˇ»;
9658 cat();
9659 }"
9660 ));
9661
9662 // Multiple cursors on one line -> advance
9663 cx.set_state(indoc!(
9664 "fn a() {
9665 ˇdˇog();
9666 cat();
9667 }"
9668 ));
9669 cx.update_editor(|editor, window, cx| {
9670 editor.toggle_comments(toggle_comments, window, cx);
9671 });
9672 cx.assert_editor_state(indoc!(
9673 "fn a() {
9674 // dog();
9675 catˇ(ˇ);
9676 }"
9677 ));
9678
9679 // Multiple cursors on one line, with selection -> don't advance
9680 cx.set_state(indoc!(
9681 "fn a() {
9682 ˇdˇog«()ˇ»;
9683 cat();
9684 }"
9685 ));
9686 cx.update_editor(|editor, window, cx| {
9687 editor.toggle_comments(toggle_comments, window, cx);
9688 });
9689 cx.assert_editor_state(indoc!(
9690 "fn a() {
9691 // ˇdˇog«()ˇ»;
9692 cat();
9693 }"
9694 ));
9695
9696 // Single cursor on one line -> advance
9697 // Cursor moves to column 0 on blank line
9698 cx.set_state(indoc!(
9699 "fn a() {
9700 ˇdog();
9701
9702 cat();
9703 }"
9704 ));
9705 cx.update_editor(|editor, window, cx| {
9706 editor.toggle_comments(toggle_comments, window, cx);
9707 });
9708 cx.assert_editor_state(indoc!(
9709 "fn a() {
9710 // dog();
9711 ˇ
9712 cat();
9713 }"
9714 ));
9715
9716 // Single cursor on one line -> advance
9717 // Cursor starts and ends at column 0
9718 cx.set_state(indoc!(
9719 "fn a() {
9720 ˇ dog();
9721 cat();
9722 }"
9723 ));
9724 cx.update_editor(|editor, window, cx| {
9725 editor.toggle_comments(toggle_comments, window, cx);
9726 });
9727 cx.assert_editor_state(indoc!(
9728 "fn a() {
9729 // dog();
9730 ˇ cat();
9731 }"
9732 ));
9733}
9734
9735#[gpui::test]
9736async fn test_toggle_block_comment(cx: &mut TestAppContext) {
9737 init_test(cx, |_| {});
9738
9739 let mut cx = EditorTestContext::new(cx).await;
9740
9741 let html_language = Arc::new(
9742 Language::new(
9743 LanguageConfig {
9744 name: "HTML".into(),
9745 block_comment: Some(("<!-- ".into(), " -->".into())),
9746 ..Default::default()
9747 },
9748 Some(tree_sitter_html::LANGUAGE.into()),
9749 )
9750 .with_injection_query(
9751 r#"
9752 (script_element
9753 (raw_text) @injection.content
9754 (#set! injection.language "javascript"))
9755 "#,
9756 )
9757 .unwrap(),
9758 );
9759
9760 let javascript_language = Arc::new(Language::new(
9761 LanguageConfig {
9762 name: "JavaScript".into(),
9763 line_comments: vec!["// ".into()],
9764 ..Default::default()
9765 },
9766 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9767 ));
9768
9769 cx.language_registry().add(html_language.clone());
9770 cx.language_registry().add(javascript_language.clone());
9771 cx.update_buffer(|buffer, cx| {
9772 buffer.set_language(Some(html_language), cx);
9773 });
9774
9775 // Toggle comments for empty selections
9776 cx.set_state(
9777 &r#"
9778 <p>A</p>ˇ
9779 <p>B</p>ˇ
9780 <p>C</p>ˇ
9781 "#
9782 .unindent(),
9783 );
9784 cx.update_editor(|editor, window, cx| {
9785 editor.toggle_comments(&ToggleComments::default(), window, cx)
9786 });
9787 cx.assert_editor_state(
9788 &r#"
9789 <!-- <p>A</p>ˇ -->
9790 <!-- <p>B</p>ˇ -->
9791 <!-- <p>C</p>ˇ -->
9792 "#
9793 .unindent(),
9794 );
9795 cx.update_editor(|editor, window, cx| {
9796 editor.toggle_comments(&ToggleComments::default(), window, cx)
9797 });
9798 cx.assert_editor_state(
9799 &r#"
9800 <p>A</p>ˇ
9801 <p>B</p>ˇ
9802 <p>C</p>ˇ
9803 "#
9804 .unindent(),
9805 );
9806
9807 // Toggle comments for mixture of empty and non-empty selections, where
9808 // multiple selections occupy a given line.
9809 cx.set_state(
9810 &r#"
9811 <p>A«</p>
9812 <p>ˇ»B</p>ˇ
9813 <p>C«</p>
9814 <p>ˇ»D</p>ˇ
9815 "#
9816 .unindent(),
9817 );
9818
9819 cx.update_editor(|editor, window, cx| {
9820 editor.toggle_comments(&ToggleComments::default(), window, cx)
9821 });
9822 cx.assert_editor_state(
9823 &r#"
9824 <!-- <p>A«</p>
9825 <p>ˇ»B</p>ˇ -->
9826 <!-- <p>C«</p>
9827 <p>ˇ»D</p>ˇ -->
9828 "#
9829 .unindent(),
9830 );
9831 cx.update_editor(|editor, window, cx| {
9832 editor.toggle_comments(&ToggleComments::default(), window, cx)
9833 });
9834 cx.assert_editor_state(
9835 &r#"
9836 <p>A«</p>
9837 <p>ˇ»B</p>ˇ
9838 <p>C«</p>
9839 <p>ˇ»D</p>ˇ
9840 "#
9841 .unindent(),
9842 );
9843
9844 // Toggle comments when different languages are active for different
9845 // selections.
9846 cx.set_state(
9847 &r#"
9848 ˇ<script>
9849 ˇvar x = new Y();
9850 ˇ</script>
9851 "#
9852 .unindent(),
9853 );
9854 cx.executor().run_until_parked();
9855 cx.update_editor(|editor, window, cx| {
9856 editor.toggle_comments(&ToggleComments::default(), window, cx)
9857 });
9858 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9859 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9860 cx.assert_editor_state(
9861 &r#"
9862 <!-- ˇ<script> -->
9863 // ˇvar x = new Y();
9864 <!-- ˇ</script> -->
9865 "#
9866 .unindent(),
9867 );
9868}
9869
9870#[gpui::test]
9871fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9872 init_test(cx, |_| {});
9873
9874 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9875 let multibuffer = cx.new(|cx| {
9876 let mut multibuffer = MultiBuffer::new(ReadWrite);
9877 multibuffer.push_excerpts(
9878 buffer.clone(),
9879 [
9880 ExcerptRange {
9881 context: Point::new(0, 0)..Point::new(0, 4),
9882 primary: None,
9883 },
9884 ExcerptRange {
9885 context: Point::new(1, 0)..Point::new(1, 4),
9886 primary: None,
9887 },
9888 ],
9889 cx,
9890 );
9891 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9892 multibuffer
9893 });
9894
9895 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9896 editor.update_in(cx, |editor, window, cx| {
9897 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9898 editor.change_selections(None, window, cx, |s| {
9899 s.select_ranges([
9900 Point::new(0, 0)..Point::new(0, 0),
9901 Point::new(1, 0)..Point::new(1, 0),
9902 ])
9903 });
9904
9905 editor.handle_input("X", window, cx);
9906 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9907 assert_eq!(
9908 editor.selections.ranges(cx),
9909 [
9910 Point::new(0, 1)..Point::new(0, 1),
9911 Point::new(1, 1)..Point::new(1, 1),
9912 ]
9913 );
9914
9915 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9916 editor.change_selections(None, window, cx, |s| {
9917 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9918 });
9919 editor.backspace(&Default::default(), window, cx);
9920 assert_eq!(editor.text(cx), "Xa\nbbb");
9921 assert_eq!(
9922 editor.selections.ranges(cx),
9923 [Point::new(1, 0)..Point::new(1, 0)]
9924 );
9925
9926 editor.change_selections(None, window, cx, |s| {
9927 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9928 });
9929 editor.backspace(&Default::default(), window, cx);
9930 assert_eq!(editor.text(cx), "X\nbb");
9931 assert_eq!(
9932 editor.selections.ranges(cx),
9933 [Point::new(0, 1)..Point::new(0, 1)]
9934 );
9935 });
9936}
9937
9938#[gpui::test]
9939fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9940 init_test(cx, |_| {});
9941
9942 let markers = vec![('[', ']').into(), ('(', ')').into()];
9943 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9944 indoc! {"
9945 [aaaa
9946 (bbbb]
9947 cccc)",
9948 },
9949 markers.clone(),
9950 );
9951 let excerpt_ranges = markers.into_iter().map(|marker| {
9952 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9953 ExcerptRange {
9954 context,
9955 primary: None,
9956 }
9957 });
9958 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9959 let multibuffer = cx.new(|cx| {
9960 let mut multibuffer = MultiBuffer::new(ReadWrite);
9961 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9962 multibuffer
9963 });
9964
9965 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9966 editor.update_in(cx, |editor, window, cx| {
9967 let (expected_text, selection_ranges) = marked_text_ranges(
9968 indoc! {"
9969 aaaa
9970 bˇbbb
9971 bˇbbˇb
9972 cccc"
9973 },
9974 true,
9975 );
9976 assert_eq!(editor.text(cx), expected_text);
9977 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9978
9979 editor.handle_input("X", window, cx);
9980
9981 let (expected_text, expected_selections) = marked_text_ranges(
9982 indoc! {"
9983 aaaa
9984 bXˇbbXb
9985 bXˇbbXˇb
9986 cccc"
9987 },
9988 false,
9989 );
9990 assert_eq!(editor.text(cx), expected_text);
9991 assert_eq!(editor.selections.ranges(cx), expected_selections);
9992
9993 editor.newline(&Newline, window, cx);
9994 let (expected_text, expected_selections) = marked_text_ranges(
9995 indoc! {"
9996 aaaa
9997 bX
9998 ˇbbX
9999 b
10000 bX
10001 ˇbbX
10002 ˇb
10003 cccc"
10004 },
10005 false,
10006 );
10007 assert_eq!(editor.text(cx), expected_text);
10008 assert_eq!(editor.selections.ranges(cx), expected_selections);
10009 });
10010}
10011
10012#[gpui::test]
10013fn test_refresh_selections(cx: &mut TestAppContext) {
10014 init_test(cx, |_| {});
10015
10016 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10017 let mut excerpt1_id = None;
10018 let multibuffer = cx.new(|cx| {
10019 let mut multibuffer = MultiBuffer::new(ReadWrite);
10020 excerpt1_id = multibuffer
10021 .push_excerpts(
10022 buffer.clone(),
10023 [
10024 ExcerptRange {
10025 context: Point::new(0, 0)..Point::new(1, 4),
10026 primary: None,
10027 },
10028 ExcerptRange {
10029 context: Point::new(1, 0)..Point::new(2, 4),
10030 primary: None,
10031 },
10032 ],
10033 cx,
10034 )
10035 .into_iter()
10036 .next();
10037 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10038 multibuffer
10039 });
10040
10041 let editor = cx.add_window(|window, cx| {
10042 let mut editor = build_editor(multibuffer.clone(), window, cx);
10043 let snapshot = editor.snapshot(window, cx);
10044 editor.change_selections(None, window, cx, |s| {
10045 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10046 });
10047 editor.begin_selection(
10048 Point::new(2, 1).to_display_point(&snapshot),
10049 true,
10050 1,
10051 window,
10052 cx,
10053 );
10054 assert_eq!(
10055 editor.selections.ranges(cx),
10056 [
10057 Point::new(1, 3)..Point::new(1, 3),
10058 Point::new(2, 1)..Point::new(2, 1),
10059 ]
10060 );
10061 editor
10062 });
10063
10064 // Refreshing selections is a no-op when excerpts haven't changed.
10065 _ = editor.update(cx, |editor, window, cx| {
10066 editor.change_selections(None, window, cx, |s| s.refresh());
10067 assert_eq!(
10068 editor.selections.ranges(cx),
10069 [
10070 Point::new(1, 3)..Point::new(1, 3),
10071 Point::new(2, 1)..Point::new(2, 1),
10072 ]
10073 );
10074 });
10075
10076 multibuffer.update(cx, |multibuffer, cx| {
10077 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10078 });
10079 _ = editor.update(cx, |editor, window, cx| {
10080 // Removing an excerpt causes the first selection to become degenerate.
10081 assert_eq!(
10082 editor.selections.ranges(cx),
10083 [
10084 Point::new(0, 0)..Point::new(0, 0),
10085 Point::new(0, 1)..Point::new(0, 1)
10086 ]
10087 );
10088
10089 // Refreshing selections will relocate the first selection to the original buffer
10090 // location.
10091 editor.change_selections(None, window, cx, |s| s.refresh());
10092 assert_eq!(
10093 editor.selections.ranges(cx),
10094 [
10095 Point::new(0, 1)..Point::new(0, 1),
10096 Point::new(0, 3)..Point::new(0, 3)
10097 ]
10098 );
10099 assert!(editor.selections.pending_anchor().is_some());
10100 });
10101}
10102
10103#[gpui::test]
10104fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10105 init_test(cx, |_| {});
10106
10107 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10108 let mut excerpt1_id = None;
10109 let multibuffer = cx.new(|cx| {
10110 let mut multibuffer = MultiBuffer::new(ReadWrite);
10111 excerpt1_id = multibuffer
10112 .push_excerpts(
10113 buffer.clone(),
10114 [
10115 ExcerptRange {
10116 context: Point::new(0, 0)..Point::new(1, 4),
10117 primary: None,
10118 },
10119 ExcerptRange {
10120 context: Point::new(1, 0)..Point::new(2, 4),
10121 primary: None,
10122 },
10123 ],
10124 cx,
10125 )
10126 .into_iter()
10127 .next();
10128 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10129 multibuffer
10130 });
10131
10132 let editor = cx.add_window(|window, cx| {
10133 let mut editor = build_editor(multibuffer.clone(), window, cx);
10134 let snapshot = editor.snapshot(window, cx);
10135 editor.begin_selection(
10136 Point::new(1, 3).to_display_point(&snapshot),
10137 false,
10138 1,
10139 window,
10140 cx,
10141 );
10142 assert_eq!(
10143 editor.selections.ranges(cx),
10144 [Point::new(1, 3)..Point::new(1, 3)]
10145 );
10146 editor
10147 });
10148
10149 multibuffer.update(cx, |multibuffer, cx| {
10150 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10151 });
10152 _ = editor.update(cx, |editor, window, cx| {
10153 assert_eq!(
10154 editor.selections.ranges(cx),
10155 [Point::new(0, 0)..Point::new(0, 0)]
10156 );
10157
10158 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10159 editor.change_selections(None, window, cx, |s| s.refresh());
10160 assert_eq!(
10161 editor.selections.ranges(cx),
10162 [Point::new(0, 3)..Point::new(0, 3)]
10163 );
10164 assert!(editor.selections.pending_anchor().is_some());
10165 });
10166}
10167
10168#[gpui::test]
10169async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10170 init_test(cx, |_| {});
10171
10172 let language = Arc::new(
10173 Language::new(
10174 LanguageConfig {
10175 brackets: BracketPairConfig {
10176 pairs: vec![
10177 BracketPair {
10178 start: "{".to_string(),
10179 end: "}".to_string(),
10180 close: true,
10181 surround: true,
10182 newline: true,
10183 },
10184 BracketPair {
10185 start: "/* ".to_string(),
10186 end: " */".to_string(),
10187 close: true,
10188 surround: true,
10189 newline: true,
10190 },
10191 ],
10192 ..Default::default()
10193 },
10194 ..Default::default()
10195 },
10196 Some(tree_sitter_rust::LANGUAGE.into()),
10197 )
10198 .with_indents_query("")
10199 .unwrap(),
10200 );
10201
10202 let text = concat!(
10203 "{ }\n", //
10204 " x\n", //
10205 " /* */\n", //
10206 "x\n", //
10207 "{{} }\n", //
10208 );
10209
10210 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10211 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10212 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10213 editor
10214 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10215 .await;
10216
10217 editor.update_in(cx, |editor, window, cx| {
10218 editor.change_selections(None, window, cx, |s| {
10219 s.select_display_ranges([
10220 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10221 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10222 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10223 ])
10224 });
10225 editor.newline(&Newline, window, cx);
10226
10227 assert_eq!(
10228 editor.buffer().read(cx).read(cx).text(),
10229 concat!(
10230 "{ \n", // Suppress rustfmt
10231 "\n", //
10232 "}\n", //
10233 " x\n", //
10234 " /* \n", //
10235 " \n", //
10236 " */\n", //
10237 "x\n", //
10238 "{{} \n", //
10239 "}\n", //
10240 )
10241 );
10242 });
10243}
10244
10245#[gpui::test]
10246fn test_highlighted_ranges(cx: &mut TestAppContext) {
10247 init_test(cx, |_| {});
10248
10249 let editor = cx.add_window(|window, cx| {
10250 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10251 build_editor(buffer.clone(), window, cx)
10252 });
10253
10254 _ = editor.update(cx, |editor, window, cx| {
10255 struct Type1;
10256 struct Type2;
10257
10258 let buffer = editor.buffer.read(cx).snapshot(cx);
10259
10260 let anchor_range =
10261 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10262
10263 editor.highlight_background::<Type1>(
10264 &[
10265 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10266 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10267 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10268 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10269 ],
10270 |_| Hsla::red(),
10271 cx,
10272 );
10273 editor.highlight_background::<Type2>(
10274 &[
10275 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10276 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10277 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10278 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10279 ],
10280 |_| Hsla::green(),
10281 cx,
10282 );
10283
10284 let snapshot = editor.snapshot(window, cx);
10285 let mut highlighted_ranges = editor.background_highlights_in_range(
10286 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10287 &snapshot,
10288 cx.theme().colors(),
10289 );
10290 // Enforce a consistent ordering based on color without relying on the ordering of the
10291 // highlight's `TypeId` which is non-executor.
10292 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10293 assert_eq!(
10294 highlighted_ranges,
10295 &[
10296 (
10297 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10298 Hsla::red(),
10299 ),
10300 (
10301 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10302 Hsla::red(),
10303 ),
10304 (
10305 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10306 Hsla::green(),
10307 ),
10308 (
10309 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10310 Hsla::green(),
10311 ),
10312 ]
10313 );
10314 assert_eq!(
10315 editor.background_highlights_in_range(
10316 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10317 &snapshot,
10318 cx.theme().colors(),
10319 ),
10320 &[(
10321 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10322 Hsla::red(),
10323 )]
10324 );
10325 });
10326}
10327
10328#[gpui::test]
10329async fn test_following(cx: &mut TestAppContext) {
10330 init_test(cx, |_| {});
10331
10332 let fs = FakeFs::new(cx.executor());
10333 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10334
10335 let buffer = project.update(cx, |project, cx| {
10336 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10337 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10338 });
10339 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10340 let follower = cx.update(|cx| {
10341 cx.open_window(
10342 WindowOptions {
10343 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10344 gpui::Point::new(px(0.), px(0.)),
10345 gpui::Point::new(px(10.), px(80.)),
10346 ))),
10347 ..Default::default()
10348 },
10349 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10350 )
10351 .unwrap()
10352 });
10353
10354 let is_still_following = Rc::new(RefCell::new(true));
10355 let follower_edit_event_count = Rc::new(RefCell::new(0));
10356 let pending_update = Rc::new(RefCell::new(None));
10357 let leader_entity = leader.root(cx).unwrap();
10358 let follower_entity = follower.root(cx).unwrap();
10359 _ = follower.update(cx, {
10360 let update = pending_update.clone();
10361 let is_still_following = is_still_following.clone();
10362 let follower_edit_event_count = follower_edit_event_count.clone();
10363 |_, window, cx| {
10364 cx.subscribe_in(
10365 &leader_entity,
10366 window,
10367 move |_, leader, event, window, cx| {
10368 leader.read(cx).add_event_to_update_proto(
10369 event,
10370 &mut update.borrow_mut(),
10371 window,
10372 cx,
10373 );
10374 },
10375 )
10376 .detach();
10377
10378 cx.subscribe_in(
10379 &follower_entity,
10380 window,
10381 move |_, _, event: &EditorEvent, _window, _cx| {
10382 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10383 *is_still_following.borrow_mut() = false;
10384 }
10385
10386 if let EditorEvent::BufferEdited = event {
10387 *follower_edit_event_count.borrow_mut() += 1;
10388 }
10389 },
10390 )
10391 .detach();
10392 }
10393 });
10394
10395 // Update the selections only
10396 _ = leader.update(cx, |leader, window, cx| {
10397 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10398 });
10399 follower
10400 .update(cx, |follower, window, cx| {
10401 follower.apply_update_proto(
10402 &project,
10403 pending_update.borrow_mut().take().unwrap(),
10404 window,
10405 cx,
10406 )
10407 })
10408 .unwrap()
10409 .await
10410 .unwrap();
10411 _ = follower.update(cx, |follower, _, cx| {
10412 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10413 });
10414 assert!(*is_still_following.borrow());
10415 assert_eq!(*follower_edit_event_count.borrow(), 0);
10416
10417 // Update the scroll position only
10418 _ = leader.update(cx, |leader, window, cx| {
10419 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10420 });
10421 follower
10422 .update(cx, |follower, window, cx| {
10423 follower.apply_update_proto(
10424 &project,
10425 pending_update.borrow_mut().take().unwrap(),
10426 window,
10427 cx,
10428 )
10429 })
10430 .unwrap()
10431 .await
10432 .unwrap();
10433 assert_eq!(
10434 follower
10435 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10436 .unwrap(),
10437 gpui::Point::new(1.5, 3.5)
10438 );
10439 assert!(*is_still_following.borrow());
10440 assert_eq!(*follower_edit_event_count.borrow(), 0);
10441
10442 // Update the selections and scroll position. The follower's scroll position is updated
10443 // via autoscroll, not via the leader's exact scroll position.
10444 _ = leader.update(cx, |leader, window, cx| {
10445 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10446 leader.request_autoscroll(Autoscroll::newest(), cx);
10447 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10448 });
10449 follower
10450 .update(cx, |follower, window, cx| {
10451 follower.apply_update_proto(
10452 &project,
10453 pending_update.borrow_mut().take().unwrap(),
10454 window,
10455 cx,
10456 )
10457 })
10458 .unwrap()
10459 .await
10460 .unwrap();
10461 _ = follower.update(cx, |follower, _, cx| {
10462 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10463 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10464 });
10465 assert!(*is_still_following.borrow());
10466
10467 // Creating a pending selection that precedes another selection
10468 _ = leader.update(cx, |leader, window, cx| {
10469 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10470 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10471 });
10472 follower
10473 .update(cx, |follower, window, cx| {
10474 follower.apply_update_proto(
10475 &project,
10476 pending_update.borrow_mut().take().unwrap(),
10477 window,
10478 cx,
10479 )
10480 })
10481 .unwrap()
10482 .await
10483 .unwrap();
10484 _ = follower.update(cx, |follower, _, cx| {
10485 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10486 });
10487 assert!(*is_still_following.borrow());
10488
10489 // Extend the pending selection so that it surrounds another selection
10490 _ = leader.update(cx, |leader, window, cx| {
10491 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10492 });
10493 follower
10494 .update(cx, |follower, window, cx| {
10495 follower.apply_update_proto(
10496 &project,
10497 pending_update.borrow_mut().take().unwrap(),
10498 window,
10499 cx,
10500 )
10501 })
10502 .unwrap()
10503 .await
10504 .unwrap();
10505 _ = follower.update(cx, |follower, _, cx| {
10506 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10507 });
10508
10509 // Scrolling locally breaks the follow
10510 _ = follower.update(cx, |follower, window, cx| {
10511 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10512 follower.set_scroll_anchor(
10513 ScrollAnchor {
10514 anchor: top_anchor,
10515 offset: gpui::Point::new(0.0, 0.5),
10516 },
10517 window,
10518 cx,
10519 );
10520 });
10521 assert!(!(*is_still_following.borrow()));
10522}
10523
10524#[gpui::test]
10525async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10526 init_test(cx, |_| {});
10527
10528 let fs = FakeFs::new(cx.executor());
10529 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10530 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10531 let pane = workspace
10532 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10533 .unwrap();
10534
10535 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10536
10537 let leader = pane.update_in(cx, |_, window, cx| {
10538 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10539 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10540 });
10541
10542 // Start following the editor when it has no excerpts.
10543 let mut state_message =
10544 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10545 let workspace_entity = workspace.root(cx).unwrap();
10546 let follower_1 = cx
10547 .update_window(*workspace.deref(), |_, window, cx| {
10548 Editor::from_state_proto(
10549 workspace_entity,
10550 ViewId {
10551 creator: Default::default(),
10552 id: 0,
10553 },
10554 &mut state_message,
10555 window,
10556 cx,
10557 )
10558 })
10559 .unwrap()
10560 .unwrap()
10561 .await
10562 .unwrap();
10563
10564 let update_message = Rc::new(RefCell::new(None));
10565 follower_1.update_in(cx, {
10566 let update = update_message.clone();
10567 |_, window, cx| {
10568 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10569 leader.read(cx).add_event_to_update_proto(
10570 event,
10571 &mut update.borrow_mut(),
10572 window,
10573 cx,
10574 );
10575 })
10576 .detach();
10577 }
10578 });
10579
10580 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10581 (
10582 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10583 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10584 )
10585 });
10586
10587 // Insert some excerpts.
10588 leader.update(cx, |leader, cx| {
10589 leader.buffer.update(cx, |multibuffer, cx| {
10590 let excerpt_ids = multibuffer.push_excerpts(
10591 buffer_1.clone(),
10592 [
10593 ExcerptRange {
10594 context: 1..6,
10595 primary: None,
10596 },
10597 ExcerptRange {
10598 context: 12..15,
10599 primary: None,
10600 },
10601 ExcerptRange {
10602 context: 0..3,
10603 primary: None,
10604 },
10605 ],
10606 cx,
10607 );
10608 multibuffer.insert_excerpts_after(
10609 excerpt_ids[0],
10610 buffer_2.clone(),
10611 [
10612 ExcerptRange {
10613 context: 8..12,
10614 primary: None,
10615 },
10616 ExcerptRange {
10617 context: 0..6,
10618 primary: None,
10619 },
10620 ],
10621 cx,
10622 );
10623 });
10624 });
10625
10626 // Apply the update of adding the excerpts.
10627 follower_1
10628 .update_in(cx, |follower, window, cx| {
10629 follower.apply_update_proto(
10630 &project,
10631 update_message.borrow().clone().unwrap(),
10632 window,
10633 cx,
10634 )
10635 })
10636 .await
10637 .unwrap();
10638 assert_eq!(
10639 follower_1.update(cx, |editor, cx| editor.text(cx)),
10640 leader.update(cx, |editor, cx| editor.text(cx))
10641 );
10642 update_message.borrow_mut().take();
10643
10644 // Start following separately after it already has excerpts.
10645 let mut state_message =
10646 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10647 let workspace_entity = workspace.root(cx).unwrap();
10648 let follower_2 = cx
10649 .update_window(*workspace.deref(), |_, window, cx| {
10650 Editor::from_state_proto(
10651 workspace_entity,
10652 ViewId {
10653 creator: Default::default(),
10654 id: 0,
10655 },
10656 &mut state_message,
10657 window,
10658 cx,
10659 )
10660 })
10661 .unwrap()
10662 .unwrap()
10663 .await
10664 .unwrap();
10665 assert_eq!(
10666 follower_2.update(cx, |editor, cx| editor.text(cx)),
10667 leader.update(cx, |editor, cx| editor.text(cx))
10668 );
10669
10670 // Remove some excerpts.
10671 leader.update(cx, |leader, cx| {
10672 leader.buffer.update(cx, |multibuffer, cx| {
10673 let excerpt_ids = multibuffer.excerpt_ids();
10674 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10675 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10676 });
10677 });
10678
10679 // Apply the update of removing the excerpts.
10680 follower_1
10681 .update_in(cx, |follower, window, cx| {
10682 follower.apply_update_proto(
10683 &project,
10684 update_message.borrow().clone().unwrap(),
10685 window,
10686 cx,
10687 )
10688 })
10689 .await
10690 .unwrap();
10691 follower_2
10692 .update_in(cx, |follower, window, cx| {
10693 follower.apply_update_proto(
10694 &project,
10695 update_message.borrow().clone().unwrap(),
10696 window,
10697 cx,
10698 )
10699 })
10700 .await
10701 .unwrap();
10702 update_message.borrow_mut().take();
10703 assert_eq!(
10704 follower_1.update(cx, |editor, cx| editor.text(cx)),
10705 leader.update(cx, |editor, cx| editor.text(cx))
10706 );
10707}
10708
10709#[gpui::test]
10710async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
10711 init_test(cx, |_| {});
10712
10713 let mut cx = EditorTestContext::new(cx).await;
10714 let lsp_store =
10715 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10716
10717 cx.set_state(indoc! {"
10718 ˇfn func(abc def: i32) -> u32 {
10719 }
10720 "});
10721
10722 cx.update(|_, cx| {
10723 lsp_store.update(cx, |lsp_store, cx| {
10724 lsp_store
10725 .update_diagnostics(
10726 LanguageServerId(0),
10727 lsp::PublishDiagnosticsParams {
10728 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10729 version: None,
10730 diagnostics: vec![
10731 lsp::Diagnostic {
10732 range: lsp::Range::new(
10733 lsp::Position::new(0, 11),
10734 lsp::Position::new(0, 12),
10735 ),
10736 severity: Some(lsp::DiagnosticSeverity::ERROR),
10737 ..Default::default()
10738 },
10739 lsp::Diagnostic {
10740 range: lsp::Range::new(
10741 lsp::Position::new(0, 12),
10742 lsp::Position::new(0, 15),
10743 ),
10744 severity: Some(lsp::DiagnosticSeverity::ERROR),
10745 ..Default::default()
10746 },
10747 lsp::Diagnostic {
10748 range: lsp::Range::new(
10749 lsp::Position::new(0, 25),
10750 lsp::Position::new(0, 28),
10751 ),
10752 severity: Some(lsp::DiagnosticSeverity::ERROR),
10753 ..Default::default()
10754 },
10755 ],
10756 },
10757 &[],
10758 cx,
10759 )
10760 .unwrap()
10761 });
10762 });
10763
10764 executor.run_until_parked();
10765
10766 cx.update_editor(|editor, window, cx| {
10767 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10768 });
10769
10770 cx.assert_editor_state(indoc! {"
10771 fn func(abc def: i32) -> ˇu32 {
10772 }
10773 "});
10774
10775 cx.update_editor(|editor, window, cx| {
10776 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10777 });
10778
10779 cx.assert_editor_state(indoc! {"
10780 fn func(abc ˇdef: i32) -> u32 {
10781 }
10782 "});
10783
10784 cx.update_editor(|editor, window, cx| {
10785 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10786 });
10787
10788 cx.assert_editor_state(indoc! {"
10789 fn func(abcˇ def: i32) -> u32 {
10790 }
10791 "});
10792
10793 cx.update_editor(|editor, window, cx| {
10794 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10795 });
10796
10797 cx.assert_editor_state(indoc! {"
10798 fn func(abc def: i32) -> ˇu32 {
10799 }
10800 "});
10801}
10802
10803#[gpui::test]
10804async fn cycle_through_same_place_diagnostics(
10805 executor: BackgroundExecutor,
10806 cx: &mut TestAppContext,
10807) {
10808 init_test(cx, |_| {});
10809
10810 let mut cx = EditorTestContext::new(cx).await;
10811 let lsp_store =
10812 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10813
10814 cx.set_state(indoc! {"
10815 ˇfn func(abc def: i32) -> u32 {
10816 }
10817 "});
10818
10819 cx.update(|_, cx| {
10820 lsp_store.update(cx, |lsp_store, cx| {
10821 lsp_store
10822 .update_diagnostics(
10823 LanguageServerId(0),
10824 lsp::PublishDiagnosticsParams {
10825 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10826 version: None,
10827 diagnostics: vec![
10828 lsp::Diagnostic {
10829 range: lsp::Range::new(
10830 lsp::Position::new(0, 11),
10831 lsp::Position::new(0, 12),
10832 ),
10833 severity: Some(lsp::DiagnosticSeverity::ERROR),
10834 ..Default::default()
10835 },
10836 lsp::Diagnostic {
10837 range: lsp::Range::new(
10838 lsp::Position::new(0, 12),
10839 lsp::Position::new(0, 15),
10840 ),
10841 severity: Some(lsp::DiagnosticSeverity::ERROR),
10842 ..Default::default()
10843 },
10844 lsp::Diagnostic {
10845 range: lsp::Range::new(
10846 lsp::Position::new(0, 12),
10847 lsp::Position::new(0, 15),
10848 ),
10849 severity: Some(lsp::DiagnosticSeverity::ERROR),
10850 ..Default::default()
10851 },
10852 lsp::Diagnostic {
10853 range: lsp::Range::new(
10854 lsp::Position::new(0, 25),
10855 lsp::Position::new(0, 28),
10856 ),
10857 severity: Some(lsp::DiagnosticSeverity::ERROR),
10858 ..Default::default()
10859 },
10860 ],
10861 },
10862 &[],
10863 cx,
10864 )
10865 .unwrap()
10866 });
10867 });
10868 executor.run_until_parked();
10869
10870 //// Backward
10871
10872 // Fourth diagnostic
10873 cx.update_editor(|editor, window, cx| {
10874 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10875 });
10876 cx.assert_editor_state(indoc! {"
10877 fn func(abc def: i32) -> ˇu32 {
10878 }
10879 "});
10880
10881 // Third diagnostic
10882 cx.update_editor(|editor, window, cx| {
10883 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10884 });
10885 cx.assert_editor_state(indoc! {"
10886 fn func(abc ˇdef: i32) -> u32 {
10887 }
10888 "});
10889
10890 // Second diagnostic, same place
10891 cx.update_editor(|editor, window, cx| {
10892 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10893 });
10894 cx.assert_editor_state(indoc! {"
10895 fn func(abc ˇdef: i32) -> u32 {
10896 }
10897 "});
10898
10899 // First diagnostic
10900 cx.update_editor(|editor, window, cx| {
10901 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10902 });
10903 cx.assert_editor_state(indoc! {"
10904 fn func(abcˇ def: i32) -> u32 {
10905 }
10906 "});
10907
10908 // Wrapped over, fourth diagnostic
10909 cx.update_editor(|editor, window, cx| {
10910 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10911 });
10912 cx.assert_editor_state(indoc! {"
10913 fn func(abc def: i32) -> ˇu32 {
10914 }
10915 "});
10916
10917 cx.update_editor(|editor, window, cx| {
10918 editor.move_to_beginning(&MoveToBeginning, window, cx);
10919 });
10920 cx.assert_editor_state(indoc! {"
10921 ˇfn func(abc def: i32) -> u32 {
10922 }
10923 "});
10924
10925 //// Forward
10926
10927 // First diagnostic
10928 cx.update_editor(|editor, window, cx| {
10929 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10930 });
10931 cx.assert_editor_state(indoc! {"
10932 fn func(abcˇ def: i32) -> u32 {
10933 }
10934 "});
10935
10936 // Second diagnostic
10937 cx.update_editor(|editor, window, cx| {
10938 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10939 });
10940 cx.assert_editor_state(indoc! {"
10941 fn func(abc ˇdef: i32) -> u32 {
10942 }
10943 "});
10944
10945 // Third diagnostic, same place
10946 cx.update_editor(|editor, window, cx| {
10947 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10948 });
10949 cx.assert_editor_state(indoc! {"
10950 fn func(abc ˇdef: i32) -> u32 {
10951 }
10952 "});
10953
10954 // Fourth diagnostic
10955 cx.update_editor(|editor, window, cx| {
10956 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10957 });
10958 cx.assert_editor_state(indoc! {"
10959 fn func(abc def: i32) -> ˇu32 {
10960 }
10961 "});
10962
10963 // Wrapped around, first diagnostic
10964 cx.update_editor(|editor, window, cx| {
10965 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10966 });
10967 cx.assert_editor_state(indoc! {"
10968 fn func(abcˇ def: i32) -> u32 {
10969 }
10970 "});
10971}
10972
10973#[gpui::test]
10974async fn active_diagnostics_dismiss_after_invalidation(
10975 executor: BackgroundExecutor,
10976 cx: &mut TestAppContext,
10977) {
10978 init_test(cx, |_| {});
10979
10980 let mut cx = EditorTestContext::new(cx).await;
10981 let lsp_store =
10982 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10983
10984 cx.set_state(indoc! {"
10985 ˇfn func(abc def: i32) -> u32 {
10986 }
10987 "});
10988
10989 let message = "Something's wrong!";
10990 cx.update(|_, cx| {
10991 lsp_store.update(cx, |lsp_store, cx| {
10992 lsp_store
10993 .update_diagnostics(
10994 LanguageServerId(0),
10995 lsp::PublishDiagnosticsParams {
10996 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10997 version: None,
10998 diagnostics: vec![lsp::Diagnostic {
10999 range: lsp::Range::new(
11000 lsp::Position::new(0, 11),
11001 lsp::Position::new(0, 12),
11002 ),
11003 severity: Some(lsp::DiagnosticSeverity::ERROR),
11004 message: message.to_string(),
11005 ..Default::default()
11006 }],
11007 },
11008 &[],
11009 cx,
11010 )
11011 .unwrap()
11012 });
11013 });
11014 executor.run_until_parked();
11015
11016 cx.update_editor(|editor, window, cx| {
11017 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11018 assert_eq!(
11019 editor
11020 .active_diagnostics
11021 .as_ref()
11022 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11023 Some(message),
11024 "Should have a diagnostics group activated"
11025 );
11026 });
11027 cx.assert_editor_state(indoc! {"
11028 fn func(abcˇ def: i32) -> u32 {
11029 }
11030 "});
11031
11032 cx.update(|_, cx| {
11033 lsp_store.update(cx, |lsp_store, cx| {
11034 lsp_store
11035 .update_diagnostics(
11036 LanguageServerId(0),
11037 lsp::PublishDiagnosticsParams {
11038 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11039 version: None,
11040 diagnostics: Vec::new(),
11041 },
11042 &[],
11043 cx,
11044 )
11045 .unwrap()
11046 });
11047 });
11048 executor.run_until_parked();
11049 cx.update_editor(|editor, _, _| {
11050 assert_eq!(
11051 editor.active_diagnostics, None,
11052 "After no diagnostics set to the editor, no diagnostics should be active"
11053 );
11054 });
11055 cx.assert_editor_state(indoc! {"
11056 fn func(abcˇ def: i32) -> u32 {
11057 }
11058 "});
11059
11060 cx.update_editor(|editor, window, cx| {
11061 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11062 assert_eq!(
11063 editor.active_diagnostics, None,
11064 "Should be no diagnostics to go to and activate"
11065 );
11066 });
11067 cx.assert_editor_state(indoc! {"
11068 fn func(abcˇ def: i32) -> u32 {
11069 }
11070 "});
11071}
11072
11073#[gpui::test]
11074async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11075 init_test(cx, |_| {});
11076
11077 let mut cx = EditorTestContext::new(cx).await;
11078
11079 cx.set_state(indoc! {"
11080 fn func(abˇc def: i32) -> u32 {
11081 }
11082 "});
11083 let lsp_store =
11084 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11085
11086 cx.update(|_, cx| {
11087 lsp_store.update(cx, |lsp_store, cx| {
11088 lsp_store.update_diagnostics(
11089 LanguageServerId(0),
11090 lsp::PublishDiagnosticsParams {
11091 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11092 version: None,
11093 diagnostics: vec![lsp::Diagnostic {
11094 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11095 severity: Some(lsp::DiagnosticSeverity::ERROR),
11096 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11097 ..Default::default()
11098 }],
11099 },
11100 &[],
11101 cx,
11102 )
11103 })
11104 }).unwrap();
11105 cx.run_until_parked();
11106 cx.update_editor(|editor, window, cx| {
11107 hover_popover::hover(editor, &Default::default(), window, cx)
11108 });
11109 cx.run_until_parked();
11110 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11111}
11112
11113#[gpui::test]
11114async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11115 init_test(cx, |_| {});
11116
11117 let mut cx = EditorTestContext::new(cx).await;
11118
11119 let diff_base = r#"
11120 use some::mod;
11121
11122 const A: u32 = 42;
11123
11124 fn main() {
11125 println!("hello");
11126
11127 println!("world");
11128 }
11129 "#
11130 .unindent();
11131
11132 // Edits are modified, removed, modified, added
11133 cx.set_state(
11134 &r#"
11135 use some::modified;
11136
11137 ˇ
11138 fn main() {
11139 println!("hello there");
11140
11141 println!("around the");
11142 println!("world");
11143 }
11144 "#
11145 .unindent(),
11146 );
11147
11148 cx.set_head_text(&diff_base);
11149 executor.run_until_parked();
11150
11151 cx.update_editor(|editor, window, cx| {
11152 //Wrap around the bottom of the buffer
11153 for _ in 0..3 {
11154 editor.go_to_next_hunk(&GoToHunk, window, cx);
11155 }
11156 });
11157
11158 cx.assert_editor_state(
11159 &r#"
11160 ˇuse some::modified;
11161
11162
11163 fn main() {
11164 println!("hello there");
11165
11166 println!("around the");
11167 println!("world");
11168 }
11169 "#
11170 .unindent(),
11171 );
11172
11173 cx.update_editor(|editor, window, cx| {
11174 //Wrap around the top of the buffer
11175 for _ in 0..2 {
11176 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11177 }
11178 });
11179
11180 cx.assert_editor_state(
11181 &r#"
11182 use some::modified;
11183
11184
11185 fn main() {
11186 ˇ println!("hello there");
11187
11188 println!("around the");
11189 println!("world");
11190 }
11191 "#
11192 .unindent(),
11193 );
11194
11195 cx.update_editor(|editor, window, cx| {
11196 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11197 });
11198
11199 cx.assert_editor_state(
11200 &r#"
11201 use some::modified;
11202
11203 ˇ
11204 fn main() {
11205 println!("hello there");
11206
11207 println!("around the");
11208 println!("world");
11209 }
11210 "#
11211 .unindent(),
11212 );
11213
11214 cx.update_editor(|editor, window, cx| {
11215 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11216 });
11217
11218 cx.assert_editor_state(
11219 &r#"
11220 ˇuse some::modified;
11221
11222
11223 fn main() {
11224 println!("hello there");
11225
11226 println!("around the");
11227 println!("world");
11228 }
11229 "#
11230 .unindent(),
11231 );
11232
11233 cx.update_editor(|editor, window, cx| {
11234 for _ in 0..2 {
11235 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11236 }
11237 });
11238
11239 cx.assert_editor_state(
11240 &r#"
11241 use some::modified;
11242
11243
11244 fn main() {
11245 ˇ println!("hello there");
11246
11247 println!("around the");
11248 println!("world");
11249 }
11250 "#
11251 .unindent(),
11252 );
11253
11254 cx.update_editor(|editor, window, cx| {
11255 editor.fold(&Fold, window, cx);
11256 });
11257
11258 cx.update_editor(|editor, window, cx| {
11259 editor.go_to_next_hunk(&GoToHunk, window, cx);
11260 });
11261
11262 cx.assert_editor_state(
11263 &r#"
11264 ˇuse some::modified;
11265
11266
11267 fn main() {
11268 println!("hello there");
11269
11270 println!("around the");
11271 println!("world");
11272 }
11273 "#
11274 .unindent(),
11275 );
11276}
11277
11278#[test]
11279fn test_split_words() {
11280 fn split(text: &str) -> Vec<&str> {
11281 split_words(text).collect()
11282 }
11283
11284 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11285 assert_eq!(split("hello_world"), &["hello_", "world"]);
11286 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11287 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11288 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11289 assert_eq!(split("helloworld"), &["helloworld"]);
11290
11291 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11292}
11293
11294#[gpui::test]
11295async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11296 init_test(cx, |_| {});
11297
11298 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11299 let mut assert = |before, after| {
11300 let _state_context = cx.set_state(before);
11301 cx.run_until_parked();
11302 cx.update_editor(|editor, window, cx| {
11303 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11304 });
11305 cx.assert_editor_state(after);
11306 };
11307
11308 // Outside bracket jumps to outside of matching bracket
11309 assert("console.logˇ(var);", "console.log(var)ˇ;");
11310 assert("console.log(var)ˇ;", "console.logˇ(var);");
11311
11312 // Inside bracket jumps to inside of matching bracket
11313 assert("console.log(ˇvar);", "console.log(varˇ);");
11314 assert("console.log(varˇ);", "console.log(ˇvar);");
11315
11316 // When outside a bracket and inside, favor jumping to the inside bracket
11317 assert(
11318 "console.log('foo', [1, 2, 3]ˇ);",
11319 "console.log(ˇ'foo', [1, 2, 3]);",
11320 );
11321 assert(
11322 "console.log(ˇ'foo', [1, 2, 3]);",
11323 "console.log('foo', [1, 2, 3]ˇ);",
11324 );
11325
11326 // Bias forward if two options are equally likely
11327 assert(
11328 "let result = curried_fun()ˇ();",
11329 "let result = curried_fun()()ˇ;",
11330 );
11331
11332 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11333 assert(
11334 indoc! {"
11335 function test() {
11336 console.log('test')ˇ
11337 }"},
11338 indoc! {"
11339 function test() {
11340 console.logˇ('test')
11341 }"},
11342 );
11343}
11344
11345#[gpui::test]
11346async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11347 init_test(cx, |_| {});
11348
11349 let fs = FakeFs::new(cx.executor());
11350 fs.insert_tree(
11351 path!("/a"),
11352 json!({
11353 "main.rs": "fn main() { let a = 5; }",
11354 "other.rs": "// Test file",
11355 }),
11356 )
11357 .await;
11358 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11359
11360 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11361 language_registry.add(Arc::new(Language::new(
11362 LanguageConfig {
11363 name: "Rust".into(),
11364 matcher: LanguageMatcher {
11365 path_suffixes: vec!["rs".to_string()],
11366 ..Default::default()
11367 },
11368 brackets: BracketPairConfig {
11369 pairs: vec![BracketPair {
11370 start: "{".to_string(),
11371 end: "}".to_string(),
11372 close: true,
11373 surround: true,
11374 newline: true,
11375 }],
11376 disabled_scopes_by_bracket_ix: Vec::new(),
11377 },
11378 ..Default::default()
11379 },
11380 Some(tree_sitter_rust::LANGUAGE.into()),
11381 )));
11382 let mut fake_servers = language_registry.register_fake_lsp(
11383 "Rust",
11384 FakeLspAdapter {
11385 capabilities: lsp::ServerCapabilities {
11386 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11387 first_trigger_character: "{".to_string(),
11388 more_trigger_character: None,
11389 }),
11390 ..Default::default()
11391 },
11392 ..Default::default()
11393 },
11394 );
11395
11396 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11397
11398 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11399
11400 let worktree_id = workspace
11401 .update(cx, |workspace, _, cx| {
11402 workspace.project().update(cx, |project, cx| {
11403 project.worktrees(cx).next().unwrap().read(cx).id()
11404 })
11405 })
11406 .unwrap();
11407
11408 let buffer = project
11409 .update(cx, |project, cx| {
11410 project.open_local_buffer(path!("/a/main.rs"), cx)
11411 })
11412 .await
11413 .unwrap();
11414 let editor_handle = workspace
11415 .update(cx, |workspace, window, cx| {
11416 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11417 })
11418 .unwrap()
11419 .await
11420 .unwrap()
11421 .downcast::<Editor>()
11422 .unwrap();
11423
11424 cx.executor().start_waiting();
11425 let fake_server = fake_servers.next().await.unwrap();
11426
11427 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11428 assert_eq!(
11429 params.text_document_position.text_document.uri,
11430 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11431 );
11432 assert_eq!(
11433 params.text_document_position.position,
11434 lsp::Position::new(0, 21),
11435 );
11436
11437 Ok(Some(vec![lsp::TextEdit {
11438 new_text: "]".to_string(),
11439 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11440 }]))
11441 });
11442
11443 editor_handle.update_in(cx, |editor, window, cx| {
11444 window.focus(&editor.focus_handle(cx));
11445 editor.change_selections(None, window, cx, |s| {
11446 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11447 });
11448 editor.handle_input("{", window, cx);
11449 });
11450
11451 cx.executor().run_until_parked();
11452
11453 buffer.update(cx, |buffer, _| {
11454 assert_eq!(
11455 buffer.text(),
11456 "fn main() { let a = {5}; }",
11457 "No extra braces from on type formatting should appear in the buffer"
11458 )
11459 });
11460}
11461
11462#[gpui::test]
11463async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11464 init_test(cx, |_| {});
11465
11466 let fs = FakeFs::new(cx.executor());
11467 fs.insert_tree(
11468 path!("/a"),
11469 json!({
11470 "main.rs": "fn main() { let a = 5; }",
11471 "other.rs": "// Test file",
11472 }),
11473 )
11474 .await;
11475
11476 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11477
11478 let server_restarts = Arc::new(AtomicUsize::new(0));
11479 let closure_restarts = Arc::clone(&server_restarts);
11480 let language_server_name = "test language server";
11481 let language_name: LanguageName = "Rust".into();
11482
11483 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11484 language_registry.add(Arc::new(Language::new(
11485 LanguageConfig {
11486 name: language_name.clone(),
11487 matcher: LanguageMatcher {
11488 path_suffixes: vec!["rs".to_string()],
11489 ..Default::default()
11490 },
11491 ..Default::default()
11492 },
11493 Some(tree_sitter_rust::LANGUAGE.into()),
11494 )));
11495 let mut fake_servers = language_registry.register_fake_lsp(
11496 "Rust",
11497 FakeLspAdapter {
11498 name: language_server_name,
11499 initialization_options: Some(json!({
11500 "testOptionValue": true
11501 })),
11502 initializer: Some(Box::new(move |fake_server| {
11503 let task_restarts = Arc::clone(&closure_restarts);
11504 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11505 task_restarts.fetch_add(1, atomic::Ordering::Release);
11506 futures::future::ready(Ok(()))
11507 });
11508 })),
11509 ..Default::default()
11510 },
11511 );
11512
11513 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11514 let _buffer = project
11515 .update(cx, |project, cx| {
11516 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11517 })
11518 .await
11519 .unwrap();
11520 let _fake_server = fake_servers.next().await.unwrap();
11521 update_test_language_settings(cx, |language_settings| {
11522 language_settings.languages.insert(
11523 language_name.clone(),
11524 LanguageSettingsContent {
11525 tab_size: NonZeroU32::new(8),
11526 ..Default::default()
11527 },
11528 );
11529 });
11530 cx.executor().run_until_parked();
11531 assert_eq!(
11532 server_restarts.load(atomic::Ordering::Acquire),
11533 0,
11534 "Should not restart LSP server on an unrelated change"
11535 );
11536
11537 update_test_project_settings(cx, |project_settings| {
11538 project_settings.lsp.insert(
11539 "Some other server name".into(),
11540 LspSettings {
11541 binary: None,
11542 settings: None,
11543 initialization_options: Some(json!({
11544 "some other init value": false
11545 })),
11546 },
11547 );
11548 });
11549 cx.executor().run_until_parked();
11550 assert_eq!(
11551 server_restarts.load(atomic::Ordering::Acquire),
11552 0,
11553 "Should not restart LSP server on an unrelated LSP settings change"
11554 );
11555
11556 update_test_project_settings(cx, |project_settings| {
11557 project_settings.lsp.insert(
11558 language_server_name.into(),
11559 LspSettings {
11560 binary: None,
11561 settings: None,
11562 initialization_options: Some(json!({
11563 "anotherInitValue": false
11564 })),
11565 },
11566 );
11567 });
11568 cx.executor().run_until_parked();
11569 assert_eq!(
11570 server_restarts.load(atomic::Ordering::Acquire),
11571 1,
11572 "Should restart LSP server on a related LSP settings change"
11573 );
11574
11575 update_test_project_settings(cx, |project_settings| {
11576 project_settings.lsp.insert(
11577 language_server_name.into(),
11578 LspSettings {
11579 binary: None,
11580 settings: None,
11581 initialization_options: Some(json!({
11582 "anotherInitValue": false
11583 })),
11584 },
11585 );
11586 });
11587 cx.executor().run_until_parked();
11588 assert_eq!(
11589 server_restarts.load(atomic::Ordering::Acquire),
11590 1,
11591 "Should not restart LSP server on a related LSP settings change that is the same"
11592 );
11593
11594 update_test_project_settings(cx, |project_settings| {
11595 project_settings.lsp.insert(
11596 language_server_name.into(),
11597 LspSettings {
11598 binary: None,
11599 settings: None,
11600 initialization_options: None,
11601 },
11602 );
11603 });
11604 cx.executor().run_until_parked();
11605 assert_eq!(
11606 server_restarts.load(atomic::Ordering::Acquire),
11607 2,
11608 "Should restart LSP server on another related LSP settings change"
11609 );
11610}
11611
11612#[gpui::test]
11613async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11614 init_test(cx, |_| {});
11615
11616 let mut cx = EditorLspTestContext::new_rust(
11617 lsp::ServerCapabilities {
11618 completion_provider: Some(lsp::CompletionOptions {
11619 trigger_characters: Some(vec![".".to_string()]),
11620 resolve_provider: Some(true),
11621 ..Default::default()
11622 }),
11623 ..Default::default()
11624 },
11625 cx,
11626 )
11627 .await;
11628
11629 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11630 cx.simulate_keystroke(".");
11631 let completion_item = lsp::CompletionItem {
11632 label: "some".into(),
11633 kind: Some(lsp::CompletionItemKind::SNIPPET),
11634 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11635 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11636 kind: lsp::MarkupKind::Markdown,
11637 value: "```rust\nSome(2)\n```".to_string(),
11638 })),
11639 deprecated: Some(false),
11640 sort_text: Some("fffffff2".to_string()),
11641 filter_text: Some("some".to_string()),
11642 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11643 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11644 range: lsp::Range {
11645 start: lsp::Position {
11646 line: 0,
11647 character: 22,
11648 },
11649 end: lsp::Position {
11650 line: 0,
11651 character: 22,
11652 },
11653 },
11654 new_text: "Some(2)".to_string(),
11655 })),
11656 additional_text_edits: Some(vec![lsp::TextEdit {
11657 range: lsp::Range {
11658 start: lsp::Position {
11659 line: 0,
11660 character: 20,
11661 },
11662 end: lsp::Position {
11663 line: 0,
11664 character: 22,
11665 },
11666 },
11667 new_text: "".to_string(),
11668 }]),
11669 ..Default::default()
11670 };
11671
11672 let closure_completion_item = completion_item.clone();
11673 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11674 let task_completion_item = closure_completion_item.clone();
11675 async move {
11676 Ok(Some(lsp::CompletionResponse::Array(vec![
11677 task_completion_item,
11678 ])))
11679 }
11680 });
11681
11682 request.next().await;
11683
11684 cx.condition(|editor, _| editor.context_menu_visible())
11685 .await;
11686 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11687 editor
11688 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11689 .unwrap()
11690 });
11691 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11692
11693 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11694 let task_completion_item = completion_item.clone();
11695 async move { Ok(task_completion_item) }
11696 })
11697 .next()
11698 .await
11699 .unwrap();
11700 apply_additional_edits.await.unwrap();
11701 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11702}
11703
11704#[gpui::test]
11705async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11706 init_test(cx, |_| {});
11707
11708 let mut cx = EditorLspTestContext::new_rust(
11709 lsp::ServerCapabilities {
11710 completion_provider: Some(lsp::CompletionOptions {
11711 trigger_characters: Some(vec![".".to_string()]),
11712 resolve_provider: Some(true),
11713 ..Default::default()
11714 }),
11715 ..Default::default()
11716 },
11717 cx,
11718 )
11719 .await;
11720
11721 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11722 cx.simulate_keystroke(".");
11723
11724 let item1 = lsp::CompletionItem {
11725 label: "method id()".to_string(),
11726 filter_text: Some("id".to_string()),
11727 detail: None,
11728 documentation: None,
11729 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11730 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11731 new_text: ".id".to_string(),
11732 })),
11733 ..lsp::CompletionItem::default()
11734 };
11735
11736 let item2 = lsp::CompletionItem {
11737 label: "other".to_string(),
11738 filter_text: Some("other".to_string()),
11739 detail: None,
11740 documentation: None,
11741 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11742 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11743 new_text: ".other".to_string(),
11744 })),
11745 ..lsp::CompletionItem::default()
11746 };
11747
11748 let item1 = item1.clone();
11749 cx.handle_request::<lsp::request::Completion, _, _>({
11750 let item1 = item1.clone();
11751 move |_, _, _| {
11752 let item1 = item1.clone();
11753 let item2 = item2.clone();
11754 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11755 }
11756 })
11757 .next()
11758 .await;
11759
11760 cx.condition(|editor, _| editor.context_menu_visible())
11761 .await;
11762 cx.update_editor(|editor, _, _| {
11763 let context_menu = editor.context_menu.borrow_mut();
11764 let context_menu = context_menu
11765 .as_ref()
11766 .expect("Should have the context menu deployed");
11767 match context_menu {
11768 CodeContextMenu::Completions(completions_menu) => {
11769 let completions = completions_menu.completions.borrow_mut();
11770 assert_eq!(
11771 completions
11772 .iter()
11773 .map(|completion| &completion.label.text)
11774 .collect::<Vec<_>>(),
11775 vec!["method id()", "other"]
11776 )
11777 }
11778 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11779 }
11780 });
11781
11782 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11783 let item1 = item1.clone();
11784 move |_, item_to_resolve, _| {
11785 let item1 = item1.clone();
11786 async move {
11787 if item1 == item_to_resolve {
11788 Ok(lsp::CompletionItem {
11789 label: "method id()".to_string(),
11790 filter_text: Some("id".to_string()),
11791 detail: Some("Now resolved!".to_string()),
11792 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11793 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11794 range: lsp::Range::new(
11795 lsp::Position::new(0, 22),
11796 lsp::Position::new(0, 22),
11797 ),
11798 new_text: ".id".to_string(),
11799 })),
11800 ..lsp::CompletionItem::default()
11801 })
11802 } else {
11803 Ok(item_to_resolve)
11804 }
11805 }
11806 }
11807 })
11808 .next()
11809 .await
11810 .unwrap();
11811 cx.run_until_parked();
11812
11813 cx.update_editor(|editor, window, cx| {
11814 editor.context_menu_next(&Default::default(), window, cx);
11815 });
11816
11817 cx.update_editor(|editor, _, _| {
11818 let context_menu = editor.context_menu.borrow_mut();
11819 let context_menu = context_menu
11820 .as_ref()
11821 .expect("Should have the context menu deployed");
11822 match context_menu {
11823 CodeContextMenu::Completions(completions_menu) => {
11824 let completions = completions_menu.completions.borrow_mut();
11825 assert_eq!(
11826 completions
11827 .iter()
11828 .map(|completion| &completion.label.text)
11829 .collect::<Vec<_>>(),
11830 vec!["method id() Now resolved!", "other"],
11831 "Should update first completion label, but not second as the filter text did not match."
11832 );
11833 }
11834 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11835 }
11836 });
11837}
11838
11839#[gpui::test]
11840async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
11841 init_test(cx, |_| {});
11842
11843 let mut cx = EditorLspTestContext::new_rust(
11844 lsp::ServerCapabilities {
11845 completion_provider: Some(lsp::CompletionOptions {
11846 trigger_characters: Some(vec![".".to_string()]),
11847 resolve_provider: Some(true),
11848 ..Default::default()
11849 }),
11850 ..Default::default()
11851 },
11852 cx,
11853 )
11854 .await;
11855
11856 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11857 cx.simulate_keystroke(".");
11858
11859 let unresolved_item_1 = lsp::CompletionItem {
11860 label: "id".to_string(),
11861 filter_text: Some("id".to_string()),
11862 detail: None,
11863 documentation: None,
11864 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11865 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11866 new_text: ".id".to_string(),
11867 })),
11868 ..lsp::CompletionItem::default()
11869 };
11870 let resolved_item_1 = lsp::CompletionItem {
11871 additional_text_edits: Some(vec![lsp::TextEdit {
11872 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11873 new_text: "!!".to_string(),
11874 }]),
11875 ..unresolved_item_1.clone()
11876 };
11877 let unresolved_item_2 = lsp::CompletionItem {
11878 label: "other".to_string(),
11879 filter_text: Some("other".to_string()),
11880 detail: None,
11881 documentation: None,
11882 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11883 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11884 new_text: ".other".to_string(),
11885 })),
11886 ..lsp::CompletionItem::default()
11887 };
11888 let resolved_item_2 = lsp::CompletionItem {
11889 additional_text_edits: Some(vec![lsp::TextEdit {
11890 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11891 new_text: "??".to_string(),
11892 }]),
11893 ..unresolved_item_2.clone()
11894 };
11895
11896 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11897 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11898 cx.lsp
11899 .server
11900 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11901 let unresolved_item_1 = unresolved_item_1.clone();
11902 let resolved_item_1 = resolved_item_1.clone();
11903 let unresolved_item_2 = unresolved_item_2.clone();
11904 let resolved_item_2 = resolved_item_2.clone();
11905 let resolve_requests_1 = resolve_requests_1.clone();
11906 let resolve_requests_2 = resolve_requests_2.clone();
11907 move |unresolved_request, _| {
11908 let unresolved_item_1 = unresolved_item_1.clone();
11909 let resolved_item_1 = resolved_item_1.clone();
11910 let unresolved_item_2 = unresolved_item_2.clone();
11911 let resolved_item_2 = resolved_item_2.clone();
11912 let resolve_requests_1 = resolve_requests_1.clone();
11913 let resolve_requests_2 = resolve_requests_2.clone();
11914 async move {
11915 if unresolved_request == unresolved_item_1 {
11916 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11917 Ok(resolved_item_1.clone())
11918 } else if unresolved_request == unresolved_item_2 {
11919 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11920 Ok(resolved_item_2.clone())
11921 } else {
11922 panic!("Unexpected completion item {unresolved_request:?}")
11923 }
11924 }
11925 }
11926 })
11927 .detach();
11928
11929 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11930 let unresolved_item_1 = unresolved_item_1.clone();
11931 let unresolved_item_2 = unresolved_item_2.clone();
11932 async move {
11933 Ok(Some(lsp::CompletionResponse::Array(vec![
11934 unresolved_item_1,
11935 unresolved_item_2,
11936 ])))
11937 }
11938 })
11939 .next()
11940 .await;
11941
11942 cx.condition(|editor, _| editor.context_menu_visible())
11943 .await;
11944 cx.update_editor(|editor, _, _| {
11945 let context_menu = editor.context_menu.borrow_mut();
11946 let context_menu = context_menu
11947 .as_ref()
11948 .expect("Should have the context menu deployed");
11949 match context_menu {
11950 CodeContextMenu::Completions(completions_menu) => {
11951 let completions = completions_menu.completions.borrow_mut();
11952 assert_eq!(
11953 completions
11954 .iter()
11955 .map(|completion| &completion.label.text)
11956 .collect::<Vec<_>>(),
11957 vec!["id", "other"]
11958 )
11959 }
11960 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11961 }
11962 });
11963 cx.run_until_parked();
11964
11965 cx.update_editor(|editor, window, cx| {
11966 editor.context_menu_next(&ContextMenuNext, window, cx);
11967 });
11968 cx.run_until_parked();
11969 cx.update_editor(|editor, window, cx| {
11970 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11971 });
11972 cx.run_until_parked();
11973 cx.update_editor(|editor, window, cx| {
11974 editor.context_menu_next(&ContextMenuNext, window, cx);
11975 });
11976 cx.run_until_parked();
11977 cx.update_editor(|editor, window, cx| {
11978 editor
11979 .compose_completion(&ComposeCompletion::default(), window, cx)
11980 .expect("No task returned")
11981 })
11982 .await
11983 .expect("Completion failed");
11984 cx.run_until_parked();
11985
11986 cx.update_editor(|editor, _, cx| {
11987 assert_eq!(
11988 resolve_requests_1.load(atomic::Ordering::Acquire),
11989 1,
11990 "Should always resolve once despite multiple selections"
11991 );
11992 assert_eq!(
11993 resolve_requests_2.load(atomic::Ordering::Acquire),
11994 1,
11995 "Should always resolve once after multiple selections and applying the completion"
11996 );
11997 assert_eq!(
11998 editor.text(cx),
11999 "fn main() { let a = ??.other; }",
12000 "Should use resolved data when applying the completion"
12001 );
12002 });
12003}
12004
12005#[gpui::test]
12006async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12007 init_test(cx, |_| {});
12008
12009 let item_0 = lsp::CompletionItem {
12010 label: "abs".into(),
12011 insert_text: Some("abs".into()),
12012 data: Some(json!({ "very": "special"})),
12013 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12014 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12015 lsp::InsertReplaceEdit {
12016 new_text: "abs".to_string(),
12017 insert: lsp::Range::default(),
12018 replace: lsp::Range::default(),
12019 },
12020 )),
12021 ..lsp::CompletionItem::default()
12022 };
12023 let items = iter::once(item_0.clone())
12024 .chain((11..51).map(|i| lsp::CompletionItem {
12025 label: format!("item_{}", i),
12026 insert_text: Some(format!("item_{}", i)),
12027 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12028 ..lsp::CompletionItem::default()
12029 }))
12030 .collect::<Vec<_>>();
12031
12032 let default_commit_characters = vec!["?".to_string()];
12033 let default_data = json!({ "default": "data"});
12034 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12035 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12036 let default_edit_range = lsp::Range {
12037 start: lsp::Position {
12038 line: 0,
12039 character: 5,
12040 },
12041 end: lsp::Position {
12042 line: 0,
12043 character: 5,
12044 },
12045 };
12046
12047 let item_0_out = lsp::CompletionItem {
12048 commit_characters: Some(default_commit_characters.clone()),
12049 insert_text_format: Some(default_insert_text_format),
12050 ..item_0
12051 };
12052 let items_out = iter::once(item_0_out)
12053 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
12054 commit_characters: Some(default_commit_characters.clone()),
12055 data: Some(default_data.clone()),
12056 insert_text_mode: Some(default_insert_text_mode),
12057 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12058 range: default_edit_range,
12059 new_text: item.label.clone(),
12060 })),
12061 ..item.clone()
12062 }))
12063 .collect::<Vec<lsp::CompletionItem>>();
12064
12065 let mut cx = EditorLspTestContext::new_rust(
12066 lsp::ServerCapabilities {
12067 completion_provider: Some(lsp::CompletionOptions {
12068 trigger_characters: Some(vec![".".to_string()]),
12069 resolve_provider: Some(true),
12070 ..Default::default()
12071 }),
12072 ..Default::default()
12073 },
12074 cx,
12075 )
12076 .await;
12077
12078 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12079 cx.simulate_keystroke(".");
12080
12081 let completion_data = default_data.clone();
12082 let completion_characters = default_commit_characters.clone();
12083 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12084 let default_data = completion_data.clone();
12085 let default_commit_characters = completion_characters.clone();
12086 let items = items.clone();
12087 async move {
12088 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12089 items,
12090 item_defaults: Some(lsp::CompletionListItemDefaults {
12091 data: Some(default_data.clone()),
12092 commit_characters: Some(default_commit_characters.clone()),
12093 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12094 default_edit_range,
12095 )),
12096 insert_text_format: Some(default_insert_text_format),
12097 insert_text_mode: Some(default_insert_text_mode),
12098 }),
12099 ..lsp::CompletionList::default()
12100 })))
12101 }
12102 })
12103 .next()
12104 .await;
12105
12106 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12107 cx.lsp
12108 .server
12109 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12110 let closure_resolved_items = resolved_items.clone();
12111 move |item_to_resolve, _| {
12112 let closure_resolved_items = closure_resolved_items.clone();
12113 async move {
12114 closure_resolved_items.lock().push(item_to_resolve.clone());
12115 Ok(item_to_resolve)
12116 }
12117 }
12118 })
12119 .detach();
12120
12121 cx.condition(|editor, _| editor.context_menu_visible())
12122 .await;
12123 cx.run_until_parked();
12124 cx.update_editor(|editor, _, _| {
12125 let menu = editor.context_menu.borrow_mut();
12126 match menu.as_ref().expect("should have the completions menu") {
12127 CodeContextMenu::Completions(completions_menu) => {
12128 assert_eq!(
12129 completions_menu
12130 .entries
12131 .borrow()
12132 .iter()
12133 .map(|mat| mat.string.clone())
12134 .collect::<Vec<String>>(),
12135 items_out
12136 .iter()
12137 .map(|completion| completion.label.clone())
12138 .collect::<Vec<String>>()
12139 );
12140 }
12141 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12142 }
12143 });
12144 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12145 // with 4 from the end.
12146 assert_eq!(
12147 *resolved_items.lock(),
12148 [
12149 &items_out[0..16],
12150 &items_out[items_out.len() - 4..items_out.len()]
12151 ]
12152 .concat()
12153 .iter()
12154 .cloned()
12155 .collect::<Vec<lsp::CompletionItem>>()
12156 );
12157 resolved_items.lock().clear();
12158
12159 cx.update_editor(|editor, window, cx| {
12160 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12161 });
12162 cx.run_until_parked();
12163 // Completions that have already been resolved are skipped.
12164 assert_eq!(
12165 *resolved_items.lock(),
12166 items_out[items_out.len() - 16..items_out.len() - 4]
12167 .iter()
12168 .cloned()
12169 .collect::<Vec<lsp::CompletionItem>>()
12170 );
12171 resolved_items.lock().clear();
12172}
12173
12174#[gpui::test]
12175async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12176 init_test(cx, |_| {});
12177
12178 let mut cx = EditorLspTestContext::new(
12179 Language::new(
12180 LanguageConfig {
12181 matcher: LanguageMatcher {
12182 path_suffixes: vec!["jsx".into()],
12183 ..Default::default()
12184 },
12185 overrides: [(
12186 "element".into(),
12187 LanguageConfigOverride {
12188 word_characters: Override::Set(['-'].into_iter().collect()),
12189 ..Default::default()
12190 },
12191 )]
12192 .into_iter()
12193 .collect(),
12194 ..Default::default()
12195 },
12196 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12197 )
12198 .with_override_query("(jsx_self_closing_element) @element")
12199 .unwrap(),
12200 lsp::ServerCapabilities {
12201 completion_provider: Some(lsp::CompletionOptions {
12202 trigger_characters: Some(vec![":".to_string()]),
12203 ..Default::default()
12204 }),
12205 ..Default::default()
12206 },
12207 cx,
12208 )
12209 .await;
12210
12211 cx.lsp
12212 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12213 Ok(Some(lsp::CompletionResponse::Array(vec![
12214 lsp::CompletionItem {
12215 label: "bg-blue".into(),
12216 ..Default::default()
12217 },
12218 lsp::CompletionItem {
12219 label: "bg-red".into(),
12220 ..Default::default()
12221 },
12222 lsp::CompletionItem {
12223 label: "bg-yellow".into(),
12224 ..Default::default()
12225 },
12226 ])))
12227 });
12228
12229 cx.set_state(r#"<p class="bgˇ" />"#);
12230
12231 // Trigger completion when typing a dash, because the dash is an extra
12232 // word character in the 'element' scope, which contains the cursor.
12233 cx.simulate_keystroke("-");
12234 cx.executor().run_until_parked();
12235 cx.update_editor(|editor, _, _| {
12236 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12237 {
12238 assert_eq!(
12239 completion_menu_entries(&menu),
12240 &["bg-red", "bg-blue", "bg-yellow"]
12241 );
12242 } else {
12243 panic!("expected completion menu to be open");
12244 }
12245 });
12246
12247 cx.simulate_keystroke("l");
12248 cx.executor().run_until_parked();
12249 cx.update_editor(|editor, _, _| {
12250 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12251 {
12252 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12253 } else {
12254 panic!("expected completion menu to be open");
12255 }
12256 });
12257
12258 // When filtering completions, consider the character after the '-' to
12259 // be the start of a subword.
12260 cx.set_state(r#"<p class="yelˇ" />"#);
12261 cx.simulate_keystroke("l");
12262 cx.executor().run_until_parked();
12263 cx.update_editor(|editor, _, _| {
12264 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12265 {
12266 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12267 } else {
12268 panic!("expected completion menu to be open");
12269 }
12270 });
12271}
12272
12273fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12274 let entries = menu.entries.borrow();
12275 entries.iter().map(|mat| mat.string.clone()).collect()
12276}
12277
12278#[gpui::test]
12279async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12280 init_test(cx, |settings| {
12281 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12282 FormatterList(vec![Formatter::Prettier].into()),
12283 ))
12284 });
12285
12286 let fs = FakeFs::new(cx.executor());
12287 fs.insert_file(path!("/file.ts"), Default::default()).await;
12288
12289 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12290 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12291
12292 language_registry.add(Arc::new(Language::new(
12293 LanguageConfig {
12294 name: "TypeScript".into(),
12295 matcher: LanguageMatcher {
12296 path_suffixes: vec!["ts".to_string()],
12297 ..Default::default()
12298 },
12299 ..Default::default()
12300 },
12301 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12302 )));
12303 update_test_language_settings(cx, |settings| {
12304 settings.defaults.prettier = Some(PrettierSettings {
12305 allowed: true,
12306 ..PrettierSettings::default()
12307 });
12308 });
12309
12310 let test_plugin = "test_plugin";
12311 let _ = language_registry.register_fake_lsp(
12312 "TypeScript",
12313 FakeLspAdapter {
12314 prettier_plugins: vec![test_plugin],
12315 ..Default::default()
12316 },
12317 );
12318
12319 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12320 let buffer = project
12321 .update(cx, |project, cx| {
12322 project.open_local_buffer(path!("/file.ts"), cx)
12323 })
12324 .await
12325 .unwrap();
12326
12327 let buffer_text = "one\ntwo\nthree\n";
12328 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12329 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12330 editor.update_in(cx, |editor, window, cx| {
12331 editor.set_text(buffer_text, window, cx)
12332 });
12333
12334 editor
12335 .update_in(cx, |editor, window, cx| {
12336 editor.perform_format(
12337 project.clone(),
12338 FormatTrigger::Manual,
12339 FormatTarget::Buffers,
12340 window,
12341 cx,
12342 )
12343 })
12344 .unwrap()
12345 .await;
12346 assert_eq!(
12347 editor.update(cx, |editor, cx| editor.text(cx)),
12348 buffer_text.to_string() + prettier_format_suffix,
12349 "Test prettier formatting was not applied to the original buffer text",
12350 );
12351
12352 update_test_language_settings(cx, |settings| {
12353 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12354 });
12355 let format = editor.update_in(cx, |editor, window, cx| {
12356 editor.perform_format(
12357 project.clone(),
12358 FormatTrigger::Manual,
12359 FormatTarget::Buffers,
12360 window,
12361 cx,
12362 )
12363 });
12364 format.await.unwrap();
12365 assert_eq!(
12366 editor.update(cx, |editor, cx| editor.text(cx)),
12367 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12368 "Autoformatting (via test prettier) was not applied to the original buffer text",
12369 );
12370}
12371
12372#[gpui::test]
12373async fn test_addition_reverts(cx: &mut TestAppContext) {
12374 init_test(cx, |_| {});
12375 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12376 let base_text = indoc! {r#"
12377 struct Row;
12378 struct Row1;
12379 struct Row2;
12380
12381 struct Row4;
12382 struct Row5;
12383 struct Row6;
12384
12385 struct Row8;
12386 struct Row9;
12387 struct Row10;"#};
12388
12389 // When addition hunks are not adjacent to carets, no hunk revert is performed
12390 assert_hunk_revert(
12391 indoc! {r#"struct Row;
12392 struct Row1;
12393 struct Row1.1;
12394 struct Row1.2;
12395 struct Row2;ˇ
12396
12397 struct Row4;
12398 struct Row5;
12399 struct Row6;
12400
12401 struct Row8;
12402 ˇstruct Row9;
12403 struct Row9.1;
12404 struct Row9.2;
12405 struct Row9.3;
12406 struct Row10;"#},
12407 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12408 indoc! {r#"struct Row;
12409 struct Row1;
12410 struct Row1.1;
12411 struct Row1.2;
12412 struct Row2;ˇ
12413
12414 struct Row4;
12415 struct Row5;
12416 struct Row6;
12417
12418 struct Row8;
12419 ˇstruct Row9;
12420 struct Row9.1;
12421 struct Row9.2;
12422 struct Row9.3;
12423 struct Row10;"#},
12424 base_text,
12425 &mut cx,
12426 );
12427 // Same for selections
12428 assert_hunk_revert(
12429 indoc! {r#"struct Row;
12430 struct Row1;
12431 struct Row2;
12432 struct Row2.1;
12433 struct Row2.2;
12434 «ˇ
12435 struct Row4;
12436 struct» Row5;
12437 «struct Row6;
12438 ˇ»
12439 struct Row9.1;
12440 struct Row9.2;
12441 struct Row9.3;
12442 struct Row8;
12443 struct Row9;
12444 struct Row10;"#},
12445 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12446 indoc! {r#"struct Row;
12447 struct Row1;
12448 struct Row2;
12449 struct Row2.1;
12450 struct Row2.2;
12451 «ˇ
12452 struct Row4;
12453 struct» Row5;
12454 «struct Row6;
12455 ˇ»
12456 struct Row9.1;
12457 struct Row9.2;
12458 struct Row9.3;
12459 struct Row8;
12460 struct Row9;
12461 struct Row10;"#},
12462 base_text,
12463 &mut cx,
12464 );
12465
12466 // When carets and selections intersect the addition hunks, those are reverted.
12467 // Adjacent carets got merged.
12468 assert_hunk_revert(
12469 indoc! {r#"struct Row;
12470 ˇ// something on the top
12471 struct Row1;
12472 struct Row2;
12473 struct Roˇw3.1;
12474 struct Row2.2;
12475 struct Row2.3;ˇ
12476
12477 struct Row4;
12478 struct ˇRow5.1;
12479 struct Row5.2;
12480 struct «Rowˇ»5.3;
12481 struct Row5;
12482 struct Row6;
12483 ˇ
12484 struct Row9.1;
12485 struct «Rowˇ»9.2;
12486 struct «ˇRow»9.3;
12487 struct Row8;
12488 struct Row9;
12489 «ˇ// something on bottom»
12490 struct Row10;"#},
12491 vec![
12492 DiffHunkStatus::added_none(),
12493 DiffHunkStatus::added_none(),
12494 DiffHunkStatus::added_none(),
12495 DiffHunkStatus::added_none(),
12496 DiffHunkStatus::added_none(),
12497 ],
12498 indoc! {r#"struct Row;
12499 ˇstruct Row1;
12500 struct Row2;
12501 ˇ
12502 struct Row4;
12503 ˇstruct Row5;
12504 struct Row6;
12505 ˇ
12506 ˇstruct Row8;
12507 struct Row9;
12508 ˇstruct Row10;"#},
12509 base_text,
12510 &mut cx,
12511 );
12512}
12513
12514#[gpui::test]
12515async fn test_modification_reverts(cx: &mut TestAppContext) {
12516 init_test(cx, |_| {});
12517 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12518 let base_text = indoc! {r#"
12519 struct Row;
12520 struct Row1;
12521 struct Row2;
12522
12523 struct Row4;
12524 struct Row5;
12525 struct Row6;
12526
12527 struct Row8;
12528 struct Row9;
12529 struct Row10;"#};
12530
12531 // Modification hunks behave the same as the addition ones.
12532 assert_hunk_revert(
12533 indoc! {r#"struct Row;
12534 struct Row1;
12535 struct Row33;
12536 ˇ
12537 struct Row4;
12538 struct Row5;
12539 struct Row6;
12540 ˇ
12541 struct Row99;
12542 struct Row9;
12543 struct Row10;"#},
12544 vec![
12545 DiffHunkStatus::modified_none(),
12546 DiffHunkStatus::modified_none(),
12547 ],
12548 indoc! {r#"struct Row;
12549 struct Row1;
12550 struct Row33;
12551 ˇ
12552 struct Row4;
12553 struct Row5;
12554 struct Row6;
12555 ˇ
12556 struct Row99;
12557 struct Row9;
12558 struct Row10;"#},
12559 base_text,
12560 &mut cx,
12561 );
12562 assert_hunk_revert(
12563 indoc! {r#"struct Row;
12564 struct Row1;
12565 struct Row33;
12566 «ˇ
12567 struct Row4;
12568 struct» Row5;
12569 «struct Row6;
12570 ˇ»
12571 struct Row99;
12572 struct Row9;
12573 struct Row10;"#},
12574 vec![
12575 DiffHunkStatus::modified_none(),
12576 DiffHunkStatus::modified_none(),
12577 ],
12578 indoc! {r#"struct Row;
12579 struct Row1;
12580 struct Row33;
12581 «ˇ
12582 struct Row4;
12583 struct» Row5;
12584 «struct Row6;
12585 ˇ»
12586 struct Row99;
12587 struct Row9;
12588 struct Row10;"#},
12589 base_text,
12590 &mut cx,
12591 );
12592
12593 assert_hunk_revert(
12594 indoc! {r#"ˇstruct Row1.1;
12595 struct Row1;
12596 «ˇstr»uct Row22;
12597
12598 struct ˇRow44;
12599 struct Row5;
12600 struct «Rˇ»ow66;ˇ
12601
12602 «struˇ»ct Row88;
12603 struct Row9;
12604 struct Row1011;ˇ"#},
12605 vec![
12606 DiffHunkStatus::modified_none(),
12607 DiffHunkStatus::modified_none(),
12608 DiffHunkStatus::modified_none(),
12609 DiffHunkStatus::modified_none(),
12610 DiffHunkStatus::modified_none(),
12611 DiffHunkStatus::modified_none(),
12612 ],
12613 indoc! {r#"struct Row;
12614 ˇstruct Row1;
12615 struct Row2;
12616 ˇ
12617 struct Row4;
12618 ˇstruct Row5;
12619 struct Row6;
12620 ˇ
12621 struct Row8;
12622 ˇstruct Row9;
12623 struct Row10;ˇ"#},
12624 base_text,
12625 &mut cx,
12626 );
12627}
12628
12629#[gpui::test]
12630async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12631 init_test(cx, |_| {});
12632 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12633 let base_text = indoc! {r#"
12634 one
12635
12636 two
12637 three
12638 "#};
12639
12640 cx.set_head_text(base_text);
12641 cx.set_state("\nˇ\n");
12642 cx.executor().run_until_parked();
12643 cx.update_editor(|editor, _window, cx| {
12644 editor.expand_selected_diff_hunks(cx);
12645 });
12646 cx.executor().run_until_parked();
12647 cx.update_editor(|editor, window, cx| {
12648 editor.backspace(&Default::default(), window, cx);
12649 });
12650 cx.run_until_parked();
12651 cx.assert_state_with_diff(
12652 indoc! {r#"
12653
12654 - two
12655 - threeˇ
12656 +
12657 "#}
12658 .to_string(),
12659 );
12660}
12661
12662#[gpui::test]
12663async fn test_deletion_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#"struct Row;
12667struct Row1;
12668struct Row2;
12669
12670struct Row4;
12671struct Row5;
12672struct Row6;
12673
12674struct Row8;
12675struct Row9;
12676struct Row10;"#};
12677
12678 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12679 assert_hunk_revert(
12680 indoc! {r#"struct Row;
12681 struct Row2;
12682
12683 ˇstruct Row4;
12684 struct Row5;
12685 struct Row6;
12686 ˇ
12687 struct Row8;
12688 struct Row10;"#},
12689 vec![
12690 DiffHunkStatus::deleted_none(),
12691 DiffHunkStatus::deleted_none(),
12692 ],
12693 indoc! {r#"struct Row;
12694 struct Row2;
12695
12696 ˇstruct Row4;
12697 struct Row5;
12698 struct Row6;
12699 ˇ
12700 struct Row8;
12701 struct Row10;"#},
12702 base_text,
12703 &mut cx,
12704 );
12705 assert_hunk_revert(
12706 indoc! {r#"struct Row;
12707 struct Row2;
12708
12709 «ˇstruct Row4;
12710 struct» Row5;
12711 «struct Row6;
12712 ˇ»
12713 struct Row8;
12714 struct Row10;"#},
12715 vec![
12716 DiffHunkStatus::deleted_none(),
12717 DiffHunkStatus::deleted_none(),
12718 ],
12719 indoc! {r#"struct Row;
12720 struct Row2;
12721
12722 «ˇstruct Row4;
12723 struct» Row5;
12724 «struct Row6;
12725 ˇ»
12726 struct Row8;
12727 struct Row10;"#},
12728 base_text,
12729 &mut cx,
12730 );
12731
12732 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12733 assert_hunk_revert(
12734 indoc! {r#"struct Row;
12735 ˇstruct Row2;
12736
12737 struct Row4;
12738 struct Row5;
12739 struct Row6;
12740
12741 struct Row8;ˇ
12742 struct Row10;"#},
12743 vec![
12744 DiffHunkStatus::deleted_none(),
12745 DiffHunkStatus::deleted_none(),
12746 ],
12747 indoc! {r#"struct Row;
12748 struct Row1;
12749 ˇstruct Row2;
12750
12751 struct Row4;
12752 struct Row5;
12753 struct Row6;
12754
12755 struct Row8;ˇ
12756 struct Row9;
12757 struct Row10;"#},
12758 base_text,
12759 &mut cx,
12760 );
12761 assert_hunk_revert(
12762 indoc! {r#"struct Row;
12763 struct Row2«ˇ;
12764 struct Row4;
12765 struct» Row5;
12766 «struct Row6;
12767
12768 struct Row8;ˇ»
12769 struct Row10;"#},
12770 vec![
12771 DiffHunkStatus::deleted_none(),
12772 DiffHunkStatus::deleted_none(),
12773 DiffHunkStatus::deleted_none(),
12774 ],
12775 indoc! {r#"struct Row;
12776 struct Row1;
12777 struct Row2«ˇ;
12778
12779 struct Row4;
12780 struct» Row5;
12781 «struct Row6;
12782
12783 struct Row8;ˇ»
12784 struct Row9;
12785 struct Row10;"#},
12786 base_text,
12787 &mut cx,
12788 );
12789}
12790
12791#[gpui::test]
12792async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
12793 init_test(cx, |_| {});
12794
12795 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12796 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12797 let base_text_3 =
12798 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12799
12800 let text_1 = edit_first_char_of_every_line(base_text_1);
12801 let text_2 = edit_first_char_of_every_line(base_text_2);
12802 let text_3 = edit_first_char_of_every_line(base_text_3);
12803
12804 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12805 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12806 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12807
12808 let multibuffer = cx.new(|cx| {
12809 let mut multibuffer = MultiBuffer::new(ReadWrite);
12810 multibuffer.push_excerpts(
12811 buffer_1.clone(),
12812 [
12813 ExcerptRange {
12814 context: Point::new(0, 0)..Point::new(3, 0),
12815 primary: None,
12816 },
12817 ExcerptRange {
12818 context: Point::new(5, 0)..Point::new(7, 0),
12819 primary: None,
12820 },
12821 ExcerptRange {
12822 context: Point::new(9, 0)..Point::new(10, 4),
12823 primary: None,
12824 },
12825 ],
12826 cx,
12827 );
12828 multibuffer.push_excerpts(
12829 buffer_2.clone(),
12830 [
12831 ExcerptRange {
12832 context: Point::new(0, 0)..Point::new(3, 0),
12833 primary: None,
12834 },
12835 ExcerptRange {
12836 context: Point::new(5, 0)..Point::new(7, 0),
12837 primary: None,
12838 },
12839 ExcerptRange {
12840 context: Point::new(9, 0)..Point::new(10, 4),
12841 primary: None,
12842 },
12843 ],
12844 cx,
12845 );
12846 multibuffer.push_excerpts(
12847 buffer_3.clone(),
12848 [
12849 ExcerptRange {
12850 context: Point::new(0, 0)..Point::new(3, 0),
12851 primary: None,
12852 },
12853 ExcerptRange {
12854 context: Point::new(5, 0)..Point::new(7, 0),
12855 primary: None,
12856 },
12857 ExcerptRange {
12858 context: Point::new(9, 0)..Point::new(10, 4),
12859 primary: None,
12860 },
12861 ],
12862 cx,
12863 );
12864 multibuffer
12865 });
12866
12867 let fs = FakeFs::new(cx.executor());
12868 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12869 let (editor, cx) = cx
12870 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
12871 editor.update_in(cx, |editor, _window, cx| {
12872 for (buffer, diff_base) in [
12873 (buffer_1.clone(), base_text_1),
12874 (buffer_2.clone(), base_text_2),
12875 (buffer_3.clone(), base_text_3),
12876 ] {
12877 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
12878 editor
12879 .buffer
12880 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
12881 }
12882 });
12883 cx.executor().run_until_parked();
12884
12885 editor.update_in(cx, |editor, window, cx| {
12886 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}");
12887 editor.select_all(&SelectAll, window, cx);
12888 editor.git_restore(&Default::default(), window, cx);
12889 });
12890 cx.executor().run_until_parked();
12891
12892 // When all ranges are selected, all buffer hunks are reverted.
12893 editor.update(cx, |editor, cx| {
12894 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");
12895 });
12896 buffer_1.update(cx, |buffer, _| {
12897 assert_eq!(buffer.text(), base_text_1);
12898 });
12899 buffer_2.update(cx, |buffer, _| {
12900 assert_eq!(buffer.text(), base_text_2);
12901 });
12902 buffer_3.update(cx, |buffer, _| {
12903 assert_eq!(buffer.text(), base_text_3);
12904 });
12905
12906 editor.update_in(cx, |editor, window, cx| {
12907 editor.undo(&Default::default(), window, cx);
12908 });
12909
12910 editor.update_in(cx, |editor, window, cx| {
12911 editor.change_selections(None, window, cx, |s| {
12912 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12913 });
12914 editor.git_restore(&Default::default(), window, cx);
12915 });
12916
12917 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12918 // but not affect buffer_2 and its related excerpts.
12919 editor.update(cx, |editor, cx| {
12920 assert_eq!(
12921 editor.text(cx),
12922 "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}"
12923 );
12924 });
12925 buffer_1.update(cx, |buffer, _| {
12926 assert_eq!(buffer.text(), base_text_1);
12927 });
12928 buffer_2.update(cx, |buffer, _| {
12929 assert_eq!(
12930 buffer.text(),
12931 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12932 );
12933 });
12934 buffer_3.update(cx, |buffer, _| {
12935 assert_eq!(
12936 buffer.text(),
12937 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12938 );
12939 });
12940
12941 fn edit_first_char_of_every_line(text: &str) -> String {
12942 text.split('\n')
12943 .map(|line| format!("X{}", &line[1..]))
12944 .collect::<Vec<_>>()
12945 .join("\n")
12946 }
12947}
12948
12949#[gpui::test]
12950async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
12951 init_test(cx, |_| {});
12952
12953 let cols = 4;
12954 let rows = 10;
12955 let sample_text_1 = sample_text(rows, cols, 'a');
12956 assert_eq!(
12957 sample_text_1,
12958 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12959 );
12960 let sample_text_2 = sample_text(rows, cols, 'l');
12961 assert_eq!(
12962 sample_text_2,
12963 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12964 );
12965 let sample_text_3 = sample_text(rows, cols, 'v');
12966 assert_eq!(
12967 sample_text_3,
12968 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12969 );
12970
12971 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12972 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12973 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12974
12975 let multi_buffer = cx.new(|cx| {
12976 let mut multibuffer = MultiBuffer::new(ReadWrite);
12977 multibuffer.push_excerpts(
12978 buffer_1.clone(),
12979 [
12980 ExcerptRange {
12981 context: Point::new(0, 0)..Point::new(3, 0),
12982 primary: None,
12983 },
12984 ExcerptRange {
12985 context: Point::new(5, 0)..Point::new(7, 0),
12986 primary: None,
12987 },
12988 ExcerptRange {
12989 context: Point::new(9, 0)..Point::new(10, 4),
12990 primary: None,
12991 },
12992 ],
12993 cx,
12994 );
12995 multibuffer.push_excerpts(
12996 buffer_2.clone(),
12997 [
12998 ExcerptRange {
12999 context: Point::new(0, 0)..Point::new(3, 0),
13000 primary: None,
13001 },
13002 ExcerptRange {
13003 context: Point::new(5, 0)..Point::new(7, 0),
13004 primary: None,
13005 },
13006 ExcerptRange {
13007 context: Point::new(9, 0)..Point::new(10, 4),
13008 primary: None,
13009 },
13010 ],
13011 cx,
13012 );
13013 multibuffer.push_excerpts(
13014 buffer_3.clone(),
13015 [
13016 ExcerptRange {
13017 context: Point::new(0, 0)..Point::new(3, 0),
13018 primary: None,
13019 },
13020 ExcerptRange {
13021 context: Point::new(5, 0)..Point::new(7, 0),
13022 primary: None,
13023 },
13024 ExcerptRange {
13025 context: Point::new(9, 0)..Point::new(10, 4),
13026 primary: None,
13027 },
13028 ],
13029 cx,
13030 );
13031 multibuffer
13032 });
13033
13034 let fs = FakeFs::new(cx.executor());
13035 fs.insert_tree(
13036 "/a",
13037 json!({
13038 "main.rs": sample_text_1,
13039 "other.rs": sample_text_2,
13040 "lib.rs": sample_text_3,
13041 }),
13042 )
13043 .await;
13044 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13045 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13046 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13047 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13048 Editor::new(
13049 EditorMode::Full,
13050 multi_buffer,
13051 Some(project.clone()),
13052 true,
13053 window,
13054 cx,
13055 )
13056 });
13057 let multibuffer_item_id = workspace
13058 .update(cx, |workspace, window, cx| {
13059 assert!(
13060 workspace.active_item(cx).is_none(),
13061 "active item should be None before the first item is added"
13062 );
13063 workspace.add_item_to_active_pane(
13064 Box::new(multi_buffer_editor.clone()),
13065 None,
13066 true,
13067 window,
13068 cx,
13069 );
13070 let active_item = workspace
13071 .active_item(cx)
13072 .expect("should have an active item after adding the multi buffer");
13073 assert!(
13074 !active_item.is_singleton(cx),
13075 "A multi buffer was expected to active after adding"
13076 );
13077 active_item.item_id()
13078 })
13079 .unwrap();
13080 cx.executor().run_until_parked();
13081
13082 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13083 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13084 s.select_ranges(Some(1..2))
13085 });
13086 editor.open_excerpts(&OpenExcerpts, window, cx);
13087 });
13088 cx.executor().run_until_parked();
13089 let first_item_id = workspace
13090 .update(cx, |workspace, window, cx| {
13091 let active_item = workspace
13092 .active_item(cx)
13093 .expect("should have an active item after navigating into the 1st buffer");
13094 let first_item_id = active_item.item_id();
13095 assert_ne!(
13096 first_item_id, multibuffer_item_id,
13097 "Should navigate into the 1st buffer and activate it"
13098 );
13099 assert!(
13100 active_item.is_singleton(cx),
13101 "New active item should be a singleton buffer"
13102 );
13103 assert_eq!(
13104 active_item
13105 .act_as::<Editor>(cx)
13106 .expect("should have navigated into an editor for the 1st buffer")
13107 .read(cx)
13108 .text(cx),
13109 sample_text_1
13110 );
13111
13112 workspace
13113 .go_back(workspace.active_pane().downgrade(), window, cx)
13114 .detach_and_log_err(cx);
13115
13116 first_item_id
13117 })
13118 .unwrap();
13119 cx.executor().run_until_parked();
13120 workspace
13121 .update(cx, |workspace, _, cx| {
13122 let active_item = workspace
13123 .active_item(cx)
13124 .expect("should have an active item after navigating back");
13125 assert_eq!(
13126 active_item.item_id(),
13127 multibuffer_item_id,
13128 "Should navigate back to the multi buffer"
13129 );
13130 assert!(!active_item.is_singleton(cx));
13131 })
13132 .unwrap();
13133
13134 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13135 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13136 s.select_ranges(Some(39..40))
13137 });
13138 editor.open_excerpts(&OpenExcerpts, window, cx);
13139 });
13140 cx.executor().run_until_parked();
13141 let second_item_id = workspace
13142 .update(cx, |workspace, window, cx| {
13143 let active_item = workspace
13144 .active_item(cx)
13145 .expect("should have an active item after navigating into the 2nd buffer");
13146 let second_item_id = active_item.item_id();
13147 assert_ne!(
13148 second_item_id, multibuffer_item_id,
13149 "Should navigate away from the multibuffer"
13150 );
13151 assert_ne!(
13152 second_item_id, first_item_id,
13153 "Should navigate into the 2nd buffer and activate it"
13154 );
13155 assert!(
13156 active_item.is_singleton(cx),
13157 "New active item should be a singleton buffer"
13158 );
13159 assert_eq!(
13160 active_item
13161 .act_as::<Editor>(cx)
13162 .expect("should have navigated into an editor")
13163 .read(cx)
13164 .text(cx),
13165 sample_text_2
13166 );
13167
13168 workspace
13169 .go_back(workspace.active_pane().downgrade(), window, cx)
13170 .detach_and_log_err(cx);
13171
13172 second_item_id
13173 })
13174 .unwrap();
13175 cx.executor().run_until_parked();
13176 workspace
13177 .update(cx, |workspace, _, cx| {
13178 let active_item = workspace
13179 .active_item(cx)
13180 .expect("should have an active item after navigating back from the 2nd buffer");
13181 assert_eq!(
13182 active_item.item_id(),
13183 multibuffer_item_id,
13184 "Should navigate back from the 2nd buffer to the multi buffer"
13185 );
13186 assert!(!active_item.is_singleton(cx));
13187 })
13188 .unwrap();
13189
13190 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13191 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13192 s.select_ranges(Some(70..70))
13193 });
13194 editor.open_excerpts(&OpenExcerpts, window, cx);
13195 });
13196 cx.executor().run_until_parked();
13197 workspace
13198 .update(cx, |workspace, window, cx| {
13199 let active_item = workspace
13200 .active_item(cx)
13201 .expect("should have an active item after navigating into the 3rd buffer");
13202 let third_item_id = active_item.item_id();
13203 assert_ne!(
13204 third_item_id, multibuffer_item_id,
13205 "Should navigate into the 3rd buffer and activate it"
13206 );
13207 assert_ne!(third_item_id, first_item_id);
13208 assert_ne!(third_item_id, second_item_id);
13209 assert!(
13210 active_item.is_singleton(cx),
13211 "New active item should be a singleton buffer"
13212 );
13213 assert_eq!(
13214 active_item
13215 .act_as::<Editor>(cx)
13216 .expect("should have navigated into an editor")
13217 .read(cx)
13218 .text(cx),
13219 sample_text_3
13220 );
13221
13222 workspace
13223 .go_back(workspace.active_pane().downgrade(), window, cx)
13224 .detach_and_log_err(cx);
13225 })
13226 .unwrap();
13227 cx.executor().run_until_parked();
13228 workspace
13229 .update(cx, |workspace, _, cx| {
13230 let active_item = workspace
13231 .active_item(cx)
13232 .expect("should have an active item after navigating back from the 3rd buffer");
13233 assert_eq!(
13234 active_item.item_id(),
13235 multibuffer_item_id,
13236 "Should navigate back from the 3rd buffer to the multi buffer"
13237 );
13238 assert!(!active_item.is_singleton(cx));
13239 })
13240 .unwrap();
13241}
13242
13243#[gpui::test]
13244async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13245 init_test(cx, |_| {});
13246
13247 let mut cx = EditorTestContext::new(cx).await;
13248
13249 let diff_base = r#"
13250 use some::mod;
13251
13252 const A: u32 = 42;
13253
13254 fn main() {
13255 println!("hello");
13256
13257 println!("world");
13258 }
13259 "#
13260 .unindent();
13261
13262 cx.set_state(
13263 &r#"
13264 use some::modified;
13265
13266 ˇ
13267 fn main() {
13268 println!("hello there");
13269
13270 println!("around the");
13271 println!("world");
13272 }
13273 "#
13274 .unindent(),
13275 );
13276
13277 cx.set_head_text(&diff_base);
13278 executor.run_until_parked();
13279
13280 cx.update_editor(|editor, window, cx| {
13281 editor.go_to_next_hunk(&GoToHunk, window, cx);
13282 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13283 });
13284 executor.run_until_parked();
13285 cx.assert_state_with_diff(
13286 r#"
13287 use some::modified;
13288
13289
13290 fn main() {
13291 - println!("hello");
13292 + ˇ println!("hello there");
13293
13294 println!("around the");
13295 println!("world");
13296 }
13297 "#
13298 .unindent(),
13299 );
13300
13301 cx.update_editor(|editor, window, cx| {
13302 for _ in 0..2 {
13303 editor.go_to_next_hunk(&GoToHunk, window, cx);
13304 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13305 }
13306 });
13307 executor.run_until_parked();
13308 cx.assert_state_with_diff(
13309 r#"
13310 - use some::mod;
13311 + ˇuse some::modified;
13312
13313
13314 fn main() {
13315 - println!("hello");
13316 + println!("hello there");
13317
13318 + println!("around the");
13319 println!("world");
13320 }
13321 "#
13322 .unindent(),
13323 );
13324
13325 cx.update_editor(|editor, window, cx| {
13326 editor.go_to_next_hunk(&GoToHunk, window, cx);
13327 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13328 });
13329 executor.run_until_parked();
13330 cx.assert_state_with_diff(
13331 r#"
13332 - use some::mod;
13333 + use some::modified;
13334
13335 - const A: u32 = 42;
13336 ˇ
13337 fn main() {
13338 - println!("hello");
13339 + println!("hello there");
13340
13341 + println!("around the");
13342 println!("world");
13343 }
13344 "#
13345 .unindent(),
13346 );
13347
13348 cx.update_editor(|editor, window, cx| {
13349 editor.cancel(&Cancel, window, cx);
13350 });
13351
13352 cx.assert_state_with_diff(
13353 r#"
13354 use some::modified;
13355
13356 ˇ
13357 fn main() {
13358 println!("hello there");
13359
13360 println!("around the");
13361 println!("world");
13362 }
13363 "#
13364 .unindent(),
13365 );
13366}
13367
13368#[gpui::test]
13369async fn test_diff_base_change_with_expanded_diff_hunks(
13370 executor: BackgroundExecutor,
13371 cx: &mut TestAppContext,
13372) {
13373 init_test(cx, |_| {});
13374
13375 let mut cx = EditorTestContext::new(cx).await;
13376
13377 let diff_base = r#"
13378 use some::mod1;
13379 use some::mod2;
13380
13381 const A: u32 = 42;
13382 const B: u32 = 42;
13383 const C: u32 = 42;
13384
13385 fn main() {
13386 println!("hello");
13387
13388 println!("world");
13389 }
13390 "#
13391 .unindent();
13392
13393 cx.set_state(
13394 &r#"
13395 use some::mod2;
13396
13397 const A: u32 = 42;
13398 const C: u32 = 42;
13399
13400 fn main(ˇ) {
13401 //println!("hello");
13402
13403 println!("world");
13404 //
13405 //
13406 }
13407 "#
13408 .unindent(),
13409 );
13410
13411 cx.set_head_text(&diff_base);
13412 executor.run_until_parked();
13413
13414 cx.update_editor(|editor, window, cx| {
13415 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13416 });
13417 executor.run_until_parked();
13418 cx.assert_state_with_diff(
13419 r#"
13420 - use some::mod1;
13421 use some::mod2;
13422
13423 const A: u32 = 42;
13424 - const B: u32 = 42;
13425 const C: u32 = 42;
13426
13427 fn main(ˇ) {
13428 - println!("hello");
13429 + //println!("hello");
13430
13431 println!("world");
13432 + //
13433 + //
13434 }
13435 "#
13436 .unindent(),
13437 );
13438
13439 cx.set_head_text("new diff base!");
13440 executor.run_until_parked();
13441 cx.assert_state_with_diff(
13442 r#"
13443 - new diff base!
13444 + use some::mod2;
13445 +
13446 + const A: u32 = 42;
13447 + const C: u32 = 42;
13448 +
13449 + fn main(ˇ) {
13450 + //println!("hello");
13451 +
13452 + println!("world");
13453 + //
13454 + //
13455 + }
13456 "#
13457 .unindent(),
13458 );
13459}
13460
13461#[gpui::test]
13462async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13463 init_test(cx, |_| {});
13464
13465 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13466 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13467 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13468 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13469 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13470 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13471
13472 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13473 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13474 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13475
13476 let multi_buffer = cx.new(|cx| {
13477 let mut multibuffer = MultiBuffer::new(ReadWrite);
13478 multibuffer.push_excerpts(
13479 buffer_1.clone(),
13480 [
13481 ExcerptRange {
13482 context: Point::new(0, 0)..Point::new(3, 0),
13483 primary: None,
13484 },
13485 ExcerptRange {
13486 context: Point::new(5, 0)..Point::new(7, 0),
13487 primary: None,
13488 },
13489 ExcerptRange {
13490 context: Point::new(9, 0)..Point::new(10, 3),
13491 primary: None,
13492 },
13493 ],
13494 cx,
13495 );
13496 multibuffer.push_excerpts(
13497 buffer_2.clone(),
13498 [
13499 ExcerptRange {
13500 context: Point::new(0, 0)..Point::new(3, 0),
13501 primary: None,
13502 },
13503 ExcerptRange {
13504 context: Point::new(5, 0)..Point::new(7, 0),
13505 primary: None,
13506 },
13507 ExcerptRange {
13508 context: Point::new(9, 0)..Point::new(10, 3),
13509 primary: None,
13510 },
13511 ],
13512 cx,
13513 );
13514 multibuffer.push_excerpts(
13515 buffer_3.clone(),
13516 [
13517 ExcerptRange {
13518 context: Point::new(0, 0)..Point::new(3, 0),
13519 primary: None,
13520 },
13521 ExcerptRange {
13522 context: Point::new(5, 0)..Point::new(7, 0),
13523 primary: None,
13524 },
13525 ExcerptRange {
13526 context: Point::new(9, 0)..Point::new(10, 3),
13527 primary: None,
13528 },
13529 ],
13530 cx,
13531 );
13532 multibuffer
13533 });
13534
13535 let editor = cx.add_window(|window, cx| {
13536 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13537 });
13538 editor
13539 .update(cx, |editor, _window, cx| {
13540 for (buffer, diff_base) in [
13541 (buffer_1.clone(), file_1_old),
13542 (buffer_2.clone(), file_2_old),
13543 (buffer_3.clone(), file_3_old),
13544 ] {
13545 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13546 editor
13547 .buffer
13548 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13549 }
13550 })
13551 .unwrap();
13552
13553 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13554 cx.run_until_parked();
13555
13556 cx.assert_editor_state(
13557 &"
13558 ˇaaa
13559 ccc
13560 ddd
13561
13562 ggg
13563 hhh
13564
13565
13566 lll
13567 mmm
13568 NNN
13569
13570 qqq
13571 rrr
13572
13573 uuu
13574 111
13575 222
13576 333
13577
13578 666
13579 777
13580
13581 000
13582 !!!"
13583 .unindent(),
13584 );
13585
13586 cx.update_editor(|editor, window, cx| {
13587 editor.select_all(&SelectAll, window, cx);
13588 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13589 });
13590 cx.executor().run_until_parked();
13591
13592 cx.assert_state_with_diff(
13593 "
13594 «aaa
13595 - bbb
13596 ccc
13597 ddd
13598
13599 ggg
13600 hhh
13601
13602
13603 lll
13604 mmm
13605 - nnn
13606 + NNN
13607
13608 qqq
13609 rrr
13610
13611 uuu
13612 111
13613 222
13614 333
13615
13616 + 666
13617 777
13618
13619 000
13620 !!!ˇ»"
13621 .unindent(),
13622 );
13623}
13624
13625#[gpui::test]
13626async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13627 init_test(cx, |_| {});
13628
13629 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13630 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13631
13632 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13633 let multi_buffer = cx.new(|cx| {
13634 let mut multibuffer = MultiBuffer::new(ReadWrite);
13635 multibuffer.push_excerpts(
13636 buffer.clone(),
13637 [
13638 ExcerptRange {
13639 context: Point::new(0, 0)..Point::new(2, 0),
13640 primary: None,
13641 },
13642 ExcerptRange {
13643 context: Point::new(4, 0)..Point::new(7, 0),
13644 primary: None,
13645 },
13646 ExcerptRange {
13647 context: Point::new(9, 0)..Point::new(10, 0),
13648 primary: None,
13649 },
13650 ],
13651 cx,
13652 );
13653 multibuffer
13654 });
13655
13656 let editor = cx.add_window(|window, cx| {
13657 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13658 });
13659 editor
13660 .update(cx, |editor, _window, cx| {
13661 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13662 editor
13663 .buffer
13664 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13665 })
13666 .unwrap();
13667
13668 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13669 cx.run_until_parked();
13670
13671 cx.update_editor(|editor, window, cx| {
13672 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13673 });
13674 cx.executor().run_until_parked();
13675
13676 // When the start of a hunk coincides with the start of its excerpt,
13677 // the hunk is expanded. When the start of a a hunk is earlier than
13678 // the start of its excerpt, the hunk is not expanded.
13679 cx.assert_state_with_diff(
13680 "
13681 ˇaaa
13682 - bbb
13683 + BBB
13684
13685 - ddd
13686 - eee
13687 + DDD
13688 + EEE
13689 fff
13690
13691 iii
13692 "
13693 .unindent(),
13694 );
13695}
13696
13697#[gpui::test]
13698async fn test_edits_around_expanded_insertion_hunks(
13699 executor: BackgroundExecutor,
13700 cx: &mut TestAppContext,
13701) {
13702 init_test(cx, |_| {});
13703
13704 let mut cx = EditorTestContext::new(cx).await;
13705
13706 let diff_base = r#"
13707 use some::mod1;
13708 use some::mod2;
13709
13710 const A: u32 = 42;
13711
13712 fn main() {
13713 println!("hello");
13714
13715 println!("world");
13716 }
13717 "#
13718 .unindent();
13719 executor.run_until_parked();
13720 cx.set_state(
13721 &r#"
13722 use some::mod1;
13723 use some::mod2;
13724
13725 const A: u32 = 42;
13726 const B: u32 = 42;
13727 const C: u32 = 42;
13728 ˇ
13729
13730 fn main() {
13731 println!("hello");
13732
13733 println!("world");
13734 }
13735 "#
13736 .unindent(),
13737 );
13738
13739 cx.set_head_text(&diff_base);
13740 executor.run_until_parked();
13741
13742 cx.update_editor(|editor, window, cx| {
13743 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13744 });
13745 executor.run_until_parked();
13746
13747 cx.assert_state_with_diff(
13748 r#"
13749 use some::mod1;
13750 use some::mod2;
13751
13752 const A: u32 = 42;
13753 + const B: u32 = 42;
13754 + const C: u32 = 42;
13755 + ˇ
13756
13757 fn main() {
13758 println!("hello");
13759
13760 println!("world");
13761 }
13762 "#
13763 .unindent(),
13764 );
13765
13766 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13767 executor.run_until_parked();
13768
13769 cx.assert_state_with_diff(
13770 r#"
13771 use some::mod1;
13772 use some::mod2;
13773
13774 const A: u32 = 42;
13775 + const B: u32 = 42;
13776 + const C: u32 = 42;
13777 + const D: u32 = 42;
13778 + ˇ
13779
13780 fn main() {
13781 println!("hello");
13782
13783 println!("world");
13784 }
13785 "#
13786 .unindent(),
13787 );
13788
13789 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13790 executor.run_until_parked();
13791
13792 cx.assert_state_with_diff(
13793 r#"
13794 use some::mod1;
13795 use some::mod2;
13796
13797 const A: u32 = 42;
13798 + const B: u32 = 42;
13799 + const C: u32 = 42;
13800 + const D: u32 = 42;
13801 + const E: u32 = 42;
13802 + ˇ
13803
13804 fn main() {
13805 println!("hello");
13806
13807 println!("world");
13808 }
13809 "#
13810 .unindent(),
13811 );
13812
13813 cx.update_editor(|editor, window, cx| {
13814 editor.delete_line(&DeleteLine, window, cx);
13815 });
13816 executor.run_until_parked();
13817
13818 cx.assert_state_with_diff(
13819 r#"
13820 use some::mod1;
13821 use some::mod2;
13822
13823 const A: u32 = 42;
13824 + const B: u32 = 42;
13825 + const C: u32 = 42;
13826 + const D: u32 = 42;
13827 + const E: u32 = 42;
13828 ˇ
13829 fn main() {
13830 println!("hello");
13831
13832 println!("world");
13833 }
13834 "#
13835 .unindent(),
13836 );
13837
13838 cx.update_editor(|editor, window, cx| {
13839 editor.move_up(&MoveUp, window, cx);
13840 editor.delete_line(&DeleteLine, window, cx);
13841 editor.move_up(&MoveUp, window, cx);
13842 editor.delete_line(&DeleteLine, window, cx);
13843 editor.move_up(&MoveUp, window, cx);
13844 editor.delete_line(&DeleteLine, window, cx);
13845 });
13846 executor.run_until_parked();
13847 cx.assert_state_with_diff(
13848 r#"
13849 use some::mod1;
13850 use some::mod2;
13851
13852 const A: u32 = 42;
13853 + const B: u32 = 42;
13854 ˇ
13855 fn main() {
13856 println!("hello");
13857
13858 println!("world");
13859 }
13860 "#
13861 .unindent(),
13862 );
13863
13864 cx.update_editor(|editor, window, cx| {
13865 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13866 editor.delete_line(&DeleteLine, window, cx);
13867 });
13868 executor.run_until_parked();
13869 cx.assert_state_with_diff(
13870 r#"
13871 ˇ
13872 fn main() {
13873 println!("hello");
13874
13875 println!("world");
13876 }
13877 "#
13878 .unindent(),
13879 );
13880}
13881
13882#[gpui::test]
13883async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13884 init_test(cx, |_| {});
13885
13886 let mut cx = EditorTestContext::new(cx).await;
13887 cx.set_head_text(indoc! { "
13888 one
13889 two
13890 three
13891 four
13892 five
13893 "
13894 });
13895 cx.set_state(indoc! { "
13896 one
13897 ˇthree
13898 five
13899 "});
13900 cx.run_until_parked();
13901 cx.update_editor(|editor, window, cx| {
13902 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13903 });
13904 cx.assert_state_with_diff(
13905 indoc! { "
13906 one
13907 - two
13908 ˇthree
13909 - four
13910 five
13911 "}
13912 .to_string(),
13913 );
13914 cx.update_editor(|editor, window, cx| {
13915 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13916 });
13917
13918 cx.assert_state_with_diff(
13919 indoc! { "
13920 one
13921 ˇthree
13922 five
13923 "}
13924 .to_string(),
13925 );
13926
13927 cx.set_state(indoc! { "
13928 one
13929 ˇTWO
13930 three
13931 four
13932 five
13933 "});
13934 cx.run_until_parked();
13935 cx.update_editor(|editor, window, cx| {
13936 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13937 });
13938
13939 cx.assert_state_with_diff(
13940 indoc! { "
13941 one
13942 - two
13943 + ˇTWO
13944 three
13945 four
13946 five
13947 "}
13948 .to_string(),
13949 );
13950 cx.update_editor(|editor, window, cx| {
13951 editor.move_up(&Default::default(), window, cx);
13952 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13953 });
13954 cx.assert_state_with_diff(
13955 indoc! { "
13956 one
13957 ˇTWO
13958 three
13959 four
13960 five
13961 "}
13962 .to_string(),
13963 );
13964}
13965
13966#[gpui::test]
13967async fn test_edits_around_expanded_deletion_hunks(
13968 executor: BackgroundExecutor,
13969 cx: &mut TestAppContext,
13970) {
13971 init_test(cx, |_| {});
13972
13973 let mut cx = EditorTestContext::new(cx).await;
13974
13975 let diff_base = r#"
13976 use some::mod1;
13977 use some::mod2;
13978
13979 const A: u32 = 42;
13980 const B: u32 = 42;
13981 const C: u32 = 42;
13982
13983
13984 fn main() {
13985 println!("hello");
13986
13987 println!("world");
13988 }
13989 "#
13990 .unindent();
13991 executor.run_until_parked();
13992 cx.set_state(
13993 &r#"
13994 use some::mod1;
13995 use some::mod2;
13996
13997 ˇconst B: u32 = 42;
13998 const C: u32 = 42;
13999
14000
14001 fn main() {
14002 println!("hello");
14003
14004 println!("world");
14005 }
14006 "#
14007 .unindent(),
14008 );
14009
14010 cx.set_head_text(&diff_base);
14011 executor.run_until_parked();
14012
14013 cx.update_editor(|editor, window, cx| {
14014 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14015 });
14016 executor.run_until_parked();
14017
14018 cx.assert_state_with_diff(
14019 r#"
14020 use some::mod1;
14021 use some::mod2;
14022
14023 - const A: u32 = 42;
14024 ˇconst B: u32 = 42;
14025 const C: u32 = 42;
14026
14027
14028 fn main() {
14029 println!("hello");
14030
14031 println!("world");
14032 }
14033 "#
14034 .unindent(),
14035 );
14036
14037 cx.update_editor(|editor, window, cx| {
14038 editor.delete_line(&DeleteLine, window, cx);
14039 });
14040 executor.run_until_parked();
14041 cx.assert_state_with_diff(
14042 r#"
14043 use some::mod1;
14044 use some::mod2;
14045
14046 - const A: u32 = 42;
14047 - const B: u32 = 42;
14048 ˇconst C: u32 = 42;
14049
14050
14051 fn main() {
14052 println!("hello");
14053
14054 println!("world");
14055 }
14056 "#
14057 .unindent(),
14058 );
14059
14060 cx.update_editor(|editor, window, cx| {
14061 editor.delete_line(&DeleteLine, window, cx);
14062 });
14063 executor.run_until_parked();
14064 cx.assert_state_with_diff(
14065 r#"
14066 use some::mod1;
14067 use some::mod2;
14068
14069 - const A: u32 = 42;
14070 - const B: u32 = 42;
14071 - const C: u32 = 42;
14072 ˇ
14073
14074 fn main() {
14075 println!("hello");
14076
14077 println!("world");
14078 }
14079 "#
14080 .unindent(),
14081 );
14082
14083 cx.update_editor(|editor, window, cx| {
14084 editor.handle_input("replacement", window, cx);
14085 });
14086 executor.run_until_parked();
14087 cx.assert_state_with_diff(
14088 r#"
14089 use some::mod1;
14090 use some::mod2;
14091
14092 - const A: u32 = 42;
14093 - const B: u32 = 42;
14094 - const C: u32 = 42;
14095 -
14096 + replacementˇ
14097
14098 fn main() {
14099 println!("hello");
14100
14101 println!("world");
14102 }
14103 "#
14104 .unindent(),
14105 );
14106}
14107
14108#[gpui::test]
14109async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14110 init_test(cx, |_| {});
14111
14112 let mut cx = EditorTestContext::new(cx).await;
14113
14114 let base_text = r#"
14115 one
14116 two
14117 three
14118 four
14119 five
14120 "#
14121 .unindent();
14122 executor.run_until_parked();
14123 cx.set_state(
14124 &r#"
14125 one
14126 two
14127 fˇour
14128 five
14129 "#
14130 .unindent(),
14131 );
14132
14133 cx.set_head_text(&base_text);
14134 executor.run_until_parked();
14135
14136 cx.update_editor(|editor, window, cx| {
14137 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14138 });
14139 executor.run_until_parked();
14140
14141 cx.assert_state_with_diff(
14142 r#"
14143 one
14144 two
14145 - three
14146 fˇour
14147 five
14148 "#
14149 .unindent(),
14150 );
14151
14152 cx.update_editor(|editor, window, cx| {
14153 editor.backspace(&Backspace, window, cx);
14154 editor.backspace(&Backspace, window, cx);
14155 });
14156 executor.run_until_parked();
14157 cx.assert_state_with_diff(
14158 r#"
14159 one
14160 two
14161 - threeˇ
14162 - four
14163 + our
14164 five
14165 "#
14166 .unindent(),
14167 );
14168}
14169
14170#[gpui::test]
14171async fn test_edit_after_expanded_modification_hunk(
14172 executor: BackgroundExecutor,
14173 cx: &mut TestAppContext,
14174) {
14175 init_test(cx, |_| {});
14176
14177 let mut cx = EditorTestContext::new(cx).await;
14178
14179 let diff_base = r#"
14180 use some::mod1;
14181 use some::mod2;
14182
14183 const A: u32 = 42;
14184 const B: u32 = 42;
14185 const C: u32 = 42;
14186 const D: u32 = 42;
14187
14188
14189 fn main() {
14190 println!("hello");
14191
14192 println!("world");
14193 }"#
14194 .unindent();
14195
14196 cx.set_state(
14197 &r#"
14198 use some::mod1;
14199 use some::mod2;
14200
14201 const A: u32 = 42;
14202 const B: u32 = 42;
14203 const C: u32 = 43ˇ
14204 const D: u32 = 42;
14205
14206
14207 fn main() {
14208 println!("hello");
14209
14210 println!("world");
14211 }"#
14212 .unindent(),
14213 );
14214
14215 cx.set_head_text(&diff_base);
14216 executor.run_until_parked();
14217 cx.update_editor(|editor, window, cx| {
14218 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14219 });
14220 executor.run_until_parked();
14221
14222 cx.assert_state_with_diff(
14223 r#"
14224 use some::mod1;
14225 use some::mod2;
14226
14227 const A: u32 = 42;
14228 const B: u32 = 42;
14229 - const C: u32 = 42;
14230 + const C: u32 = 43ˇ
14231 const D: u32 = 42;
14232
14233
14234 fn main() {
14235 println!("hello");
14236
14237 println!("world");
14238 }"#
14239 .unindent(),
14240 );
14241
14242 cx.update_editor(|editor, window, cx| {
14243 editor.handle_input("\nnew_line\n", window, cx);
14244 });
14245 executor.run_until_parked();
14246
14247 cx.assert_state_with_diff(
14248 r#"
14249 use some::mod1;
14250 use some::mod2;
14251
14252 const A: u32 = 42;
14253 const B: u32 = 42;
14254 - const C: u32 = 42;
14255 + const C: u32 = 43
14256 + new_line
14257 + ˇ
14258 const D: u32 = 42;
14259
14260
14261 fn main() {
14262 println!("hello");
14263
14264 println!("world");
14265 }"#
14266 .unindent(),
14267 );
14268}
14269
14270#[gpui::test]
14271async fn test_stage_and_unstage_added_file_hunk(
14272 executor: BackgroundExecutor,
14273 cx: &mut TestAppContext,
14274) {
14275 init_test(cx, |_| {});
14276
14277 let mut cx = EditorTestContext::new(cx).await;
14278 cx.update_editor(|editor, _, cx| {
14279 editor.set_expand_all_diff_hunks(cx);
14280 });
14281
14282 let working_copy = r#"
14283 ˇfn main() {
14284 println!("hello, world!");
14285 }
14286 "#
14287 .unindent();
14288
14289 cx.set_state(&working_copy);
14290 executor.run_until_parked();
14291
14292 cx.assert_state_with_diff(
14293 r#"
14294 + ˇfn main() {
14295 + println!("hello, world!");
14296 + }
14297 "#
14298 .unindent(),
14299 );
14300 cx.assert_index_text(None);
14301
14302 cx.update_editor(|editor, window, cx| {
14303 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14304 });
14305 executor.run_until_parked();
14306 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14307 cx.assert_state_with_diff(
14308 r#"
14309 + ˇfn main() {
14310 + println!("hello, world!");
14311 + }
14312 "#
14313 .unindent(),
14314 );
14315
14316 cx.update_editor(|editor, window, cx| {
14317 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14318 });
14319 executor.run_until_parked();
14320 cx.assert_index_text(None);
14321}
14322
14323async fn setup_indent_guides_editor(
14324 text: &str,
14325 cx: &mut TestAppContext,
14326) -> (BufferId, EditorTestContext) {
14327 init_test(cx, |_| {});
14328
14329 let mut cx = EditorTestContext::new(cx).await;
14330
14331 let buffer_id = cx.update_editor(|editor, window, cx| {
14332 editor.set_text(text, window, cx);
14333 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14334
14335 buffer_ids[0]
14336 });
14337
14338 (buffer_id, cx)
14339}
14340
14341fn assert_indent_guides(
14342 range: Range<u32>,
14343 expected: Vec<IndentGuide>,
14344 active_indices: Option<Vec<usize>>,
14345 cx: &mut EditorTestContext,
14346) {
14347 let indent_guides = cx.update_editor(|editor, window, cx| {
14348 let snapshot = editor.snapshot(window, cx).display_snapshot;
14349 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14350 editor,
14351 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14352 true,
14353 &snapshot,
14354 cx,
14355 );
14356
14357 indent_guides.sort_by(|a, b| {
14358 a.depth.cmp(&b.depth).then(
14359 a.start_row
14360 .cmp(&b.start_row)
14361 .then(a.end_row.cmp(&b.end_row)),
14362 )
14363 });
14364 indent_guides
14365 });
14366
14367 if let Some(expected) = active_indices {
14368 let active_indices = cx.update_editor(|editor, window, cx| {
14369 let snapshot = editor.snapshot(window, cx).display_snapshot;
14370 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14371 });
14372
14373 assert_eq!(
14374 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14375 expected,
14376 "Active indent guide indices do not match"
14377 );
14378 }
14379
14380 assert_eq!(indent_guides, expected, "Indent guides do not match");
14381}
14382
14383fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14384 IndentGuide {
14385 buffer_id,
14386 start_row: MultiBufferRow(start_row),
14387 end_row: MultiBufferRow(end_row),
14388 depth,
14389 tab_size: 4,
14390 settings: IndentGuideSettings {
14391 enabled: true,
14392 line_width: 1,
14393 active_line_width: 1,
14394 ..Default::default()
14395 },
14396 }
14397}
14398
14399#[gpui::test]
14400async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14401 let (buffer_id, mut cx) = setup_indent_guides_editor(
14402 &"
14403 fn main() {
14404 let a = 1;
14405 }"
14406 .unindent(),
14407 cx,
14408 )
14409 .await;
14410
14411 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14412}
14413
14414#[gpui::test]
14415async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14416 let (buffer_id, mut cx) = setup_indent_guides_editor(
14417 &"
14418 fn main() {
14419 let a = 1;
14420 let b = 2;
14421 }"
14422 .unindent(),
14423 cx,
14424 )
14425 .await;
14426
14427 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14428}
14429
14430#[gpui::test]
14431async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14432 let (buffer_id, mut cx) = setup_indent_guides_editor(
14433 &"
14434 fn main() {
14435 let a = 1;
14436 if a == 3 {
14437 let b = 2;
14438 } else {
14439 let c = 3;
14440 }
14441 }"
14442 .unindent(),
14443 cx,
14444 )
14445 .await;
14446
14447 assert_indent_guides(
14448 0..8,
14449 vec![
14450 indent_guide(buffer_id, 1, 6, 0),
14451 indent_guide(buffer_id, 3, 3, 1),
14452 indent_guide(buffer_id, 5, 5, 1),
14453 ],
14454 None,
14455 &mut cx,
14456 );
14457}
14458
14459#[gpui::test]
14460async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14461 let (buffer_id, mut cx) = setup_indent_guides_editor(
14462 &"
14463 fn main() {
14464 let a = 1;
14465 let b = 2;
14466 let c = 3;
14467 }"
14468 .unindent(),
14469 cx,
14470 )
14471 .await;
14472
14473 assert_indent_guides(
14474 0..5,
14475 vec![
14476 indent_guide(buffer_id, 1, 3, 0),
14477 indent_guide(buffer_id, 2, 2, 1),
14478 ],
14479 None,
14480 &mut cx,
14481 );
14482}
14483
14484#[gpui::test]
14485async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14486 let (buffer_id, mut cx) = setup_indent_guides_editor(
14487 &"
14488 fn main() {
14489 let a = 1;
14490
14491 let c = 3;
14492 }"
14493 .unindent(),
14494 cx,
14495 )
14496 .await;
14497
14498 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14499}
14500
14501#[gpui::test]
14502async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14503 let (buffer_id, mut cx) = setup_indent_guides_editor(
14504 &"
14505 fn main() {
14506 let a = 1;
14507
14508 let c = 3;
14509
14510 if a == 3 {
14511 let b = 2;
14512 } else {
14513 let c = 3;
14514 }
14515 }"
14516 .unindent(),
14517 cx,
14518 )
14519 .await;
14520
14521 assert_indent_guides(
14522 0..11,
14523 vec![
14524 indent_guide(buffer_id, 1, 9, 0),
14525 indent_guide(buffer_id, 6, 6, 1),
14526 indent_guide(buffer_id, 8, 8, 1),
14527 ],
14528 None,
14529 &mut cx,
14530 );
14531}
14532
14533#[gpui::test]
14534async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14535 let (buffer_id, mut cx) = setup_indent_guides_editor(
14536 &"
14537 fn main() {
14538 let a = 1;
14539
14540 let c = 3;
14541
14542 if a == 3 {
14543 let b = 2;
14544 } else {
14545 let c = 3;
14546 }
14547 }"
14548 .unindent(),
14549 cx,
14550 )
14551 .await;
14552
14553 assert_indent_guides(
14554 1..11,
14555 vec![
14556 indent_guide(buffer_id, 1, 9, 0),
14557 indent_guide(buffer_id, 6, 6, 1),
14558 indent_guide(buffer_id, 8, 8, 1),
14559 ],
14560 None,
14561 &mut cx,
14562 );
14563}
14564
14565#[gpui::test]
14566async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14567 let (buffer_id, mut cx) = setup_indent_guides_editor(
14568 &"
14569 fn main() {
14570 let a = 1;
14571
14572 let c = 3;
14573
14574 if a == 3 {
14575 let b = 2;
14576 } else {
14577 let c = 3;
14578 }
14579 }"
14580 .unindent(),
14581 cx,
14582 )
14583 .await;
14584
14585 assert_indent_guides(
14586 1..10,
14587 vec![
14588 indent_guide(buffer_id, 1, 9, 0),
14589 indent_guide(buffer_id, 6, 6, 1),
14590 indent_guide(buffer_id, 8, 8, 1),
14591 ],
14592 None,
14593 &mut cx,
14594 );
14595}
14596
14597#[gpui::test]
14598async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14599 let (buffer_id, mut cx) = setup_indent_guides_editor(
14600 &"
14601 block1
14602 block2
14603 block3
14604 block4
14605 block2
14606 block1
14607 block1"
14608 .unindent(),
14609 cx,
14610 )
14611 .await;
14612
14613 assert_indent_guides(
14614 1..10,
14615 vec![
14616 indent_guide(buffer_id, 1, 4, 0),
14617 indent_guide(buffer_id, 2, 3, 1),
14618 indent_guide(buffer_id, 3, 3, 2),
14619 ],
14620 None,
14621 &mut cx,
14622 );
14623}
14624
14625#[gpui::test]
14626async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14627 let (buffer_id, mut cx) = setup_indent_guides_editor(
14628 &"
14629 block1
14630 block2
14631 block3
14632
14633 block1
14634 block1"
14635 .unindent(),
14636 cx,
14637 )
14638 .await;
14639
14640 assert_indent_guides(
14641 0..6,
14642 vec![
14643 indent_guide(buffer_id, 1, 2, 0),
14644 indent_guide(buffer_id, 2, 2, 1),
14645 ],
14646 None,
14647 &mut cx,
14648 );
14649}
14650
14651#[gpui::test]
14652async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14653 let (buffer_id, mut cx) = setup_indent_guides_editor(
14654 &"
14655 block1
14656
14657
14658
14659 block2
14660 "
14661 .unindent(),
14662 cx,
14663 )
14664 .await;
14665
14666 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14667}
14668
14669#[gpui::test]
14670async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14671 let (buffer_id, mut cx) = setup_indent_guides_editor(
14672 &"
14673 def a:
14674 \tb = 3
14675 \tif True:
14676 \t\tc = 4
14677 \t\td = 5
14678 \tprint(b)
14679 "
14680 .unindent(),
14681 cx,
14682 )
14683 .await;
14684
14685 assert_indent_guides(
14686 0..6,
14687 vec![
14688 indent_guide(buffer_id, 1, 6, 0),
14689 indent_guide(buffer_id, 3, 4, 1),
14690 ],
14691 None,
14692 &mut cx,
14693 );
14694}
14695
14696#[gpui::test]
14697async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14698 let (buffer_id, mut cx) = setup_indent_guides_editor(
14699 &"
14700 fn main() {
14701 let a = 1;
14702 }"
14703 .unindent(),
14704 cx,
14705 )
14706 .await;
14707
14708 cx.update_editor(|editor, window, cx| {
14709 editor.change_selections(None, window, cx, |s| {
14710 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14711 });
14712 });
14713
14714 assert_indent_guides(
14715 0..3,
14716 vec![indent_guide(buffer_id, 1, 1, 0)],
14717 Some(vec![0]),
14718 &mut cx,
14719 );
14720}
14721
14722#[gpui::test]
14723async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14724 let (buffer_id, mut cx) = setup_indent_guides_editor(
14725 &"
14726 fn main() {
14727 if 1 == 2 {
14728 let a = 1;
14729 }
14730 }"
14731 .unindent(),
14732 cx,
14733 )
14734 .await;
14735
14736 cx.update_editor(|editor, window, cx| {
14737 editor.change_selections(None, window, cx, |s| {
14738 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14739 });
14740 });
14741
14742 assert_indent_guides(
14743 0..4,
14744 vec![
14745 indent_guide(buffer_id, 1, 3, 0),
14746 indent_guide(buffer_id, 2, 2, 1),
14747 ],
14748 Some(vec![1]),
14749 &mut cx,
14750 );
14751
14752 cx.update_editor(|editor, window, cx| {
14753 editor.change_selections(None, window, cx, |s| {
14754 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14755 });
14756 });
14757
14758 assert_indent_guides(
14759 0..4,
14760 vec![
14761 indent_guide(buffer_id, 1, 3, 0),
14762 indent_guide(buffer_id, 2, 2, 1),
14763 ],
14764 Some(vec![1]),
14765 &mut cx,
14766 );
14767
14768 cx.update_editor(|editor, window, cx| {
14769 editor.change_selections(None, window, cx, |s| {
14770 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14771 });
14772 });
14773
14774 assert_indent_guides(
14775 0..4,
14776 vec![
14777 indent_guide(buffer_id, 1, 3, 0),
14778 indent_guide(buffer_id, 2, 2, 1),
14779 ],
14780 Some(vec![0]),
14781 &mut cx,
14782 );
14783}
14784
14785#[gpui::test]
14786async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
14787 let (buffer_id, mut cx) = setup_indent_guides_editor(
14788 &"
14789 fn main() {
14790 let a = 1;
14791
14792 let b = 2;
14793 }"
14794 .unindent(),
14795 cx,
14796 )
14797 .await;
14798
14799 cx.update_editor(|editor, window, cx| {
14800 editor.change_selections(None, window, cx, |s| {
14801 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14802 });
14803 });
14804
14805 assert_indent_guides(
14806 0..5,
14807 vec![indent_guide(buffer_id, 1, 3, 0)],
14808 Some(vec![0]),
14809 &mut cx,
14810 );
14811}
14812
14813#[gpui::test]
14814async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
14815 let (buffer_id, mut cx) = setup_indent_guides_editor(
14816 &"
14817 def m:
14818 a = 1
14819 pass"
14820 .unindent(),
14821 cx,
14822 )
14823 .await;
14824
14825 cx.update_editor(|editor, window, cx| {
14826 editor.change_selections(None, window, cx, |s| {
14827 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14828 });
14829 });
14830
14831 assert_indent_guides(
14832 0..3,
14833 vec![indent_guide(buffer_id, 1, 2, 0)],
14834 Some(vec![0]),
14835 &mut cx,
14836 );
14837}
14838
14839#[gpui::test]
14840async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
14841 init_test(cx, |_| {});
14842 let mut cx = EditorTestContext::new(cx).await;
14843 let text = indoc! {
14844 "
14845 impl A {
14846 fn b() {
14847 0;
14848 3;
14849 5;
14850 6;
14851 7;
14852 }
14853 }
14854 "
14855 };
14856 let base_text = indoc! {
14857 "
14858 impl A {
14859 fn b() {
14860 0;
14861 1;
14862 2;
14863 3;
14864 4;
14865 }
14866 fn c() {
14867 5;
14868 6;
14869 7;
14870 }
14871 }
14872 "
14873 };
14874
14875 cx.update_editor(|editor, window, cx| {
14876 editor.set_text(text, window, cx);
14877
14878 editor.buffer().update(cx, |multibuffer, cx| {
14879 let buffer = multibuffer.as_singleton().unwrap();
14880 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
14881
14882 multibuffer.set_all_diff_hunks_expanded(cx);
14883 multibuffer.add_diff(diff, cx);
14884
14885 buffer.read(cx).remote_id()
14886 })
14887 });
14888 cx.run_until_parked();
14889
14890 cx.assert_state_with_diff(
14891 indoc! { "
14892 impl A {
14893 fn b() {
14894 0;
14895 - 1;
14896 - 2;
14897 3;
14898 - 4;
14899 - }
14900 - fn c() {
14901 5;
14902 6;
14903 7;
14904 }
14905 }
14906 ˇ"
14907 }
14908 .to_string(),
14909 );
14910
14911 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14912 editor
14913 .snapshot(window, cx)
14914 .buffer_snapshot
14915 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14916 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14917 .collect::<Vec<_>>()
14918 });
14919 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14920 assert_eq!(
14921 actual_guides,
14922 vec![
14923 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14924 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14925 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14926 ]
14927 );
14928}
14929
14930#[gpui::test]
14931async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14932 init_test(cx, |_| {});
14933 let mut cx = EditorTestContext::new(cx).await;
14934
14935 let diff_base = r#"
14936 a
14937 b
14938 c
14939 "#
14940 .unindent();
14941
14942 cx.set_state(
14943 &r#"
14944 ˇA
14945 b
14946 C
14947 "#
14948 .unindent(),
14949 );
14950 cx.set_head_text(&diff_base);
14951 cx.update_editor(|editor, window, cx| {
14952 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14953 });
14954 executor.run_until_parked();
14955
14956 let both_hunks_expanded = r#"
14957 - a
14958 + ˇA
14959 b
14960 - c
14961 + C
14962 "#
14963 .unindent();
14964
14965 cx.assert_state_with_diff(both_hunks_expanded.clone());
14966
14967 let hunk_ranges = cx.update_editor(|editor, window, cx| {
14968 let snapshot = editor.snapshot(window, cx);
14969 let hunks = editor
14970 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
14971 .collect::<Vec<_>>();
14972 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
14973 let buffer_id = hunks[0].buffer_id;
14974 hunks
14975 .into_iter()
14976 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
14977 .collect::<Vec<_>>()
14978 });
14979 assert_eq!(hunk_ranges.len(), 2);
14980
14981 cx.update_editor(|editor, _, cx| {
14982 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
14983 });
14984 executor.run_until_parked();
14985
14986 let second_hunk_expanded = r#"
14987 ˇA
14988 b
14989 - c
14990 + C
14991 "#
14992 .unindent();
14993
14994 cx.assert_state_with_diff(second_hunk_expanded);
14995
14996 cx.update_editor(|editor, _, cx| {
14997 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
14998 });
14999 executor.run_until_parked();
15000
15001 cx.assert_state_with_diff(both_hunks_expanded.clone());
15002
15003 cx.update_editor(|editor, _, cx| {
15004 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15005 });
15006 executor.run_until_parked();
15007
15008 let first_hunk_expanded = r#"
15009 - a
15010 + ˇA
15011 b
15012 C
15013 "#
15014 .unindent();
15015
15016 cx.assert_state_with_diff(first_hunk_expanded);
15017
15018 cx.update_editor(|editor, _, cx| {
15019 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15020 });
15021 executor.run_until_parked();
15022
15023 cx.assert_state_with_diff(both_hunks_expanded);
15024
15025 cx.set_state(
15026 &r#"
15027 ˇA
15028 b
15029 "#
15030 .unindent(),
15031 );
15032 cx.run_until_parked();
15033
15034 // TODO this cursor position seems bad
15035 cx.assert_state_with_diff(
15036 r#"
15037 - ˇa
15038 + A
15039 b
15040 "#
15041 .unindent(),
15042 );
15043
15044 cx.update_editor(|editor, window, cx| {
15045 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15046 });
15047
15048 cx.assert_state_with_diff(
15049 r#"
15050 - ˇa
15051 + A
15052 b
15053 - c
15054 "#
15055 .unindent(),
15056 );
15057
15058 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15059 let snapshot = editor.snapshot(window, cx);
15060 let hunks = editor
15061 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15062 .collect::<Vec<_>>();
15063 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15064 let buffer_id = hunks[0].buffer_id;
15065 hunks
15066 .into_iter()
15067 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15068 .collect::<Vec<_>>()
15069 });
15070 assert_eq!(hunk_ranges.len(), 2);
15071
15072 cx.update_editor(|editor, _, cx| {
15073 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15074 });
15075 executor.run_until_parked();
15076
15077 cx.assert_state_with_diff(
15078 r#"
15079 - ˇa
15080 + A
15081 b
15082 "#
15083 .unindent(),
15084 );
15085}
15086
15087#[gpui::test]
15088async fn test_toggle_deletion_hunk_at_start_of_file(
15089 executor: BackgroundExecutor,
15090 cx: &mut TestAppContext,
15091) {
15092 init_test(cx, |_| {});
15093 let mut cx = EditorTestContext::new(cx).await;
15094
15095 let diff_base = r#"
15096 a
15097 b
15098 c
15099 "#
15100 .unindent();
15101
15102 cx.set_state(
15103 &r#"
15104 ˇb
15105 c
15106 "#
15107 .unindent(),
15108 );
15109 cx.set_head_text(&diff_base);
15110 cx.update_editor(|editor, window, cx| {
15111 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15112 });
15113 executor.run_until_parked();
15114
15115 let hunk_expanded = r#"
15116 - a
15117 ˇb
15118 c
15119 "#
15120 .unindent();
15121
15122 cx.assert_state_with_diff(hunk_expanded.clone());
15123
15124 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15125 let snapshot = editor.snapshot(window, cx);
15126 let hunks = editor
15127 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15128 .collect::<Vec<_>>();
15129 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15130 let buffer_id = hunks[0].buffer_id;
15131 hunks
15132 .into_iter()
15133 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15134 .collect::<Vec<_>>()
15135 });
15136 assert_eq!(hunk_ranges.len(), 1);
15137
15138 cx.update_editor(|editor, _, cx| {
15139 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15140 });
15141 executor.run_until_parked();
15142
15143 let hunk_collapsed = r#"
15144 ˇb
15145 c
15146 "#
15147 .unindent();
15148
15149 cx.assert_state_with_diff(hunk_collapsed);
15150
15151 cx.update_editor(|editor, _, cx| {
15152 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15153 });
15154 executor.run_until_parked();
15155
15156 cx.assert_state_with_diff(hunk_expanded.clone());
15157}
15158
15159#[gpui::test]
15160async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15161 init_test(cx, |_| {});
15162
15163 let fs = FakeFs::new(cx.executor());
15164 fs.insert_tree(
15165 path!("/test"),
15166 json!({
15167 ".git": {},
15168 "file-1": "ONE\n",
15169 "file-2": "TWO\n",
15170 "file-3": "THREE\n",
15171 }),
15172 )
15173 .await;
15174
15175 fs.set_head_for_repo(
15176 path!("/test/.git").as_ref(),
15177 &[
15178 ("file-1".into(), "one\n".into()),
15179 ("file-2".into(), "two\n".into()),
15180 ("file-3".into(), "three\n".into()),
15181 ],
15182 );
15183
15184 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15185 let mut buffers = vec![];
15186 for i in 1..=3 {
15187 let buffer = project
15188 .update(cx, |project, cx| {
15189 let path = format!(path!("/test/file-{}"), i);
15190 project.open_local_buffer(path, cx)
15191 })
15192 .await
15193 .unwrap();
15194 buffers.push(buffer);
15195 }
15196
15197 let multibuffer = cx.new(|cx| {
15198 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15199 multibuffer.set_all_diff_hunks_expanded(cx);
15200 for buffer in &buffers {
15201 let snapshot = buffer.read(cx).snapshot();
15202 multibuffer.set_excerpts_for_path(
15203 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15204 buffer.clone(),
15205 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15206 DEFAULT_MULTIBUFFER_CONTEXT,
15207 cx,
15208 );
15209 }
15210 multibuffer
15211 });
15212
15213 let editor = cx.add_window(|window, cx| {
15214 Editor::new(
15215 EditorMode::Full,
15216 multibuffer,
15217 Some(project),
15218 true,
15219 window,
15220 cx,
15221 )
15222 });
15223 cx.run_until_parked();
15224
15225 let snapshot = editor
15226 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15227 .unwrap();
15228 let hunks = snapshot
15229 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15230 .map(|hunk| match hunk {
15231 DisplayDiffHunk::Unfolded {
15232 display_row_range, ..
15233 } => display_row_range,
15234 DisplayDiffHunk::Folded { .. } => unreachable!(),
15235 })
15236 .collect::<Vec<_>>();
15237 assert_eq!(
15238 hunks,
15239 [
15240 DisplayRow(3)..DisplayRow(5),
15241 DisplayRow(10)..DisplayRow(12),
15242 DisplayRow(17)..DisplayRow(19),
15243 ]
15244 );
15245}
15246
15247#[gpui::test]
15248async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15249 init_test(cx, |_| {});
15250
15251 let mut cx = EditorTestContext::new(cx).await;
15252 cx.set_head_text(indoc! { "
15253 one
15254 two
15255 three
15256 four
15257 five
15258 "
15259 });
15260 cx.set_index_text(indoc! { "
15261 one
15262 two
15263 three
15264 four
15265 five
15266 "
15267 });
15268 cx.set_state(indoc! {"
15269 one
15270 TWO
15271 ˇTHREE
15272 FOUR
15273 five
15274 "});
15275 cx.run_until_parked();
15276 cx.update_editor(|editor, window, cx| {
15277 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15278 });
15279 cx.run_until_parked();
15280 cx.assert_index_text(Some(indoc! {"
15281 one
15282 TWO
15283 THREE
15284 FOUR
15285 five
15286 "}));
15287 cx.set_state(indoc! { "
15288 one
15289 TWO
15290 ˇTHREE-HUNDRED
15291 FOUR
15292 five
15293 "});
15294 cx.run_until_parked();
15295 cx.update_editor(|editor, window, cx| {
15296 let snapshot = editor.snapshot(window, cx);
15297 let hunks = editor
15298 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15299 .collect::<Vec<_>>();
15300 assert_eq!(hunks.len(), 1);
15301 assert_eq!(
15302 hunks[0].status(),
15303 DiffHunkStatus {
15304 kind: DiffHunkStatusKind::Modified,
15305 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15306 }
15307 );
15308
15309 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15310 });
15311 cx.run_until_parked();
15312 cx.assert_index_text(Some(indoc! {"
15313 one
15314 TWO
15315 THREE-HUNDRED
15316 FOUR
15317 five
15318 "}));
15319}
15320
15321#[gpui::test]
15322fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15323 init_test(cx, |_| {});
15324
15325 let editor = cx.add_window(|window, cx| {
15326 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15327 build_editor(buffer, window, cx)
15328 });
15329
15330 let render_args = Arc::new(Mutex::new(None));
15331 let snapshot = editor
15332 .update(cx, |editor, window, cx| {
15333 let snapshot = editor.buffer().read(cx).snapshot(cx);
15334 let range =
15335 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15336
15337 struct RenderArgs {
15338 row: MultiBufferRow,
15339 folded: bool,
15340 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15341 }
15342
15343 let crease = Crease::inline(
15344 range,
15345 FoldPlaceholder::test(),
15346 {
15347 let toggle_callback = render_args.clone();
15348 move |row, folded, callback, _window, _cx| {
15349 *toggle_callback.lock() = Some(RenderArgs {
15350 row,
15351 folded,
15352 callback,
15353 });
15354 div()
15355 }
15356 },
15357 |_row, _folded, _window, _cx| div(),
15358 );
15359
15360 editor.insert_creases(Some(crease), cx);
15361 let snapshot = editor.snapshot(window, cx);
15362 let _div = snapshot.render_crease_toggle(
15363 MultiBufferRow(1),
15364 false,
15365 cx.entity().clone(),
15366 window,
15367 cx,
15368 );
15369 snapshot
15370 })
15371 .unwrap();
15372
15373 let render_args = render_args.lock().take().unwrap();
15374 assert_eq!(render_args.row, MultiBufferRow(1));
15375 assert!(!render_args.folded);
15376 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15377
15378 cx.update_window(*editor, |_, window, cx| {
15379 (render_args.callback)(true, window, cx)
15380 })
15381 .unwrap();
15382 let snapshot = editor
15383 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15384 .unwrap();
15385 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15386
15387 cx.update_window(*editor, |_, window, cx| {
15388 (render_args.callback)(false, window, cx)
15389 })
15390 .unwrap();
15391 let snapshot = editor
15392 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15393 .unwrap();
15394 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15395}
15396
15397#[gpui::test]
15398async fn test_input_text(cx: &mut TestAppContext) {
15399 init_test(cx, |_| {});
15400 let mut cx = EditorTestContext::new(cx).await;
15401
15402 cx.set_state(
15403 &r#"ˇone
15404 two
15405
15406 three
15407 fourˇ
15408 five
15409
15410 siˇx"#
15411 .unindent(),
15412 );
15413
15414 cx.dispatch_action(HandleInput(String::new()));
15415 cx.assert_editor_state(
15416 &r#"ˇone
15417 two
15418
15419 three
15420 fourˇ
15421 five
15422
15423 siˇx"#
15424 .unindent(),
15425 );
15426
15427 cx.dispatch_action(HandleInput("AAAA".to_string()));
15428 cx.assert_editor_state(
15429 &r#"AAAAˇone
15430 two
15431
15432 three
15433 fourAAAAˇ
15434 five
15435
15436 siAAAAˇx"#
15437 .unindent(),
15438 );
15439}
15440
15441#[gpui::test]
15442async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15443 init_test(cx, |_| {});
15444
15445 let mut cx = EditorTestContext::new(cx).await;
15446 cx.set_state(
15447 r#"let foo = 1;
15448let foo = 2;
15449let foo = 3;
15450let fooˇ = 4;
15451let foo = 5;
15452let foo = 6;
15453let foo = 7;
15454let foo = 8;
15455let foo = 9;
15456let foo = 10;
15457let foo = 11;
15458let foo = 12;
15459let foo = 13;
15460let foo = 14;
15461let foo = 15;"#,
15462 );
15463
15464 cx.update_editor(|e, window, cx| {
15465 assert_eq!(
15466 e.next_scroll_position,
15467 NextScrollCursorCenterTopBottom::Center,
15468 "Default next scroll direction is center",
15469 );
15470
15471 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15472 assert_eq!(
15473 e.next_scroll_position,
15474 NextScrollCursorCenterTopBottom::Top,
15475 "After center, next scroll direction should be top",
15476 );
15477
15478 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15479 assert_eq!(
15480 e.next_scroll_position,
15481 NextScrollCursorCenterTopBottom::Bottom,
15482 "After top, next scroll direction should be bottom",
15483 );
15484
15485 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15486 assert_eq!(
15487 e.next_scroll_position,
15488 NextScrollCursorCenterTopBottom::Center,
15489 "After bottom, scrolling should start over",
15490 );
15491
15492 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15493 assert_eq!(
15494 e.next_scroll_position,
15495 NextScrollCursorCenterTopBottom::Top,
15496 "Scrolling continues if retriggered fast enough"
15497 );
15498 });
15499
15500 cx.executor()
15501 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15502 cx.executor().run_until_parked();
15503 cx.update_editor(|e, _, _| {
15504 assert_eq!(
15505 e.next_scroll_position,
15506 NextScrollCursorCenterTopBottom::Center,
15507 "If scrolling is not triggered fast enough, it should reset"
15508 );
15509 });
15510}
15511
15512#[gpui::test]
15513async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15514 init_test(cx, |_| {});
15515 let mut cx = EditorLspTestContext::new_rust(
15516 lsp::ServerCapabilities {
15517 definition_provider: Some(lsp::OneOf::Left(true)),
15518 references_provider: Some(lsp::OneOf::Left(true)),
15519 ..lsp::ServerCapabilities::default()
15520 },
15521 cx,
15522 )
15523 .await;
15524
15525 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15526 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15527 move |params, _| async move {
15528 if empty_go_to_definition {
15529 Ok(None)
15530 } else {
15531 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15532 uri: params.text_document_position_params.text_document.uri,
15533 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15534 })))
15535 }
15536 },
15537 );
15538 let references =
15539 cx.lsp
15540 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15541 Ok(Some(vec![lsp::Location {
15542 uri: params.text_document_position.text_document.uri,
15543 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15544 }]))
15545 });
15546 (go_to_definition, references)
15547 };
15548
15549 cx.set_state(
15550 &r#"fn one() {
15551 let mut a = ˇtwo();
15552 }
15553
15554 fn two() {}"#
15555 .unindent(),
15556 );
15557 set_up_lsp_handlers(false, &mut cx);
15558 let navigated = cx
15559 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15560 .await
15561 .expect("Failed to navigate to definition");
15562 assert_eq!(
15563 navigated,
15564 Navigated::Yes,
15565 "Should have navigated to definition from the GetDefinition response"
15566 );
15567 cx.assert_editor_state(
15568 &r#"fn one() {
15569 let mut a = two();
15570 }
15571
15572 fn «twoˇ»() {}"#
15573 .unindent(),
15574 );
15575
15576 let editors = cx.update_workspace(|workspace, _, cx| {
15577 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15578 });
15579 cx.update_editor(|_, _, test_editor_cx| {
15580 assert_eq!(
15581 editors.len(),
15582 1,
15583 "Initially, only one, test, editor should be open in the workspace"
15584 );
15585 assert_eq!(
15586 test_editor_cx.entity(),
15587 editors.last().expect("Asserted len is 1").clone()
15588 );
15589 });
15590
15591 set_up_lsp_handlers(true, &mut cx);
15592 let navigated = cx
15593 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15594 .await
15595 .expect("Failed to navigate to lookup references");
15596 assert_eq!(
15597 navigated,
15598 Navigated::Yes,
15599 "Should have navigated to references as a fallback after empty GoToDefinition response"
15600 );
15601 // We should not change the selections in the existing file,
15602 // if opening another milti buffer with the references
15603 cx.assert_editor_state(
15604 &r#"fn one() {
15605 let mut a = two();
15606 }
15607
15608 fn «twoˇ»() {}"#
15609 .unindent(),
15610 );
15611 let editors = cx.update_workspace(|workspace, _, cx| {
15612 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15613 });
15614 cx.update_editor(|_, _, test_editor_cx| {
15615 assert_eq!(
15616 editors.len(),
15617 2,
15618 "After falling back to references search, we open a new editor with the results"
15619 );
15620 let references_fallback_text = editors
15621 .into_iter()
15622 .find(|new_editor| *new_editor != test_editor_cx.entity())
15623 .expect("Should have one non-test editor now")
15624 .read(test_editor_cx)
15625 .text(test_editor_cx);
15626 assert_eq!(
15627 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15628 "Should use the range from the references response and not the GoToDefinition one"
15629 );
15630 });
15631}
15632
15633#[gpui::test]
15634async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15635 init_test(cx, |_| {});
15636
15637 let language = Arc::new(Language::new(
15638 LanguageConfig::default(),
15639 Some(tree_sitter_rust::LANGUAGE.into()),
15640 ));
15641
15642 let text = r#"
15643 #[cfg(test)]
15644 mod tests() {
15645 #[test]
15646 fn runnable_1() {
15647 let a = 1;
15648 }
15649
15650 #[test]
15651 fn runnable_2() {
15652 let a = 1;
15653 let b = 2;
15654 }
15655 }
15656 "#
15657 .unindent();
15658
15659 let fs = FakeFs::new(cx.executor());
15660 fs.insert_file("/file.rs", Default::default()).await;
15661
15662 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15663 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15664 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15665 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15666 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15667
15668 let editor = cx.new_window_entity(|window, cx| {
15669 Editor::new(
15670 EditorMode::Full,
15671 multi_buffer,
15672 Some(project.clone()),
15673 true,
15674 window,
15675 cx,
15676 )
15677 });
15678
15679 editor.update_in(cx, |editor, window, cx| {
15680 let snapshot = editor.buffer().read(cx).snapshot(cx);
15681 editor.tasks.insert(
15682 (buffer.read(cx).remote_id(), 3),
15683 RunnableTasks {
15684 templates: vec![],
15685 offset: snapshot.anchor_before(43),
15686 column: 0,
15687 extra_variables: HashMap::default(),
15688 context_range: BufferOffset(43)..BufferOffset(85),
15689 },
15690 );
15691 editor.tasks.insert(
15692 (buffer.read(cx).remote_id(), 8),
15693 RunnableTasks {
15694 templates: vec![],
15695 offset: snapshot.anchor_before(86),
15696 column: 0,
15697 extra_variables: HashMap::default(),
15698 context_range: BufferOffset(86)..BufferOffset(191),
15699 },
15700 );
15701
15702 // Test finding task when cursor is inside function body
15703 editor.change_selections(None, window, cx, |s| {
15704 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15705 });
15706 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15707 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15708
15709 // Test finding task when cursor is on function name
15710 editor.change_selections(None, window, cx, |s| {
15711 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15712 });
15713 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15714 assert_eq!(row, 8, "Should find task when cursor is on function name");
15715 });
15716}
15717
15718#[gpui::test]
15719async fn test_folding_buffers(cx: &mut TestAppContext) {
15720 init_test(cx, |_| {});
15721
15722 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15723 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15724 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15725
15726 let fs = FakeFs::new(cx.executor());
15727 fs.insert_tree(
15728 path!("/a"),
15729 json!({
15730 "first.rs": sample_text_1,
15731 "second.rs": sample_text_2,
15732 "third.rs": sample_text_3,
15733 }),
15734 )
15735 .await;
15736 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15737 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15738 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15739 let worktree = project.update(cx, |project, cx| {
15740 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15741 assert_eq!(worktrees.len(), 1);
15742 worktrees.pop().unwrap()
15743 });
15744 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15745
15746 let buffer_1 = project
15747 .update(cx, |project, cx| {
15748 project.open_buffer((worktree_id, "first.rs"), cx)
15749 })
15750 .await
15751 .unwrap();
15752 let buffer_2 = project
15753 .update(cx, |project, cx| {
15754 project.open_buffer((worktree_id, "second.rs"), cx)
15755 })
15756 .await
15757 .unwrap();
15758 let buffer_3 = project
15759 .update(cx, |project, cx| {
15760 project.open_buffer((worktree_id, "third.rs"), cx)
15761 })
15762 .await
15763 .unwrap();
15764
15765 let multi_buffer = cx.new(|cx| {
15766 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15767 multi_buffer.push_excerpts(
15768 buffer_1.clone(),
15769 [
15770 ExcerptRange {
15771 context: Point::new(0, 0)..Point::new(3, 0),
15772 primary: None,
15773 },
15774 ExcerptRange {
15775 context: Point::new(5, 0)..Point::new(7, 0),
15776 primary: None,
15777 },
15778 ExcerptRange {
15779 context: Point::new(9, 0)..Point::new(10, 4),
15780 primary: None,
15781 },
15782 ],
15783 cx,
15784 );
15785 multi_buffer.push_excerpts(
15786 buffer_2.clone(),
15787 [
15788 ExcerptRange {
15789 context: Point::new(0, 0)..Point::new(3, 0),
15790 primary: None,
15791 },
15792 ExcerptRange {
15793 context: Point::new(5, 0)..Point::new(7, 0),
15794 primary: None,
15795 },
15796 ExcerptRange {
15797 context: Point::new(9, 0)..Point::new(10, 4),
15798 primary: None,
15799 },
15800 ],
15801 cx,
15802 );
15803 multi_buffer.push_excerpts(
15804 buffer_3.clone(),
15805 [
15806 ExcerptRange {
15807 context: Point::new(0, 0)..Point::new(3, 0),
15808 primary: None,
15809 },
15810 ExcerptRange {
15811 context: Point::new(5, 0)..Point::new(7, 0),
15812 primary: None,
15813 },
15814 ExcerptRange {
15815 context: Point::new(9, 0)..Point::new(10, 4),
15816 primary: None,
15817 },
15818 ],
15819 cx,
15820 );
15821 multi_buffer
15822 });
15823 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15824 Editor::new(
15825 EditorMode::Full,
15826 multi_buffer.clone(),
15827 Some(project.clone()),
15828 true,
15829 window,
15830 cx,
15831 )
15832 });
15833
15834 assert_eq!(
15835 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15836 "\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",
15837 );
15838
15839 multi_buffer_editor.update(cx, |editor, cx| {
15840 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15841 });
15842 assert_eq!(
15843 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15844 "\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",
15845 "After folding the first buffer, its text should not be displayed"
15846 );
15847
15848 multi_buffer_editor.update(cx, |editor, cx| {
15849 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15850 });
15851 assert_eq!(
15852 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15853 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15854 "After folding the second buffer, its text should not be displayed"
15855 );
15856
15857 multi_buffer_editor.update(cx, |editor, cx| {
15858 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15859 });
15860 assert_eq!(
15861 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15862 "\n\n\n\n\n",
15863 "After folding the third buffer, its text should not be displayed"
15864 );
15865
15866 // Emulate selection inside the fold logic, that should work
15867 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15868 editor
15869 .snapshot(window, cx)
15870 .next_line_boundary(Point::new(0, 4));
15871 });
15872
15873 multi_buffer_editor.update(cx, |editor, cx| {
15874 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15875 });
15876 assert_eq!(
15877 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15878 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15879 "After unfolding the second buffer, its text should be displayed"
15880 );
15881
15882 // Typing inside of buffer 1 causes that buffer to be unfolded.
15883 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15884 assert_eq!(
15885 multi_buffer
15886 .read(cx)
15887 .snapshot(cx)
15888 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
15889 .collect::<String>(),
15890 "bbbb"
15891 );
15892 editor.change_selections(None, window, cx, |selections| {
15893 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
15894 });
15895 editor.handle_input("B", window, cx);
15896 });
15897
15898 assert_eq!(
15899 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15900 "\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",
15901 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15902 );
15903
15904 multi_buffer_editor.update(cx, |editor, cx| {
15905 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15906 });
15907 assert_eq!(
15908 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15909 "\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",
15910 "After unfolding the all buffers, all original text should be displayed"
15911 );
15912}
15913
15914#[gpui::test]
15915async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
15916 init_test(cx, |_| {});
15917
15918 let sample_text_1 = "1111\n2222\n3333".to_string();
15919 let sample_text_2 = "4444\n5555\n6666".to_string();
15920 let sample_text_3 = "7777\n8888\n9999".to_string();
15921
15922 let fs = FakeFs::new(cx.executor());
15923 fs.insert_tree(
15924 path!("/a"),
15925 json!({
15926 "first.rs": sample_text_1,
15927 "second.rs": sample_text_2,
15928 "third.rs": sample_text_3,
15929 }),
15930 )
15931 .await;
15932 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15933 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15934 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15935 let worktree = project.update(cx, |project, cx| {
15936 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15937 assert_eq!(worktrees.len(), 1);
15938 worktrees.pop().unwrap()
15939 });
15940 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15941
15942 let buffer_1 = project
15943 .update(cx, |project, cx| {
15944 project.open_buffer((worktree_id, "first.rs"), cx)
15945 })
15946 .await
15947 .unwrap();
15948 let buffer_2 = project
15949 .update(cx, |project, cx| {
15950 project.open_buffer((worktree_id, "second.rs"), cx)
15951 })
15952 .await
15953 .unwrap();
15954 let buffer_3 = project
15955 .update(cx, |project, cx| {
15956 project.open_buffer((worktree_id, "third.rs"), cx)
15957 })
15958 .await
15959 .unwrap();
15960
15961 let multi_buffer = cx.new(|cx| {
15962 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15963 multi_buffer.push_excerpts(
15964 buffer_1.clone(),
15965 [ExcerptRange {
15966 context: Point::new(0, 0)..Point::new(3, 0),
15967 primary: None,
15968 }],
15969 cx,
15970 );
15971 multi_buffer.push_excerpts(
15972 buffer_2.clone(),
15973 [ExcerptRange {
15974 context: Point::new(0, 0)..Point::new(3, 0),
15975 primary: None,
15976 }],
15977 cx,
15978 );
15979 multi_buffer.push_excerpts(
15980 buffer_3.clone(),
15981 [ExcerptRange {
15982 context: Point::new(0, 0)..Point::new(3, 0),
15983 primary: None,
15984 }],
15985 cx,
15986 );
15987 multi_buffer
15988 });
15989
15990 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15991 Editor::new(
15992 EditorMode::Full,
15993 multi_buffer,
15994 Some(project.clone()),
15995 true,
15996 window,
15997 cx,
15998 )
15999 });
16000
16001 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16002 assert_eq!(
16003 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16004 full_text,
16005 );
16006
16007 multi_buffer_editor.update(cx, |editor, cx| {
16008 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16009 });
16010 assert_eq!(
16011 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16012 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16013 "After folding the first buffer, its text should not be displayed"
16014 );
16015
16016 multi_buffer_editor.update(cx, |editor, cx| {
16017 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16018 });
16019
16020 assert_eq!(
16021 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16022 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16023 "After folding the second buffer, its text should not be displayed"
16024 );
16025
16026 multi_buffer_editor.update(cx, |editor, cx| {
16027 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16028 });
16029 assert_eq!(
16030 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16031 "\n\n\n\n\n",
16032 "After folding the third buffer, its text should not be displayed"
16033 );
16034
16035 multi_buffer_editor.update(cx, |editor, cx| {
16036 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16037 });
16038 assert_eq!(
16039 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16040 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16041 "After unfolding the second buffer, its text should be displayed"
16042 );
16043
16044 multi_buffer_editor.update(cx, |editor, cx| {
16045 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16046 });
16047 assert_eq!(
16048 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16049 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16050 "After unfolding the first buffer, its text should be displayed"
16051 );
16052
16053 multi_buffer_editor.update(cx, |editor, cx| {
16054 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16055 });
16056 assert_eq!(
16057 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16058 full_text,
16059 "After unfolding all buffers, all original text should be displayed"
16060 );
16061}
16062
16063#[gpui::test]
16064async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16065 init_test(cx, |_| {});
16066
16067 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16068
16069 let fs = FakeFs::new(cx.executor());
16070 fs.insert_tree(
16071 path!("/a"),
16072 json!({
16073 "main.rs": sample_text,
16074 }),
16075 )
16076 .await;
16077 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16078 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16079 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16080 let worktree = project.update(cx, |project, cx| {
16081 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16082 assert_eq!(worktrees.len(), 1);
16083 worktrees.pop().unwrap()
16084 });
16085 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16086
16087 let buffer_1 = project
16088 .update(cx, |project, cx| {
16089 project.open_buffer((worktree_id, "main.rs"), cx)
16090 })
16091 .await
16092 .unwrap();
16093
16094 let multi_buffer = cx.new(|cx| {
16095 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16096 multi_buffer.push_excerpts(
16097 buffer_1.clone(),
16098 [ExcerptRange {
16099 context: Point::new(0, 0)
16100 ..Point::new(
16101 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16102 0,
16103 ),
16104 primary: None,
16105 }],
16106 cx,
16107 );
16108 multi_buffer
16109 });
16110 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16111 Editor::new(
16112 EditorMode::Full,
16113 multi_buffer,
16114 Some(project.clone()),
16115 true,
16116 window,
16117 cx,
16118 )
16119 });
16120
16121 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16122 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16123 enum TestHighlight {}
16124 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16125 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16126 editor.highlight_text::<TestHighlight>(
16127 vec![highlight_range.clone()],
16128 HighlightStyle::color(Hsla::green()),
16129 cx,
16130 );
16131 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16132 });
16133
16134 let full_text = format!("\n\n\n{sample_text}\n");
16135 assert_eq!(
16136 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16137 full_text,
16138 );
16139}
16140
16141#[gpui::test]
16142async fn test_inline_completion_text(cx: &mut TestAppContext) {
16143 init_test(cx, |_| {});
16144
16145 // Simple insertion
16146 assert_highlighted_edits(
16147 "Hello, world!",
16148 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16149 true,
16150 cx,
16151 |highlighted_edits, cx| {
16152 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16153 assert_eq!(highlighted_edits.highlights.len(), 1);
16154 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16155 assert_eq!(
16156 highlighted_edits.highlights[0].1.background_color,
16157 Some(cx.theme().status().created_background)
16158 );
16159 },
16160 )
16161 .await;
16162
16163 // Replacement
16164 assert_highlighted_edits(
16165 "This is a test.",
16166 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16167 false,
16168 cx,
16169 |highlighted_edits, cx| {
16170 assert_eq!(highlighted_edits.text, "That is a test.");
16171 assert_eq!(highlighted_edits.highlights.len(), 1);
16172 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16173 assert_eq!(
16174 highlighted_edits.highlights[0].1.background_color,
16175 Some(cx.theme().status().created_background)
16176 );
16177 },
16178 )
16179 .await;
16180
16181 // Multiple edits
16182 assert_highlighted_edits(
16183 "Hello, world!",
16184 vec![
16185 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16186 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16187 ],
16188 false,
16189 cx,
16190 |highlighted_edits, cx| {
16191 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16192 assert_eq!(highlighted_edits.highlights.len(), 2);
16193 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16194 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16195 assert_eq!(
16196 highlighted_edits.highlights[0].1.background_color,
16197 Some(cx.theme().status().created_background)
16198 );
16199 assert_eq!(
16200 highlighted_edits.highlights[1].1.background_color,
16201 Some(cx.theme().status().created_background)
16202 );
16203 },
16204 )
16205 .await;
16206
16207 // Multiple lines with edits
16208 assert_highlighted_edits(
16209 "First line\nSecond line\nThird line\nFourth line",
16210 vec![
16211 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16212 (
16213 Point::new(2, 0)..Point::new(2, 10),
16214 "New third line".to_string(),
16215 ),
16216 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16217 ],
16218 false,
16219 cx,
16220 |highlighted_edits, cx| {
16221 assert_eq!(
16222 highlighted_edits.text,
16223 "Second modified\nNew third line\nFourth updated line"
16224 );
16225 assert_eq!(highlighted_edits.highlights.len(), 3);
16226 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16227 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16228 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16229 for highlight in &highlighted_edits.highlights {
16230 assert_eq!(
16231 highlight.1.background_color,
16232 Some(cx.theme().status().created_background)
16233 );
16234 }
16235 },
16236 )
16237 .await;
16238}
16239
16240#[gpui::test]
16241async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16242 init_test(cx, |_| {});
16243
16244 // Deletion
16245 assert_highlighted_edits(
16246 "Hello, world!",
16247 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16248 true,
16249 cx,
16250 |highlighted_edits, cx| {
16251 assert_eq!(highlighted_edits.text, "Hello, world!");
16252 assert_eq!(highlighted_edits.highlights.len(), 1);
16253 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16254 assert_eq!(
16255 highlighted_edits.highlights[0].1.background_color,
16256 Some(cx.theme().status().deleted_background)
16257 );
16258 },
16259 )
16260 .await;
16261
16262 // Insertion
16263 assert_highlighted_edits(
16264 "Hello, world!",
16265 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16266 true,
16267 cx,
16268 |highlighted_edits, cx| {
16269 assert_eq!(highlighted_edits.highlights.len(), 1);
16270 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16271 assert_eq!(
16272 highlighted_edits.highlights[0].1.background_color,
16273 Some(cx.theme().status().created_background)
16274 );
16275 },
16276 )
16277 .await;
16278}
16279
16280async fn assert_highlighted_edits(
16281 text: &str,
16282 edits: Vec<(Range<Point>, String)>,
16283 include_deletions: bool,
16284 cx: &mut TestAppContext,
16285 assertion_fn: impl Fn(HighlightedText, &App),
16286) {
16287 let window = cx.add_window(|window, cx| {
16288 let buffer = MultiBuffer::build_simple(text, cx);
16289 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16290 });
16291 let cx = &mut VisualTestContext::from_window(*window, cx);
16292
16293 let (buffer, snapshot) = window
16294 .update(cx, |editor, _window, cx| {
16295 (
16296 editor.buffer().clone(),
16297 editor.buffer().read(cx).snapshot(cx),
16298 )
16299 })
16300 .unwrap();
16301
16302 let edits = edits
16303 .into_iter()
16304 .map(|(range, edit)| {
16305 (
16306 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16307 edit,
16308 )
16309 })
16310 .collect::<Vec<_>>();
16311
16312 let text_anchor_edits = edits
16313 .clone()
16314 .into_iter()
16315 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16316 .collect::<Vec<_>>();
16317
16318 let edit_preview = window
16319 .update(cx, |_, _window, cx| {
16320 buffer
16321 .read(cx)
16322 .as_singleton()
16323 .unwrap()
16324 .read(cx)
16325 .preview_edits(text_anchor_edits.into(), cx)
16326 })
16327 .unwrap()
16328 .await;
16329
16330 cx.update(|_window, cx| {
16331 let highlighted_edits = inline_completion_edit_text(
16332 &snapshot.as_singleton().unwrap().2,
16333 &edits,
16334 &edit_preview,
16335 include_deletions,
16336 cx,
16337 );
16338 assertion_fn(highlighted_edits, cx)
16339 });
16340}
16341
16342#[gpui::test]
16343async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16344 init_test(cx, |_| {});
16345 let capabilities = lsp::ServerCapabilities {
16346 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16347 prepare_provider: Some(true),
16348 work_done_progress_options: Default::default(),
16349 })),
16350 ..Default::default()
16351 };
16352 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16353
16354 cx.set_state(indoc! {"
16355 struct Fˇoo {}
16356 "});
16357
16358 cx.update_editor(|editor, _, cx| {
16359 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16360 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16361 editor.highlight_background::<DocumentHighlightRead>(
16362 &[highlight_range],
16363 |c| c.editor_document_highlight_read_background,
16364 cx,
16365 );
16366 });
16367
16368 let mut prepare_rename_handler =
16369 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16370 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16371 start: lsp::Position {
16372 line: 0,
16373 character: 7,
16374 },
16375 end: lsp::Position {
16376 line: 0,
16377 character: 10,
16378 },
16379 })))
16380 });
16381 let prepare_rename_task = cx
16382 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16383 .expect("Prepare rename was not started");
16384 prepare_rename_handler.next().await.unwrap();
16385 prepare_rename_task.await.expect("Prepare rename failed");
16386
16387 let mut rename_handler =
16388 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16389 let edit = lsp::TextEdit {
16390 range: lsp::Range {
16391 start: lsp::Position {
16392 line: 0,
16393 character: 7,
16394 },
16395 end: lsp::Position {
16396 line: 0,
16397 character: 10,
16398 },
16399 },
16400 new_text: "FooRenamed".to_string(),
16401 };
16402 Ok(Some(lsp::WorkspaceEdit::new(
16403 // Specify the same edit twice
16404 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16405 )))
16406 });
16407 let rename_task = cx
16408 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16409 .expect("Confirm rename was not started");
16410 rename_handler.next().await.unwrap();
16411 rename_task.await.expect("Confirm rename failed");
16412 cx.run_until_parked();
16413
16414 // Despite two edits, only one is actually applied as those are identical
16415 cx.assert_editor_state(indoc! {"
16416 struct FooRenamedˇ {}
16417 "});
16418}
16419
16420#[gpui::test]
16421async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16422 init_test(cx, |_| {});
16423 // These capabilities indicate that the server does not support prepare rename.
16424 let capabilities = lsp::ServerCapabilities {
16425 rename_provider: Some(lsp::OneOf::Left(true)),
16426 ..Default::default()
16427 };
16428 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16429
16430 cx.set_state(indoc! {"
16431 struct Fˇoo {}
16432 "});
16433
16434 cx.update_editor(|editor, _window, cx| {
16435 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16436 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16437 editor.highlight_background::<DocumentHighlightRead>(
16438 &[highlight_range],
16439 |c| c.editor_document_highlight_read_background,
16440 cx,
16441 );
16442 });
16443
16444 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16445 .expect("Prepare rename was not started")
16446 .await
16447 .expect("Prepare rename failed");
16448
16449 let mut rename_handler =
16450 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16451 let edit = lsp::TextEdit {
16452 range: lsp::Range {
16453 start: lsp::Position {
16454 line: 0,
16455 character: 7,
16456 },
16457 end: lsp::Position {
16458 line: 0,
16459 character: 10,
16460 },
16461 },
16462 new_text: "FooRenamed".to_string(),
16463 };
16464 Ok(Some(lsp::WorkspaceEdit::new(
16465 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16466 )))
16467 });
16468 let rename_task = cx
16469 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16470 .expect("Confirm rename was not started");
16471 rename_handler.next().await.unwrap();
16472 rename_task.await.expect("Confirm rename failed");
16473 cx.run_until_parked();
16474
16475 // Correct range is renamed, as `surrounding_word` is used to find it.
16476 cx.assert_editor_state(indoc! {"
16477 struct FooRenamedˇ {}
16478 "});
16479}
16480
16481#[gpui::test]
16482async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16483 init_test(cx, |_| {});
16484 let mut cx = EditorTestContext::new(cx).await;
16485
16486 let language = Arc::new(
16487 Language::new(
16488 LanguageConfig::default(),
16489 Some(tree_sitter_html::LANGUAGE.into()),
16490 )
16491 .with_brackets_query(
16492 r#"
16493 ("<" @open "/>" @close)
16494 ("</" @open ">" @close)
16495 ("<" @open ">" @close)
16496 ("\"" @open "\"" @close)
16497 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16498 "#,
16499 )
16500 .unwrap(),
16501 );
16502 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16503
16504 cx.set_state(indoc! {"
16505 <span>ˇ</span>
16506 "});
16507 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16508 cx.assert_editor_state(indoc! {"
16509 <span>
16510 ˇ
16511 </span>
16512 "});
16513
16514 cx.set_state(indoc! {"
16515 <span><span></span>ˇ</span>
16516 "});
16517 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16518 cx.assert_editor_state(indoc! {"
16519 <span><span></span>
16520 ˇ</span>
16521 "});
16522
16523 cx.set_state(indoc! {"
16524 <span>ˇ
16525 </span>
16526 "});
16527 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16528 cx.assert_editor_state(indoc! {"
16529 <span>
16530 ˇ
16531 </span>
16532 "});
16533}
16534
16535fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16536 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16537 point..point
16538}
16539
16540fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16541 let (text, ranges) = marked_text_ranges(marked_text, true);
16542 assert_eq!(editor.text(cx), text);
16543 assert_eq!(
16544 editor.selections.ranges(cx),
16545 ranges,
16546 "Assert selections are {}",
16547 marked_text
16548 );
16549}
16550
16551pub fn handle_signature_help_request(
16552 cx: &mut EditorLspTestContext,
16553 mocked_response: lsp::SignatureHelp,
16554) -> impl Future<Output = ()> {
16555 let mut request =
16556 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16557 let mocked_response = mocked_response.clone();
16558 async move { Ok(Some(mocked_response)) }
16559 });
16560
16561 async move {
16562 request.next().await;
16563 }
16564}
16565
16566/// Handle completion request passing a marked string specifying where the completion
16567/// should be triggered from using '|' character, what range should be replaced, and what completions
16568/// should be returned using '<' and '>' to delimit the range
16569pub fn handle_completion_request(
16570 cx: &mut EditorLspTestContext,
16571 marked_string: &str,
16572 completions: Vec<&'static str>,
16573 counter: Arc<AtomicUsize>,
16574) -> impl Future<Output = ()> {
16575 let complete_from_marker: TextRangeMarker = '|'.into();
16576 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16577 let (_, mut marked_ranges) = marked_text_ranges_by(
16578 marked_string,
16579 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16580 );
16581
16582 let complete_from_position =
16583 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16584 let replace_range =
16585 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16586
16587 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16588 let completions = completions.clone();
16589 counter.fetch_add(1, atomic::Ordering::Release);
16590 async move {
16591 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16592 assert_eq!(
16593 params.text_document_position.position,
16594 complete_from_position
16595 );
16596 Ok(Some(lsp::CompletionResponse::Array(
16597 completions
16598 .iter()
16599 .map(|completion_text| lsp::CompletionItem {
16600 label: completion_text.to_string(),
16601 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16602 range: replace_range,
16603 new_text: completion_text.to_string(),
16604 })),
16605 ..Default::default()
16606 })
16607 .collect(),
16608 )))
16609 }
16610 });
16611
16612 async move {
16613 request.next().await;
16614 }
16615}
16616
16617fn handle_resolve_completion_request(
16618 cx: &mut EditorLspTestContext,
16619 edits: Option<Vec<(&'static str, &'static str)>>,
16620) -> impl Future<Output = ()> {
16621 let edits = edits.map(|edits| {
16622 edits
16623 .iter()
16624 .map(|(marked_string, new_text)| {
16625 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16626 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16627 lsp::TextEdit::new(replace_range, new_text.to_string())
16628 })
16629 .collect::<Vec<_>>()
16630 });
16631
16632 let mut request =
16633 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16634 let edits = edits.clone();
16635 async move {
16636 Ok(lsp::CompletionItem {
16637 additional_text_edits: edits,
16638 ..Default::default()
16639 })
16640 }
16641 });
16642
16643 async move {
16644 request.next().await;
16645 }
16646}
16647
16648pub(crate) fn update_test_language_settings(
16649 cx: &mut TestAppContext,
16650 f: impl Fn(&mut AllLanguageSettingsContent),
16651) {
16652 cx.update(|cx| {
16653 SettingsStore::update_global(cx, |store, cx| {
16654 store.update_user_settings::<AllLanguageSettings>(cx, f);
16655 });
16656 });
16657}
16658
16659pub(crate) fn update_test_project_settings(
16660 cx: &mut TestAppContext,
16661 f: impl Fn(&mut ProjectSettings),
16662) {
16663 cx.update(|cx| {
16664 SettingsStore::update_global(cx, |store, cx| {
16665 store.update_user_settings::<ProjectSettings>(cx, f);
16666 });
16667 });
16668}
16669
16670pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16671 cx.update(|cx| {
16672 assets::Assets.load_test_fonts(cx);
16673 let store = SettingsStore::test(cx);
16674 cx.set_global(store);
16675 theme::init(theme::LoadThemes::JustBase, cx);
16676 release_channel::init(SemanticVersion::default(), cx);
16677 client::init_settings(cx);
16678 language::init(cx);
16679 Project::init_settings(cx);
16680 workspace::init_settings(cx);
16681 crate::init(cx);
16682 });
16683
16684 update_test_language_settings(cx, f);
16685}
16686
16687#[track_caller]
16688fn assert_hunk_revert(
16689 not_reverted_text_with_selections: &str,
16690 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
16691 expected_reverted_text_with_selections: &str,
16692 base_text: &str,
16693 cx: &mut EditorLspTestContext,
16694) {
16695 cx.set_state(not_reverted_text_with_selections);
16696 cx.set_head_text(base_text);
16697 cx.clear_index_text();
16698 cx.executor().run_until_parked();
16699
16700 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16701 let snapshot = editor.snapshot(window, cx);
16702 let reverted_hunk_statuses = snapshot
16703 .buffer_snapshot
16704 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16705 .map(|hunk| hunk.status())
16706 .collect::<Vec<_>>();
16707
16708 editor.git_restore(&Default::default(), window, cx);
16709 reverted_hunk_statuses
16710 });
16711 cx.executor().run_until_parked();
16712 cx.assert_editor_state(expected_reverted_text_with_selections);
16713 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16714}