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_organize_imports_manual_trigger(cx: &mut TestAppContext) {
7880 init_test(cx, |settings| {
7881 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7882 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7883 ))
7884 });
7885
7886 let fs = FakeFs::new(cx.executor());
7887 fs.insert_file(path!("/file.ts"), Default::default()).await;
7888
7889 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7890
7891 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7892 language_registry.add(Arc::new(Language::new(
7893 LanguageConfig {
7894 name: "TypeScript".into(),
7895 matcher: LanguageMatcher {
7896 path_suffixes: vec!["ts".to_string()],
7897 ..Default::default()
7898 },
7899 ..LanguageConfig::default()
7900 },
7901 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
7902 )));
7903 update_test_language_settings(cx, |settings| {
7904 settings.defaults.prettier = Some(PrettierSettings {
7905 allowed: true,
7906 ..PrettierSettings::default()
7907 });
7908 });
7909 let mut fake_servers = language_registry.register_fake_lsp(
7910 "TypeScript",
7911 FakeLspAdapter {
7912 capabilities: lsp::ServerCapabilities {
7913 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
7914 ..Default::default()
7915 },
7916 ..Default::default()
7917 },
7918 );
7919
7920 let buffer = project
7921 .update(cx, |project, cx| {
7922 project.open_local_buffer(path!("/file.ts"), cx)
7923 })
7924 .await
7925 .unwrap();
7926
7927 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7928 let (editor, cx) = cx.add_window_view(|window, cx| {
7929 build_editor_with_project(project.clone(), buffer, window, cx)
7930 });
7931 editor.update_in(cx, |editor, window, cx| {
7932 editor.set_text(
7933 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
7934 window,
7935 cx,
7936 )
7937 });
7938
7939 cx.executor().start_waiting();
7940 let fake_server = fake_servers.next().await.unwrap();
7941
7942 let format = editor
7943 .update_in(cx, |editor, window, cx| {
7944 editor.perform_code_action_kind(
7945 project.clone(),
7946 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
7947 window,
7948 cx,
7949 )
7950 })
7951 .unwrap();
7952 fake_server
7953 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
7954 assert_eq!(
7955 params.text_document.uri,
7956 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
7957 );
7958 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
7959 lsp::CodeAction {
7960 title: "Organize Imports".to_string(),
7961 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
7962 edit: Some(lsp::WorkspaceEdit {
7963 changes: Some(
7964 [(
7965 params.text_document.uri.clone(),
7966 vec![lsp::TextEdit::new(
7967 lsp::Range::new(
7968 lsp::Position::new(1, 0),
7969 lsp::Position::new(2, 0),
7970 ),
7971 "".to_string(),
7972 )],
7973 )]
7974 .into_iter()
7975 .collect(),
7976 ),
7977 ..Default::default()
7978 }),
7979 ..Default::default()
7980 },
7981 )]))
7982 })
7983 .next()
7984 .await;
7985 cx.executor().start_waiting();
7986 format.await;
7987 assert_eq!(
7988 editor.update(cx, |editor, cx| editor.text(cx)),
7989 "import { a } from 'module';\n\nconst x = a;\n"
7990 );
7991
7992 editor.update_in(cx, |editor, window, cx| {
7993 editor.set_text(
7994 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
7995 window,
7996 cx,
7997 )
7998 });
7999 // Ensure we don't lock if code action hangs.
8000 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8001 move |params, _| async move {
8002 assert_eq!(
8003 params.text_document.uri,
8004 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8005 );
8006 futures::future::pending::<()>().await;
8007 unreachable!()
8008 },
8009 );
8010 let format = editor
8011 .update_in(cx, |editor, window, cx| {
8012 editor.perform_code_action_kind(
8013 project,
8014 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8015 window,
8016 cx,
8017 )
8018 })
8019 .unwrap();
8020 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8021 cx.executor().start_waiting();
8022 format.await;
8023 assert_eq!(
8024 editor.update(cx, |editor, cx| editor.text(cx)),
8025 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8026 );
8027}
8028
8029#[gpui::test]
8030async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8031 init_test(cx, |_| {});
8032
8033 let mut cx = EditorLspTestContext::new_rust(
8034 lsp::ServerCapabilities {
8035 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8036 ..Default::default()
8037 },
8038 cx,
8039 )
8040 .await;
8041
8042 cx.set_state(indoc! {"
8043 one.twoˇ
8044 "});
8045
8046 // The format request takes a long time. When it completes, it inserts
8047 // a newline and an indent before the `.`
8048 cx.lsp
8049 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8050 let executor = cx.background_executor().clone();
8051 async move {
8052 executor.timer(Duration::from_millis(100)).await;
8053 Ok(Some(vec![lsp::TextEdit {
8054 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8055 new_text: "\n ".into(),
8056 }]))
8057 }
8058 });
8059
8060 // Submit a format request.
8061 let format_1 = cx
8062 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8063 .unwrap();
8064 cx.executor().run_until_parked();
8065
8066 // Submit a second format request.
8067 let format_2 = cx
8068 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8069 .unwrap();
8070 cx.executor().run_until_parked();
8071
8072 // Wait for both format requests to complete
8073 cx.executor().advance_clock(Duration::from_millis(200));
8074 cx.executor().start_waiting();
8075 format_1.await.unwrap();
8076 cx.executor().start_waiting();
8077 format_2.await.unwrap();
8078
8079 // The formatting edits only happens once.
8080 cx.assert_editor_state(indoc! {"
8081 one
8082 .twoˇ
8083 "});
8084}
8085
8086#[gpui::test]
8087async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8088 init_test(cx, |settings| {
8089 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8090 });
8091
8092 let mut cx = EditorLspTestContext::new_rust(
8093 lsp::ServerCapabilities {
8094 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8095 ..Default::default()
8096 },
8097 cx,
8098 )
8099 .await;
8100
8101 // Set up a buffer white some trailing whitespace and no trailing newline.
8102 cx.set_state(
8103 &[
8104 "one ", //
8105 "twoˇ", //
8106 "three ", //
8107 "four", //
8108 ]
8109 .join("\n"),
8110 );
8111
8112 // Submit a format request.
8113 let format = cx
8114 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8115 .unwrap();
8116
8117 // Record which buffer changes have been sent to the language server
8118 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8119 cx.lsp
8120 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8121 let buffer_changes = buffer_changes.clone();
8122 move |params, _| {
8123 buffer_changes.lock().extend(
8124 params
8125 .content_changes
8126 .into_iter()
8127 .map(|e| (e.range.unwrap(), e.text)),
8128 );
8129 }
8130 });
8131
8132 // Handle formatting requests to the language server.
8133 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8134 let buffer_changes = buffer_changes.clone();
8135 move |_, _| {
8136 // When formatting is requested, trailing whitespace has already been stripped,
8137 // and the trailing newline has already been added.
8138 assert_eq!(
8139 &buffer_changes.lock()[1..],
8140 &[
8141 (
8142 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8143 "".into()
8144 ),
8145 (
8146 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8147 "".into()
8148 ),
8149 (
8150 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8151 "\n".into()
8152 ),
8153 ]
8154 );
8155
8156 // Insert blank lines between each line of the buffer.
8157 async move {
8158 Ok(Some(vec![
8159 lsp::TextEdit {
8160 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8161 new_text: "\n".into(),
8162 },
8163 lsp::TextEdit {
8164 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8165 new_text: "\n".into(),
8166 },
8167 ]))
8168 }
8169 }
8170 });
8171
8172 // After formatting the buffer, the trailing whitespace is stripped,
8173 // a newline is appended, and the edits provided by the language server
8174 // have been applied.
8175 format.await.unwrap();
8176 cx.assert_editor_state(
8177 &[
8178 "one", //
8179 "", //
8180 "twoˇ", //
8181 "", //
8182 "three", //
8183 "four", //
8184 "", //
8185 ]
8186 .join("\n"),
8187 );
8188
8189 // Undoing the formatting undoes the trailing whitespace removal, the
8190 // trailing newline, and the LSP edits.
8191 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8192 cx.assert_editor_state(
8193 &[
8194 "one ", //
8195 "twoˇ", //
8196 "three ", //
8197 "four", //
8198 ]
8199 .join("\n"),
8200 );
8201}
8202
8203#[gpui::test]
8204async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8205 cx: &mut TestAppContext,
8206) {
8207 init_test(cx, |_| {});
8208
8209 cx.update(|cx| {
8210 cx.update_global::<SettingsStore, _>(|settings, cx| {
8211 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8212 settings.auto_signature_help = Some(true);
8213 });
8214 });
8215 });
8216
8217 let mut cx = EditorLspTestContext::new_rust(
8218 lsp::ServerCapabilities {
8219 signature_help_provider: Some(lsp::SignatureHelpOptions {
8220 ..Default::default()
8221 }),
8222 ..Default::default()
8223 },
8224 cx,
8225 )
8226 .await;
8227
8228 let language = Language::new(
8229 LanguageConfig {
8230 name: "Rust".into(),
8231 brackets: BracketPairConfig {
8232 pairs: vec![
8233 BracketPair {
8234 start: "{".to_string(),
8235 end: "}".to_string(),
8236 close: true,
8237 surround: true,
8238 newline: true,
8239 },
8240 BracketPair {
8241 start: "(".to_string(),
8242 end: ")".to_string(),
8243 close: true,
8244 surround: true,
8245 newline: true,
8246 },
8247 BracketPair {
8248 start: "/*".to_string(),
8249 end: " */".to_string(),
8250 close: true,
8251 surround: true,
8252 newline: true,
8253 },
8254 BracketPair {
8255 start: "[".to_string(),
8256 end: "]".to_string(),
8257 close: false,
8258 surround: false,
8259 newline: true,
8260 },
8261 BracketPair {
8262 start: "\"".to_string(),
8263 end: "\"".to_string(),
8264 close: true,
8265 surround: true,
8266 newline: false,
8267 },
8268 BracketPair {
8269 start: "<".to_string(),
8270 end: ">".to_string(),
8271 close: false,
8272 surround: true,
8273 newline: true,
8274 },
8275 ],
8276 ..Default::default()
8277 },
8278 autoclose_before: "})]".to_string(),
8279 ..Default::default()
8280 },
8281 Some(tree_sitter_rust::LANGUAGE.into()),
8282 );
8283 let language = Arc::new(language);
8284
8285 cx.language_registry().add(language.clone());
8286 cx.update_buffer(|buffer, cx| {
8287 buffer.set_language(Some(language), cx);
8288 });
8289
8290 cx.set_state(
8291 &r#"
8292 fn main() {
8293 sampleˇ
8294 }
8295 "#
8296 .unindent(),
8297 );
8298
8299 cx.update_editor(|editor, window, cx| {
8300 editor.handle_input("(", window, cx);
8301 });
8302 cx.assert_editor_state(
8303 &"
8304 fn main() {
8305 sample(ˇ)
8306 }
8307 "
8308 .unindent(),
8309 );
8310
8311 let mocked_response = lsp::SignatureHelp {
8312 signatures: vec![lsp::SignatureInformation {
8313 label: "fn sample(param1: u8, param2: u8)".to_string(),
8314 documentation: None,
8315 parameters: Some(vec![
8316 lsp::ParameterInformation {
8317 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8318 documentation: None,
8319 },
8320 lsp::ParameterInformation {
8321 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8322 documentation: None,
8323 },
8324 ]),
8325 active_parameter: None,
8326 }],
8327 active_signature: Some(0),
8328 active_parameter: Some(0),
8329 };
8330 handle_signature_help_request(&mut cx, mocked_response).await;
8331
8332 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8333 .await;
8334
8335 cx.editor(|editor, _, _| {
8336 let signature_help_state = editor.signature_help_state.popover().cloned();
8337 assert_eq!(
8338 signature_help_state.unwrap().label,
8339 "param1: u8, param2: u8"
8340 );
8341 });
8342}
8343
8344#[gpui::test]
8345async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8346 init_test(cx, |_| {});
8347
8348 cx.update(|cx| {
8349 cx.update_global::<SettingsStore, _>(|settings, cx| {
8350 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8351 settings.auto_signature_help = Some(false);
8352 settings.show_signature_help_after_edits = Some(false);
8353 });
8354 });
8355 });
8356
8357 let mut cx = EditorLspTestContext::new_rust(
8358 lsp::ServerCapabilities {
8359 signature_help_provider: Some(lsp::SignatureHelpOptions {
8360 ..Default::default()
8361 }),
8362 ..Default::default()
8363 },
8364 cx,
8365 )
8366 .await;
8367
8368 let language = Language::new(
8369 LanguageConfig {
8370 name: "Rust".into(),
8371 brackets: BracketPairConfig {
8372 pairs: vec![
8373 BracketPair {
8374 start: "{".to_string(),
8375 end: "}".to_string(),
8376 close: true,
8377 surround: true,
8378 newline: true,
8379 },
8380 BracketPair {
8381 start: "(".to_string(),
8382 end: ")".to_string(),
8383 close: true,
8384 surround: true,
8385 newline: true,
8386 },
8387 BracketPair {
8388 start: "/*".to_string(),
8389 end: " */".to_string(),
8390 close: true,
8391 surround: true,
8392 newline: true,
8393 },
8394 BracketPair {
8395 start: "[".to_string(),
8396 end: "]".to_string(),
8397 close: false,
8398 surround: false,
8399 newline: true,
8400 },
8401 BracketPair {
8402 start: "\"".to_string(),
8403 end: "\"".to_string(),
8404 close: true,
8405 surround: true,
8406 newline: false,
8407 },
8408 BracketPair {
8409 start: "<".to_string(),
8410 end: ">".to_string(),
8411 close: false,
8412 surround: true,
8413 newline: true,
8414 },
8415 ],
8416 ..Default::default()
8417 },
8418 autoclose_before: "})]".to_string(),
8419 ..Default::default()
8420 },
8421 Some(tree_sitter_rust::LANGUAGE.into()),
8422 );
8423 let language = Arc::new(language);
8424
8425 cx.language_registry().add(language.clone());
8426 cx.update_buffer(|buffer, cx| {
8427 buffer.set_language(Some(language), cx);
8428 });
8429
8430 // Ensure that signature_help is not called when no signature help is enabled.
8431 cx.set_state(
8432 &r#"
8433 fn main() {
8434 sampleˇ
8435 }
8436 "#
8437 .unindent(),
8438 );
8439 cx.update_editor(|editor, window, cx| {
8440 editor.handle_input("(", window, cx);
8441 });
8442 cx.assert_editor_state(
8443 &"
8444 fn main() {
8445 sample(ˇ)
8446 }
8447 "
8448 .unindent(),
8449 );
8450 cx.editor(|editor, _, _| {
8451 assert!(editor.signature_help_state.task().is_none());
8452 });
8453
8454 let mocked_response = lsp::SignatureHelp {
8455 signatures: vec![lsp::SignatureInformation {
8456 label: "fn sample(param1: u8, param2: u8)".to_string(),
8457 documentation: None,
8458 parameters: Some(vec![
8459 lsp::ParameterInformation {
8460 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8461 documentation: None,
8462 },
8463 lsp::ParameterInformation {
8464 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8465 documentation: None,
8466 },
8467 ]),
8468 active_parameter: None,
8469 }],
8470 active_signature: Some(0),
8471 active_parameter: Some(0),
8472 };
8473
8474 // Ensure that signature_help is called when enabled afte edits
8475 cx.update(|_, cx| {
8476 cx.update_global::<SettingsStore, _>(|settings, cx| {
8477 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8478 settings.auto_signature_help = Some(false);
8479 settings.show_signature_help_after_edits = Some(true);
8480 });
8481 });
8482 });
8483 cx.set_state(
8484 &r#"
8485 fn main() {
8486 sampleˇ
8487 }
8488 "#
8489 .unindent(),
8490 );
8491 cx.update_editor(|editor, window, cx| {
8492 editor.handle_input("(", window, cx);
8493 });
8494 cx.assert_editor_state(
8495 &"
8496 fn main() {
8497 sample(ˇ)
8498 }
8499 "
8500 .unindent(),
8501 );
8502 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8503 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8504 .await;
8505 cx.update_editor(|editor, _, _| {
8506 let signature_help_state = editor.signature_help_state.popover().cloned();
8507 assert!(signature_help_state.is_some());
8508 assert_eq!(
8509 signature_help_state.unwrap().label,
8510 "param1: u8, param2: u8"
8511 );
8512 editor.signature_help_state = SignatureHelpState::default();
8513 });
8514
8515 // Ensure that signature_help is called when auto signature help override is enabled
8516 cx.update(|_, cx| {
8517 cx.update_global::<SettingsStore, _>(|settings, cx| {
8518 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8519 settings.auto_signature_help = Some(true);
8520 settings.show_signature_help_after_edits = Some(false);
8521 });
8522 });
8523 });
8524 cx.set_state(
8525 &r#"
8526 fn main() {
8527 sampleˇ
8528 }
8529 "#
8530 .unindent(),
8531 );
8532 cx.update_editor(|editor, window, cx| {
8533 editor.handle_input("(", window, cx);
8534 });
8535 cx.assert_editor_state(
8536 &"
8537 fn main() {
8538 sample(ˇ)
8539 }
8540 "
8541 .unindent(),
8542 );
8543 handle_signature_help_request(&mut cx, mocked_response).await;
8544 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8545 .await;
8546 cx.editor(|editor, _, _| {
8547 let signature_help_state = editor.signature_help_state.popover().cloned();
8548 assert!(signature_help_state.is_some());
8549 assert_eq!(
8550 signature_help_state.unwrap().label,
8551 "param1: u8, param2: u8"
8552 );
8553 });
8554}
8555
8556#[gpui::test]
8557async fn test_signature_help(cx: &mut TestAppContext) {
8558 init_test(cx, |_| {});
8559 cx.update(|cx| {
8560 cx.update_global::<SettingsStore, _>(|settings, cx| {
8561 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8562 settings.auto_signature_help = Some(true);
8563 });
8564 });
8565 });
8566
8567 let mut cx = EditorLspTestContext::new_rust(
8568 lsp::ServerCapabilities {
8569 signature_help_provider: Some(lsp::SignatureHelpOptions {
8570 ..Default::default()
8571 }),
8572 ..Default::default()
8573 },
8574 cx,
8575 )
8576 .await;
8577
8578 // A test that directly calls `show_signature_help`
8579 cx.update_editor(|editor, window, cx| {
8580 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8581 });
8582
8583 let mocked_response = lsp::SignatureHelp {
8584 signatures: vec![lsp::SignatureInformation {
8585 label: "fn sample(param1: u8, param2: u8)".to_string(),
8586 documentation: None,
8587 parameters: Some(vec![
8588 lsp::ParameterInformation {
8589 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8590 documentation: None,
8591 },
8592 lsp::ParameterInformation {
8593 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8594 documentation: None,
8595 },
8596 ]),
8597 active_parameter: None,
8598 }],
8599 active_signature: Some(0),
8600 active_parameter: Some(0),
8601 };
8602 handle_signature_help_request(&mut cx, mocked_response).await;
8603
8604 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8605 .await;
8606
8607 cx.editor(|editor, _, _| {
8608 let signature_help_state = editor.signature_help_state.popover().cloned();
8609 assert!(signature_help_state.is_some());
8610 assert_eq!(
8611 signature_help_state.unwrap().label,
8612 "param1: u8, param2: u8"
8613 );
8614 });
8615
8616 // When exiting outside from inside the brackets, `signature_help` is closed.
8617 cx.set_state(indoc! {"
8618 fn main() {
8619 sample(ˇ);
8620 }
8621
8622 fn sample(param1: u8, param2: u8) {}
8623 "});
8624
8625 cx.update_editor(|editor, window, cx| {
8626 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8627 });
8628
8629 let mocked_response = lsp::SignatureHelp {
8630 signatures: Vec::new(),
8631 active_signature: None,
8632 active_parameter: None,
8633 };
8634 handle_signature_help_request(&mut cx, mocked_response).await;
8635
8636 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8637 .await;
8638
8639 cx.editor(|editor, _, _| {
8640 assert!(!editor.signature_help_state.is_shown());
8641 });
8642
8643 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8644 cx.set_state(indoc! {"
8645 fn main() {
8646 sample(ˇ);
8647 }
8648
8649 fn sample(param1: u8, param2: u8) {}
8650 "});
8651
8652 let mocked_response = lsp::SignatureHelp {
8653 signatures: vec![lsp::SignatureInformation {
8654 label: "fn sample(param1: u8, param2: u8)".to_string(),
8655 documentation: None,
8656 parameters: Some(vec![
8657 lsp::ParameterInformation {
8658 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8659 documentation: None,
8660 },
8661 lsp::ParameterInformation {
8662 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8663 documentation: None,
8664 },
8665 ]),
8666 active_parameter: None,
8667 }],
8668 active_signature: Some(0),
8669 active_parameter: Some(0),
8670 };
8671 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8672 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8673 .await;
8674 cx.editor(|editor, _, _| {
8675 assert!(editor.signature_help_state.is_shown());
8676 });
8677
8678 // Restore the popover with more parameter input
8679 cx.set_state(indoc! {"
8680 fn main() {
8681 sample(param1, param2ˇ);
8682 }
8683
8684 fn sample(param1: u8, param2: u8) {}
8685 "});
8686
8687 let mocked_response = lsp::SignatureHelp {
8688 signatures: vec![lsp::SignatureInformation {
8689 label: "fn sample(param1: u8, param2: u8)".to_string(),
8690 documentation: None,
8691 parameters: Some(vec![
8692 lsp::ParameterInformation {
8693 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8694 documentation: None,
8695 },
8696 lsp::ParameterInformation {
8697 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8698 documentation: None,
8699 },
8700 ]),
8701 active_parameter: None,
8702 }],
8703 active_signature: Some(0),
8704 active_parameter: Some(1),
8705 };
8706 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8707 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8708 .await;
8709
8710 // When selecting a range, the popover is gone.
8711 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8712 cx.update_editor(|editor, window, cx| {
8713 editor.change_selections(None, window, cx, |s| {
8714 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8715 })
8716 });
8717 cx.assert_editor_state(indoc! {"
8718 fn main() {
8719 sample(param1, «ˇparam2»);
8720 }
8721
8722 fn sample(param1: u8, param2: u8) {}
8723 "});
8724 cx.editor(|editor, _, _| {
8725 assert!(!editor.signature_help_state.is_shown());
8726 });
8727
8728 // When unselecting again, the popover is back if within the brackets.
8729 cx.update_editor(|editor, window, cx| {
8730 editor.change_selections(None, window, cx, |s| {
8731 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8732 })
8733 });
8734 cx.assert_editor_state(indoc! {"
8735 fn main() {
8736 sample(param1, ˇparam2);
8737 }
8738
8739 fn sample(param1: u8, param2: u8) {}
8740 "});
8741 handle_signature_help_request(&mut cx, mocked_response).await;
8742 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8743 .await;
8744 cx.editor(|editor, _, _| {
8745 assert!(editor.signature_help_state.is_shown());
8746 });
8747
8748 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8749 cx.update_editor(|editor, window, cx| {
8750 editor.change_selections(None, window, cx, |s| {
8751 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8752 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8753 })
8754 });
8755 cx.assert_editor_state(indoc! {"
8756 fn main() {
8757 sample(param1, ˇparam2);
8758 }
8759
8760 fn sample(param1: u8, param2: u8) {}
8761 "});
8762
8763 let mocked_response = lsp::SignatureHelp {
8764 signatures: vec![lsp::SignatureInformation {
8765 label: "fn sample(param1: u8, param2: u8)".to_string(),
8766 documentation: None,
8767 parameters: Some(vec![
8768 lsp::ParameterInformation {
8769 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8770 documentation: None,
8771 },
8772 lsp::ParameterInformation {
8773 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8774 documentation: None,
8775 },
8776 ]),
8777 active_parameter: None,
8778 }],
8779 active_signature: Some(0),
8780 active_parameter: Some(1),
8781 };
8782 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8783 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8784 .await;
8785 cx.update_editor(|editor, _, cx| {
8786 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8787 });
8788 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8789 .await;
8790 cx.update_editor(|editor, window, cx| {
8791 editor.change_selections(None, window, cx, |s| {
8792 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8793 })
8794 });
8795 cx.assert_editor_state(indoc! {"
8796 fn main() {
8797 sample(param1, «ˇparam2»);
8798 }
8799
8800 fn sample(param1: u8, param2: u8) {}
8801 "});
8802 cx.update_editor(|editor, window, cx| {
8803 editor.change_selections(None, window, cx, |s| {
8804 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8805 })
8806 });
8807 cx.assert_editor_state(indoc! {"
8808 fn main() {
8809 sample(param1, ˇparam2);
8810 }
8811
8812 fn sample(param1: u8, param2: u8) {}
8813 "});
8814 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8815 .await;
8816}
8817
8818#[gpui::test]
8819async fn test_completion(cx: &mut TestAppContext) {
8820 init_test(cx, |_| {});
8821
8822 let mut cx = EditorLspTestContext::new_rust(
8823 lsp::ServerCapabilities {
8824 completion_provider: Some(lsp::CompletionOptions {
8825 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8826 resolve_provider: Some(true),
8827 ..Default::default()
8828 }),
8829 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8830 ..Default::default()
8831 },
8832 cx,
8833 )
8834 .await;
8835 let counter = Arc::new(AtomicUsize::new(0));
8836
8837 cx.set_state(indoc! {"
8838 oneˇ
8839 two
8840 three
8841 "});
8842 cx.simulate_keystroke(".");
8843 handle_completion_request(
8844 &mut cx,
8845 indoc! {"
8846 one.|<>
8847 two
8848 three
8849 "},
8850 vec!["first_completion", "second_completion"],
8851 counter.clone(),
8852 )
8853 .await;
8854 cx.condition(|editor, _| editor.context_menu_visible())
8855 .await;
8856 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8857
8858 let _handler = handle_signature_help_request(
8859 &mut cx,
8860 lsp::SignatureHelp {
8861 signatures: vec![lsp::SignatureInformation {
8862 label: "test signature".to_string(),
8863 documentation: None,
8864 parameters: Some(vec![lsp::ParameterInformation {
8865 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8866 documentation: None,
8867 }]),
8868 active_parameter: None,
8869 }],
8870 active_signature: None,
8871 active_parameter: None,
8872 },
8873 );
8874 cx.update_editor(|editor, window, cx| {
8875 assert!(
8876 !editor.signature_help_state.is_shown(),
8877 "No signature help was called for"
8878 );
8879 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8880 });
8881 cx.run_until_parked();
8882 cx.update_editor(|editor, _, _| {
8883 assert!(
8884 !editor.signature_help_state.is_shown(),
8885 "No signature help should be shown when completions menu is open"
8886 );
8887 });
8888
8889 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8890 editor.context_menu_next(&Default::default(), window, cx);
8891 editor
8892 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8893 .unwrap()
8894 });
8895 cx.assert_editor_state(indoc! {"
8896 one.second_completionˇ
8897 two
8898 three
8899 "});
8900
8901 handle_resolve_completion_request(
8902 &mut cx,
8903 Some(vec![
8904 (
8905 //This overlaps with the primary completion edit which is
8906 //misbehavior from the LSP spec, test that we filter it out
8907 indoc! {"
8908 one.second_ˇcompletion
8909 two
8910 threeˇ
8911 "},
8912 "overlapping additional edit",
8913 ),
8914 (
8915 indoc! {"
8916 one.second_completion
8917 two
8918 threeˇ
8919 "},
8920 "\nadditional edit",
8921 ),
8922 ]),
8923 )
8924 .await;
8925 apply_additional_edits.await.unwrap();
8926 cx.assert_editor_state(indoc! {"
8927 one.second_completionˇ
8928 two
8929 three
8930 additional edit
8931 "});
8932
8933 cx.set_state(indoc! {"
8934 one.second_completion
8935 twoˇ
8936 threeˇ
8937 additional edit
8938 "});
8939 cx.simulate_keystroke(" ");
8940 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8941 cx.simulate_keystroke("s");
8942 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8943
8944 cx.assert_editor_state(indoc! {"
8945 one.second_completion
8946 two sˇ
8947 three sˇ
8948 additional edit
8949 "});
8950 handle_completion_request(
8951 &mut cx,
8952 indoc! {"
8953 one.second_completion
8954 two s
8955 three <s|>
8956 additional edit
8957 "},
8958 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8959 counter.clone(),
8960 )
8961 .await;
8962 cx.condition(|editor, _| editor.context_menu_visible())
8963 .await;
8964 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8965
8966 cx.simulate_keystroke("i");
8967
8968 handle_completion_request(
8969 &mut cx,
8970 indoc! {"
8971 one.second_completion
8972 two si
8973 three <si|>
8974 additional edit
8975 "},
8976 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8977 counter.clone(),
8978 )
8979 .await;
8980 cx.condition(|editor, _| editor.context_menu_visible())
8981 .await;
8982 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8983
8984 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8985 editor
8986 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8987 .unwrap()
8988 });
8989 cx.assert_editor_state(indoc! {"
8990 one.second_completion
8991 two sixth_completionˇ
8992 three sixth_completionˇ
8993 additional edit
8994 "});
8995
8996 apply_additional_edits.await.unwrap();
8997
8998 update_test_language_settings(&mut cx, |settings| {
8999 settings.defaults.show_completions_on_input = Some(false);
9000 });
9001 cx.set_state("editorˇ");
9002 cx.simulate_keystroke(".");
9003 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9004 cx.simulate_keystroke("c");
9005 cx.simulate_keystroke("l");
9006 cx.simulate_keystroke("o");
9007 cx.assert_editor_state("editor.cloˇ");
9008 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9009 cx.update_editor(|editor, window, cx| {
9010 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9011 });
9012 handle_completion_request(
9013 &mut cx,
9014 "editor.<clo|>",
9015 vec!["close", "clobber"],
9016 counter.clone(),
9017 )
9018 .await;
9019 cx.condition(|editor, _| editor.context_menu_visible())
9020 .await;
9021 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9022
9023 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9024 editor
9025 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9026 .unwrap()
9027 });
9028 cx.assert_editor_state("editor.closeˇ");
9029 handle_resolve_completion_request(&mut cx, None).await;
9030 apply_additional_edits.await.unwrap();
9031}
9032
9033#[gpui::test]
9034async fn test_multiline_completion(cx: &mut TestAppContext) {
9035 init_test(cx, |_| {});
9036
9037 let fs = FakeFs::new(cx.executor());
9038 fs.insert_tree(
9039 path!("/a"),
9040 json!({
9041 "main.ts": "a",
9042 }),
9043 )
9044 .await;
9045
9046 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9047 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9048 let typescript_language = Arc::new(Language::new(
9049 LanguageConfig {
9050 name: "TypeScript".into(),
9051 matcher: LanguageMatcher {
9052 path_suffixes: vec!["ts".to_string()],
9053 ..LanguageMatcher::default()
9054 },
9055 line_comments: vec!["// ".into()],
9056 ..LanguageConfig::default()
9057 },
9058 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9059 ));
9060 language_registry.add(typescript_language.clone());
9061 let mut fake_servers = language_registry.register_fake_lsp(
9062 "TypeScript",
9063 FakeLspAdapter {
9064 capabilities: lsp::ServerCapabilities {
9065 completion_provider: Some(lsp::CompletionOptions {
9066 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9067 ..lsp::CompletionOptions::default()
9068 }),
9069 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9070 ..lsp::ServerCapabilities::default()
9071 },
9072 // Emulate vtsls label generation
9073 label_for_completion: Some(Box::new(|item, _| {
9074 let text = if let Some(description) = item
9075 .label_details
9076 .as_ref()
9077 .and_then(|label_details| label_details.description.as_ref())
9078 {
9079 format!("{} {}", item.label, description)
9080 } else if let Some(detail) = &item.detail {
9081 format!("{} {}", item.label, detail)
9082 } else {
9083 item.label.clone()
9084 };
9085 let len = text.len();
9086 Some(language::CodeLabel {
9087 text,
9088 runs: Vec::new(),
9089 filter_range: 0..len,
9090 })
9091 })),
9092 ..FakeLspAdapter::default()
9093 },
9094 );
9095 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9096 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9097 let worktree_id = workspace
9098 .update(cx, |workspace, _window, cx| {
9099 workspace.project().update(cx, |project, cx| {
9100 project.worktrees(cx).next().unwrap().read(cx).id()
9101 })
9102 })
9103 .unwrap();
9104 let _buffer = project
9105 .update(cx, |project, cx| {
9106 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9107 })
9108 .await
9109 .unwrap();
9110 let editor = workspace
9111 .update(cx, |workspace, window, cx| {
9112 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9113 })
9114 .unwrap()
9115 .await
9116 .unwrap()
9117 .downcast::<Editor>()
9118 .unwrap();
9119 let fake_server = fake_servers.next().await.unwrap();
9120
9121 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9122 let multiline_label_2 = "a\nb\nc\n";
9123 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9124 let multiline_description = "d\ne\nf\n";
9125 let multiline_detail_2 = "g\nh\ni\n";
9126
9127 let mut completion_handle =
9128 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9129 Ok(Some(lsp::CompletionResponse::Array(vec![
9130 lsp::CompletionItem {
9131 label: multiline_label.to_string(),
9132 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9133 range: lsp::Range {
9134 start: lsp::Position {
9135 line: params.text_document_position.position.line,
9136 character: params.text_document_position.position.character,
9137 },
9138 end: lsp::Position {
9139 line: params.text_document_position.position.line,
9140 character: params.text_document_position.position.character,
9141 },
9142 },
9143 new_text: "new_text_1".to_string(),
9144 })),
9145 ..lsp::CompletionItem::default()
9146 },
9147 lsp::CompletionItem {
9148 label: "single line label 1".to_string(),
9149 detail: Some(multiline_detail.to_string()),
9150 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9151 range: lsp::Range {
9152 start: lsp::Position {
9153 line: params.text_document_position.position.line,
9154 character: params.text_document_position.position.character,
9155 },
9156 end: lsp::Position {
9157 line: params.text_document_position.position.line,
9158 character: params.text_document_position.position.character,
9159 },
9160 },
9161 new_text: "new_text_2".to_string(),
9162 })),
9163 ..lsp::CompletionItem::default()
9164 },
9165 lsp::CompletionItem {
9166 label: "single line label 2".to_string(),
9167 label_details: Some(lsp::CompletionItemLabelDetails {
9168 description: Some(multiline_description.to_string()),
9169 detail: None,
9170 }),
9171 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9172 range: lsp::Range {
9173 start: lsp::Position {
9174 line: params.text_document_position.position.line,
9175 character: params.text_document_position.position.character,
9176 },
9177 end: lsp::Position {
9178 line: params.text_document_position.position.line,
9179 character: params.text_document_position.position.character,
9180 },
9181 },
9182 new_text: "new_text_2".to_string(),
9183 })),
9184 ..lsp::CompletionItem::default()
9185 },
9186 lsp::CompletionItem {
9187 label: multiline_label_2.to_string(),
9188 detail: Some(multiline_detail_2.to_string()),
9189 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9190 range: lsp::Range {
9191 start: lsp::Position {
9192 line: params.text_document_position.position.line,
9193 character: params.text_document_position.position.character,
9194 },
9195 end: lsp::Position {
9196 line: params.text_document_position.position.line,
9197 character: params.text_document_position.position.character,
9198 },
9199 },
9200 new_text: "new_text_3".to_string(),
9201 })),
9202 ..lsp::CompletionItem::default()
9203 },
9204 lsp::CompletionItem {
9205 label: "Label with many spaces and \t but without newlines".to_string(),
9206 detail: Some(
9207 "Details with many spaces and \t but without newlines".to_string(),
9208 ),
9209 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9210 range: lsp::Range {
9211 start: lsp::Position {
9212 line: params.text_document_position.position.line,
9213 character: params.text_document_position.position.character,
9214 },
9215 end: lsp::Position {
9216 line: params.text_document_position.position.line,
9217 character: params.text_document_position.position.character,
9218 },
9219 },
9220 new_text: "new_text_4".to_string(),
9221 })),
9222 ..lsp::CompletionItem::default()
9223 },
9224 ])))
9225 });
9226
9227 editor.update_in(cx, |editor, window, cx| {
9228 cx.focus_self(window);
9229 editor.move_to_end(&MoveToEnd, window, cx);
9230 editor.handle_input(".", window, cx);
9231 });
9232 cx.run_until_parked();
9233 completion_handle.next().await.unwrap();
9234
9235 editor.update(cx, |editor, _| {
9236 assert!(editor.context_menu_visible());
9237 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9238 {
9239 let completion_labels = menu
9240 .completions
9241 .borrow()
9242 .iter()
9243 .map(|c| c.label.text.clone())
9244 .collect::<Vec<_>>();
9245 assert_eq!(
9246 completion_labels,
9247 &[
9248 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9249 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9250 "single line label 2 d e f ",
9251 "a b c g h i ",
9252 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9253 ],
9254 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9255 );
9256
9257 for completion in menu
9258 .completions
9259 .borrow()
9260 .iter() {
9261 assert_eq!(
9262 completion.label.filter_range,
9263 0..completion.label.text.len(),
9264 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9265 );
9266 }
9267
9268 } else {
9269 panic!("expected completion menu to be open");
9270 }
9271 });
9272}
9273
9274#[gpui::test]
9275async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9276 init_test(cx, |_| {});
9277 let mut cx = EditorLspTestContext::new_rust(
9278 lsp::ServerCapabilities {
9279 completion_provider: Some(lsp::CompletionOptions {
9280 trigger_characters: Some(vec![".".to_string()]),
9281 ..Default::default()
9282 }),
9283 ..Default::default()
9284 },
9285 cx,
9286 )
9287 .await;
9288 cx.lsp
9289 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9290 Ok(Some(lsp::CompletionResponse::Array(vec![
9291 lsp::CompletionItem {
9292 label: "first".into(),
9293 ..Default::default()
9294 },
9295 lsp::CompletionItem {
9296 label: "last".into(),
9297 ..Default::default()
9298 },
9299 ])))
9300 });
9301 cx.set_state("variableˇ");
9302 cx.simulate_keystroke(".");
9303 cx.executor().run_until_parked();
9304
9305 cx.update_editor(|editor, _, _| {
9306 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9307 {
9308 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9309 } else {
9310 panic!("expected completion menu to be open");
9311 }
9312 });
9313
9314 cx.update_editor(|editor, window, cx| {
9315 editor.move_page_down(&MovePageDown::default(), window, cx);
9316 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9317 {
9318 assert!(
9319 menu.selected_item == 1,
9320 "expected PageDown to select the last item from the context menu"
9321 );
9322 } else {
9323 panic!("expected completion menu to stay open after PageDown");
9324 }
9325 });
9326
9327 cx.update_editor(|editor, window, cx| {
9328 editor.move_page_up(&MovePageUp::default(), window, cx);
9329 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9330 {
9331 assert!(
9332 menu.selected_item == 0,
9333 "expected PageUp to select the first item from the context menu"
9334 );
9335 } else {
9336 panic!("expected completion menu to stay open after PageUp");
9337 }
9338 });
9339}
9340
9341#[gpui::test]
9342async fn test_completion_sort(cx: &mut TestAppContext) {
9343 init_test(cx, |_| {});
9344 let mut cx = EditorLspTestContext::new_rust(
9345 lsp::ServerCapabilities {
9346 completion_provider: Some(lsp::CompletionOptions {
9347 trigger_characters: Some(vec![".".to_string()]),
9348 ..Default::default()
9349 }),
9350 ..Default::default()
9351 },
9352 cx,
9353 )
9354 .await;
9355 cx.lsp
9356 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9357 Ok(Some(lsp::CompletionResponse::Array(vec![
9358 lsp::CompletionItem {
9359 label: "Range".into(),
9360 sort_text: Some("a".into()),
9361 ..Default::default()
9362 },
9363 lsp::CompletionItem {
9364 label: "r".into(),
9365 sort_text: Some("b".into()),
9366 ..Default::default()
9367 },
9368 lsp::CompletionItem {
9369 label: "ret".into(),
9370 sort_text: Some("c".into()),
9371 ..Default::default()
9372 },
9373 lsp::CompletionItem {
9374 label: "return".into(),
9375 sort_text: Some("d".into()),
9376 ..Default::default()
9377 },
9378 lsp::CompletionItem {
9379 label: "slice".into(),
9380 sort_text: Some("d".into()),
9381 ..Default::default()
9382 },
9383 ])))
9384 });
9385 cx.set_state("rˇ");
9386 cx.executor().run_until_parked();
9387 cx.update_editor(|editor, window, cx| {
9388 editor.show_completions(
9389 &ShowCompletions {
9390 trigger: Some("r".into()),
9391 },
9392 window,
9393 cx,
9394 );
9395 });
9396 cx.executor().run_until_parked();
9397
9398 cx.update_editor(|editor, _, _| {
9399 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9400 {
9401 assert_eq!(
9402 completion_menu_entries(&menu),
9403 &["r", "ret", "Range", "return"]
9404 );
9405 } else {
9406 panic!("expected completion menu to be open");
9407 }
9408 });
9409}
9410
9411#[gpui::test]
9412async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9413 init_test(cx, |_| {});
9414
9415 let mut cx = EditorLspTestContext::new_rust(
9416 lsp::ServerCapabilities {
9417 completion_provider: Some(lsp::CompletionOptions {
9418 trigger_characters: Some(vec![".".to_string()]),
9419 resolve_provider: Some(true),
9420 ..Default::default()
9421 }),
9422 ..Default::default()
9423 },
9424 cx,
9425 )
9426 .await;
9427
9428 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9429 cx.simulate_keystroke(".");
9430 let completion_item = lsp::CompletionItem {
9431 label: "Some".into(),
9432 kind: Some(lsp::CompletionItemKind::SNIPPET),
9433 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9434 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9435 kind: lsp::MarkupKind::Markdown,
9436 value: "```rust\nSome(2)\n```".to_string(),
9437 })),
9438 deprecated: Some(false),
9439 sort_text: Some("Some".to_string()),
9440 filter_text: Some("Some".to_string()),
9441 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9442 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9443 range: lsp::Range {
9444 start: lsp::Position {
9445 line: 0,
9446 character: 22,
9447 },
9448 end: lsp::Position {
9449 line: 0,
9450 character: 22,
9451 },
9452 },
9453 new_text: "Some(2)".to_string(),
9454 })),
9455 additional_text_edits: Some(vec![lsp::TextEdit {
9456 range: lsp::Range {
9457 start: lsp::Position {
9458 line: 0,
9459 character: 20,
9460 },
9461 end: lsp::Position {
9462 line: 0,
9463 character: 22,
9464 },
9465 },
9466 new_text: "".to_string(),
9467 }]),
9468 ..Default::default()
9469 };
9470
9471 let closure_completion_item = completion_item.clone();
9472 let counter = Arc::new(AtomicUsize::new(0));
9473 let counter_clone = counter.clone();
9474 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9475 let task_completion_item = closure_completion_item.clone();
9476 counter_clone.fetch_add(1, atomic::Ordering::Release);
9477 async move {
9478 Ok(Some(lsp::CompletionResponse::Array(vec![
9479 task_completion_item,
9480 ])))
9481 }
9482 });
9483
9484 cx.condition(|editor, _| editor.context_menu_visible())
9485 .await;
9486 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9487 assert!(request.next().await.is_some());
9488 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9489
9490 cx.simulate_keystroke("S");
9491 cx.simulate_keystroke("o");
9492 cx.simulate_keystroke("m");
9493 cx.condition(|editor, _| editor.context_menu_visible())
9494 .await;
9495 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9496 assert!(request.next().await.is_some());
9497 assert!(request.next().await.is_some());
9498 assert!(request.next().await.is_some());
9499 request.close();
9500 assert!(request.next().await.is_none());
9501 assert_eq!(
9502 counter.load(atomic::Ordering::Acquire),
9503 4,
9504 "With the completions menu open, only one LSP request should happen per input"
9505 );
9506}
9507
9508#[gpui::test]
9509async fn test_toggle_comment(cx: &mut TestAppContext) {
9510 init_test(cx, |_| {});
9511 let mut cx = EditorTestContext::new(cx).await;
9512 let language = Arc::new(Language::new(
9513 LanguageConfig {
9514 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9515 ..Default::default()
9516 },
9517 Some(tree_sitter_rust::LANGUAGE.into()),
9518 ));
9519 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9520
9521 // If multiple selections intersect a line, the line is only toggled once.
9522 cx.set_state(indoc! {"
9523 fn a() {
9524 «//b();
9525 ˇ»// «c();
9526 //ˇ» d();
9527 }
9528 "});
9529
9530 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9531
9532 cx.assert_editor_state(indoc! {"
9533 fn a() {
9534 «b();
9535 c();
9536 ˇ» d();
9537 }
9538 "});
9539
9540 // The comment prefix is inserted at the same column for every line in a
9541 // selection.
9542 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9543
9544 cx.assert_editor_state(indoc! {"
9545 fn a() {
9546 // «b();
9547 // c();
9548 ˇ»// d();
9549 }
9550 "});
9551
9552 // If a selection ends at the beginning of a line, that line is not toggled.
9553 cx.set_selections_state(indoc! {"
9554 fn a() {
9555 // b();
9556 «// c();
9557 ˇ» // d();
9558 }
9559 "});
9560
9561 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9562
9563 cx.assert_editor_state(indoc! {"
9564 fn a() {
9565 // b();
9566 «c();
9567 ˇ» // d();
9568 }
9569 "});
9570
9571 // If a selection span a single line and is empty, the line is toggled.
9572 cx.set_state(indoc! {"
9573 fn a() {
9574 a();
9575 b();
9576 ˇ
9577 }
9578 "});
9579
9580 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9581
9582 cx.assert_editor_state(indoc! {"
9583 fn a() {
9584 a();
9585 b();
9586 //•ˇ
9587 }
9588 "});
9589
9590 // If a selection span multiple lines, empty lines are not toggled.
9591 cx.set_state(indoc! {"
9592 fn a() {
9593 «a();
9594
9595 c();ˇ»
9596 }
9597 "});
9598
9599 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9600
9601 cx.assert_editor_state(indoc! {"
9602 fn a() {
9603 // «a();
9604
9605 // c();ˇ»
9606 }
9607 "});
9608
9609 // If a selection includes multiple comment prefixes, all lines are uncommented.
9610 cx.set_state(indoc! {"
9611 fn a() {
9612 «// a();
9613 /// b();
9614 //! c();ˇ»
9615 }
9616 "});
9617
9618 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9619
9620 cx.assert_editor_state(indoc! {"
9621 fn a() {
9622 «a();
9623 b();
9624 c();ˇ»
9625 }
9626 "});
9627}
9628
9629#[gpui::test]
9630async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9631 init_test(cx, |_| {});
9632 let mut cx = EditorTestContext::new(cx).await;
9633 let language = Arc::new(Language::new(
9634 LanguageConfig {
9635 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9636 ..Default::default()
9637 },
9638 Some(tree_sitter_rust::LANGUAGE.into()),
9639 ));
9640 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9641
9642 let toggle_comments = &ToggleComments {
9643 advance_downwards: false,
9644 ignore_indent: true,
9645 };
9646
9647 // If multiple selections intersect a line, the line is only toggled once.
9648 cx.set_state(indoc! {"
9649 fn a() {
9650 // «b();
9651 // c();
9652 // ˇ» d();
9653 }
9654 "});
9655
9656 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9657
9658 cx.assert_editor_state(indoc! {"
9659 fn a() {
9660 «b();
9661 c();
9662 ˇ» d();
9663 }
9664 "});
9665
9666 // The comment prefix is inserted at the beginning of each line
9667 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9668
9669 cx.assert_editor_state(indoc! {"
9670 fn a() {
9671 // «b();
9672 // c();
9673 // ˇ» d();
9674 }
9675 "});
9676
9677 // If a selection ends at the beginning of a line, that line is not toggled.
9678 cx.set_selections_state(indoc! {"
9679 fn a() {
9680 // b();
9681 // «c();
9682 ˇ»// d();
9683 }
9684 "});
9685
9686 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9687
9688 cx.assert_editor_state(indoc! {"
9689 fn a() {
9690 // b();
9691 «c();
9692 ˇ»// d();
9693 }
9694 "});
9695
9696 // If a selection span a single line and is empty, the line is toggled.
9697 cx.set_state(indoc! {"
9698 fn a() {
9699 a();
9700 b();
9701 ˇ
9702 }
9703 "});
9704
9705 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9706
9707 cx.assert_editor_state(indoc! {"
9708 fn a() {
9709 a();
9710 b();
9711 //ˇ
9712 }
9713 "});
9714
9715 // If a selection span multiple lines, empty lines are not toggled.
9716 cx.set_state(indoc! {"
9717 fn a() {
9718 «a();
9719
9720 c();ˇ»
9721 }
9722 "});
9723
9724 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9725
9726 cx.assert_editor_state(indoc! {"
9727 fn a() {
9728 // «a();
9729
9730 // c();ˇ»
9731 }
9732 "});
9733
9734 // If a selection includes multiple comment prefixes, all lines are uncommented.
9735 cx.set_state(indoc! {"
9736 fn a() {
9737 // «a();
9738 /// b();
9739 //! c();ˇ»
9740 }
9741 "});
9742
9743 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9744
9745 cx.assert_editor_state(indoc! {"
9746 fn a() {
9747 «a();
9748 b();
9749 c();ˇ»
9750 }
9751 "});
9752}
9753
9754#[gpui::test]
9755async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9756 init_test(cx, |_| {});
9757
9758 let language = Arc::new(Language::new(
9759 LanguageConfig {
9760 line_comments: vec!["// ".into()],
9761 ..Default::default()
9762 },
9763 Some(tree_sitter_rust::LANGUAGE.into()),
9764 ));
9765
9766 let mut cx = EditorTestContext::new(cx).await;
9767
9768 cx.language_registry().add(language.clone());
9769 cx.update_buffer(|buffer, cx| {
9770 buffer.set_language(Some(language), cx);
9771 });
9772
9773 let toggle_comments = &ToggleComments {
9774 advance_downwards: true,
9775 ignore_indent: false,
9776 };
9777
9778 // Single cursor on one line -> advance
9779 // Cursor moves horizontally 3 characters as well on non-blank line
9780 cx.set_state(indoc!(
9781 "fn a() {
9782 ˇdog();
9783 cat();
9784 }"
9785 ));
9786 cx.update_editor(|editor, window, cx| {
9787 editor.toggle_comments(toggle_comments, window, cx);
9788 });
9789 cx.assert_editor_state(indoc!(
9790 "fn a() {
9791 // dog();
9792 catˇ();
9793 }"
9794 ));
9795
9796 // Single selection on one line -> don't advance
9797 cx.set_state(indoc!(
9798 "fn a() {
9799 «dog()ˇ»;
9800 cat();
9801 }"
9802 ));
9803 cx.update_editor(|editor, window, cx| {
9804 editor.toggle_comments(toggle_comments, window, cx);
9805 });
9806 cx.assert_editor_state(indoc!(
9807 "fn a() {
9808 // «dog()ˇ»;
9809 cat();
9810 }"
9811 ));
9812
9813 // Multiple cursors on one line -> advance
9814 cx.set_state(indoc!(
9815 "fn a() {
9816 ˇdˇog();
9817 cat();
9818 }"
9819 ));
9820 cx.update_editor(|editor, window, cx| {
9821 editor.toggle_comments(toggle_comments, window, cx);
9822 });
9823 cx.assert_editor_state(indoc!(
9824 "fn a() {
9825 // dog();
9826 catˇ(ˇ);
9827 }"
9828 ));
9829
9830 // Multiple cursors on one line, with selection -> don't advance
9831 cx.set_state(indoc!(
9832 "fn a() {
9833 ˇdˇog«()ˇ»;
9834 cat();
9835 }"
9836 ));
9837 cx.update_editor(|editor, window, cx| {
9838 editor.toggle_comments(toggle_comments, window, cx);
9839 });
9840 cx.assert_editor_state(indoc!(
9841 "fn a() {
9842 // ˇdˇog«()ˇ»;
9843 cat();
9844 }"
9845 ));
9846
9847 // Single cursor on one line -> advance
9848 // Cursor moves to column 0 on blank line
9849 cx.set_state(indoc!(
9850 "fn a() {
9851 ˇdog();
9852
9853 cat();
9854 }"
9855 ));
9856 cx.update_editor(|editor, window, cx| {
9857 editor.toggle_comments(toggle_comments, window, cx);
9858 });
9859 cx.assert_editor_state(indoc!(
9860 "fn a() {
9861 // dog();
9862 ˇ
9863 cat();
9864 }"
9865 ));
9866
9867 // Single cursor on one line -> advance
9868 // Cursor starts and ends at column 0
9869 cx.set_state(indoc!(
9870 "fn a() {
9871 ˇ dog();
9872 cat();
9873 }"
9874 ));
9875 cx.update_editor(|editor, window, cx| {
9876 editor.toggle_comments(toggle_comments, window, cx);
9877 });
9878 cx.assert_editor_state(indoc!(
9879 "fn a() {
9880 // dog();
9881 ˇ cat();
9882 }"
9883 ));
9884}
9885
9886#[gpui::test]
9887async fn test_toggle_block_comment(cx: &mut TestAppContext) {
9888 init_test(cx, |_| {});
9889
9890 let mut cx = EditorTestContext::new(cx).await;
9891
9892 let html_language = Arc::new(
9893 Language::new(
9894 LanguageConfig {
9895 name: "HTML".into(),
9896 block_comment: Some(("<!-- ".into(), " -->".into())),
9897 ..Default::default()
9898 },
9899 Some(tree_sitter_html::LANGUAGE.into()),
9900 )
9901 .with_injection_query(
9902 r#"
9903 (script_element
9904 (raw_text) @injection.content
9905 (#set! injection.language "javascript"))
9906 "#,
9907 )
9908 .unwrap(),
9909 );
9910
9911 let javascript_language = Arc::new(Language::new(
9912 LanguageConfig {
9913 name: "JavaScript".into(),
9914 line_comments: vec!["// ".into()],
9915 ..Default::default()
9916 },
9917 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9918 ));
9919
9920 cx.language_registry().add(html_language.clone());
9921 cx.language_registry().add(javascript_language.clone());
9922 cx.update_buffer(|buffer, cx| {
9923 buffer.set_language(Some(html_language), cx);
9924 });
9925
9926 // Toggle comments for empty selections
9927 cx.set_state(
9928 &r#"
9929 <p>A</p>ˇ
9930 <p>B</p>ˇ
9931 <p>C</p>ˇ
9932 "#
9933 .unindent(),
9934 );
9935 cx.update_editor(|editor, window, cx| {
9936 editor.toggle_comments(&ToggleComments::default(), window, cx)
9937 });
9938 cx.assert_editor_state(
9939 &r#"
9940 <!-- <p>A</p>ˇ -->
9941 <!-- <p>B</p>ˇ -->
9942 <!-- <p>C</p>ˇ -->
9943 "#
9944 .unindent(),
9945 );
9946 cx.update_editor(|editor, window, cx| {
9947 editor.toggle_comments(&ToggleComments::default(), window, cx)
9948 });
9949 cx.assert_editor_state(
9950 &r#"
9951 <p>A</p>ˇ
9952 <p>B</p>ˇ
9953 <p>C</p>ˇ
9954 "#
9955 .unindent(),
9956 );
9957
9958 // Toggle comments for mixture of empty and non-empty selections, where
9959 // multiple selections occupy a given line.
9960 cx.set_state(
9961 &r#"
9962 <p>A«</p>
9963 <p>ˇ»B</p>ˇ
9964 <p>C«</p>
9965 <p>ˇ»D</p>ˇ
9966 "#
9967 .unindent(),
9968 );
9969
9970 cx.update_editor(|editor, window, cx| {
9971 editor.toggle_comments(&ToggleComments::default(), window, cx)
9972 });
9973 cx.assert_editor_state(
9974 &r#"
9975 <!-- <p>A«</p>
9976 <p>ˇ»B</p>ˇ -->
9977 <!-- <p>C«</p>
9978 <p>ˇ»D</p>ˇ -->
9979 "#
9980 .unindent(),
9981 );
9982 cx.update_editor(|editor, window, cx| {
9983 editor.toggle_comments(&ToggleComments::default(), window, cx)
9984 });
9985 cx.assert_editor_state(
9986 &r#"
9987 <p>A«</p>
9988 <p>ˇ»B</p>ˇ
9989 <p>C«</p>
9990 <p>ˇ»D</p>ˇ
9991 "#
9992 .unindent(),
9993 );
9994
9995 // Toggle comments when different languages are active for different
9996 // selections.
9997 cx.set_state(
9998 &r#"
9999 ˇ<script>
10000 ˇvar x = new Y();
10001 ˇ</script>
10002 "#
10003 .unindent(),
10004 );
10005 cx.executor().run_until_parked();
10006 cx.update_editor(|editor, window, cx| {
10007 editor.toggle_comments(&ToggleComments::default(), window, cx)
10008 });
10009 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10010 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10011 cx.assert_editor_state(
10012 &r#"
10013 <!-- ˇ<script> -->
10014 // ˇvar x = new Y();
10015 <!-- ˇ</script> -->
10016 "#
10017 .unindent(),
10018 );
10019}
10020
10021#[gpui::test]
10022fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10023 init_test(cx, |_| {});
10024
10025 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10026 let multibuffer = cx.new(|cx| {
10027 let mut multibuffer = MultiBuffer::new(ReadWrite);
10028 multibuffer.push_excerpts(
10029 buffer.clone(),
10030 [
10031 ExcerptRange {
10032 context: Point::new(0, 0)..Point::new(0, 4),
10033 primary: None,
10034 },
10035 ExcerptRange {
10036 context: Point::new(1, 0)..Point::new(1, 4),
10037 primary: None,
10038 },
10039 ],
10040 cx,
10041 );
10042 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10043 multibuffer
10044 });
10045
10046 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10047 editor.update_in(cx, |editor, window, cx| {
10048 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10049 editor.change_selections(None, window, cx, |s| {
10050 s.select_ranges([
10051 Point::new(0, 0)..Point::new(0, 0),
10052 Point::new(1, 0)..Point::new(1, 0),
10053 ])
10054 });
10055
10056 editor.handle_input("X", window, cx);
10057 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10058 assert_eq!(
10059 editor.selections.ranges(cx),
10060 [
10061 Point::new(0, 1)..Point::new(0, 1),
10062 Point::new(1, 1)..Point::new(1, 1),
10063 ]
10064 );
10065
10066 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10067 editor.change_selections(None, window, cx, |s| {
10068 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10069 });
10070 editor.backspace(&Default::default(), window, cx);
10071 assert_eq!(editor.text(cx), "Xa\nbbb");
10072 assert_eq!(
10073 editor.selections.ranges(cx),
10074 [Point::new(1, 0)..Point::new(1, 0)]
10075 );
10076
10077 editor.change_selections(None, window, cx, |s| {
10078 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10079 });
10080 editor.backspace(&Default::default(), window, cx);
10081 assert_eq!(editor.text(cx), "X\nbb");
10082 assert_eq!(
10083 editor.selections.ranges(cx),
10084 [Point::new(0, 1)..Point::new(0, 1)]
10085 );
10086 });
10087}
10088
10089#[gpui::test]
10090fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10091 init_test(cx, |_| {});
10092
10093 let markers = vec![('[', ']').into(), ('(', ')').into()];
10094 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10095 indoc! {"
10096 [aaaa
10097 (bbbb]
10098 cccc)",
10099 },
10100 markers.clone(),
10101 );
10102 let excerpt_ranges = markers.into_iter().map(|marker| {
10103 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10104 ExcerptRange {
10105 context,
10106 primary: None,
10107 }
10108 });
10109 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10110 let multibuffer = cx.new(|cx| {
10111 let mut multibuffer = MultiBuffer::new(ReadWrite);
10112 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10113 multibuffer
10114 });
10115
10116 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10117 editor.update_in(cx, |editor, window, cx| {
10118 let (expected_text, selection_ranges) = marked_text_ranges(
10119 indoc! {"
10120 aaaa
10121 bˇbbb
10122 bˇbbˇb
10123 cccc"
10124 },
10125 true,
10126 );
10127 assert_eq!(editor.text(cx), expected_text);
10128 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10129
10130 editor.handle_input("X", window, cx);
10131
10132 let (expected_text, expected_selections) = marked_text_ranges(
10133 indoc! {"
10134 aaaa
10135 bXˇbbXb
10136 bXˇbbXˇb
10137 cccc"
10138 },
10139 false,
10140 );
10141 assert_eq!(editor.text(cx), expected_text);
10142 assert_eq!(editor.selections.ranges(cx), expected_selections);
10143
10144 editor.newline(&Newline, window, cx);
10145 let (expected_text, expected_selections) = marked_text_ranges(
10146 indoc! {"
10147 aaaa
10148 bX
10149 ˇbbX
10150 b
10151 bX
10152 ˇbbX
10153 ˇb
10154 cccc"
10155 },
10156 false,
10157 );
10158 assert_eq!(editor.text(cx), expected_text);
10159 assert_eq!(editor.selections.ranges(cx), expected_selections);
10160 });
10161}
10162
10163#[gpui::test]
10164fn test_refresh_selections(cx: &mut TestAppContext) {
10165 init_test(cx, |_| {});
10166
10167 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10168 let mut excerpt1_id = None;
10169 let multibuffer = cx.new(|cx| {
10170 let mut multibuffer = MultiBuffer::new(ReadWrite);
10171 excerpt1_id = multibuffer
10172 .push_excerpts(
10173 buffer.clone(),
10174 [
10175 ExcerptRange {
10176 context: Point::new(0, 0)..Point::new(1, 4),
10177 primary: None,
10178 },
10179 ExcerptRange {
10180 context: Point::new(1, 0)..Point::new(2, 4),
10181 primary: None,
10182 },
10183 ],
10184 cx,
10185 )
10186 .into_iter()
10187 .next();
10188 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10189 multibuffer
10190 });
10191
10192 let editor = cx.add_window(|window, cx| {
10193 let mut editor = build_editor(multibuffer.clone(), window, cx);
10194 let snapshot = editor.snapshot(window, cx);
10195 editor.change_selections(None, window, cx, |s| {
10196 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10197 });
10198 editor.begin_selection(
10199 Point::new(2, 1).to_display_point(&snapshot),
10200 true,
10201 1,
10202 window,
10203 cx,
10204 );
10205 assert_eq!(
10206 editor.selections.ranges(cx),
10207 [
10208 Point::new(1, 3)..Point::new(1, 3),
10209 Point::new(2, 1)..Point::new(2, 1),
10210 ]
10211 );
10212 editor
10213 });
10214
10215 // Refreshing selections is a no-op when excerpts haven't changed.
10216 _ = editor.update(cx, |editor, window, cx| {
10217 editor.change_selections(None, window, cx, |s| s.refresh());
10218 assert_eq!(
10219 editor.selections.ranges(cx),
10220 [
10221 Point::new(1, 3)..Point::new(1, 3),
10222 Point::new(2, 1)..Point::new(2, 1),
10223 ]
10224 );
10225 });
10226
10227 multibuffer.update(cx, |multibuffer, cx| {
10228 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10229 });
10230 _ = editor.update(cx, |editor, window, cx| {
10231 // Removing an excerpt causes the first selection to become degenerate.
10232 assert_eq!(
10233 editor.selections.ranges(cx),
10234 [
10235 Point::new(0, 0)..Point::new(0, 0),
10236 Point::new(0, 1)..Point::new(0, 1)
10237 ]
10238 );
10239
10240 // Refreshing selections will relocate the first selection to the original buffer
10241 // location.
10242 editor.change_selections(None, window, cx, |s| s.refresh());
10243 assert_eq!(
10244 editor.selections.ranges(cx),
10245 [
10246 Point::new(0, 1)..Point::new(0, 1),
10247 Point::new(0, 3)..Point::new(0, 3)
10248 ]
10249 );
10250 assert!(editor.selections.pending_anchor().is_some());
10251 });
10252}
10253
10254#[gpui::test]
10255fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10256 init_test(cx, |_| {});
10257
10258 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10259 let mut excerpt1_id = None;
10260 let multibuffer = cx.new(|cx| {
10261 let mut multibuffer = MultiBuffer::new(ReadWrite);
10262 excerpt1_id = multibuffer
10263 .push_excerpts(
10264 buffer.clone(),
10265 [
10266 ExcerptRange {
10267 context: Point::new(0, 0)..Point::new(1, 4),
10268 primary: None,
10269 },
10270 ExcerptRange {
10271 context: Point::new(1, 0)..Point::new(2, 4),
10272 primary: None,
10273 },
10274 ],
10275 cx,
10276 )
10277 .into_iter()
10278 .next();
10279 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10280 multibuffer
10281 });
10282
10283 let editor = cx.add_window(|window, cx| {
10284 let mut editor = build_editor(multibuffer.clone(), window, cx);
10285 let snapshot = editor.snapshot(window, cx);
10286 editor.begin_selection(
10287 Point::new(1, 3).to_display_point(&snapshot),
10288 false,
10289 1,
10290 window,
10291 cx,
10292 );
10293 assert_eq!(
10294 editor.selections.ranges(cx),
10295 [Point::new(1, 3)..Point::new(1, 3)]
10296 );
10297 editor
10298 });
10299
10300 multibuffer.update(cx, |multibuffer, cx| {
10301 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10302 });
10303 _ = editor.update(cx, |editor, window, cx| {
10304 assert_eq!(
10305 editor.selections.ranges(cx),
10306 [Point::new(0, 0)..Point::new(0, 0)]
10307 );
10308
10309 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10310 editor.change_selections(None, window, cx, |s| s.refresh());
10311 assert_eq!(
10312 editor.selections.ranges(cx),
10313 [Point::new(0, 3)..Point::new(0, 3)]
10314 );
10315 assert!(editor.selections.pending_anchor().is_some());
10316 });
10317}
10318
10319#[gpui::test]
10320async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10321 init_test(cx, |_| {});
10322
10323 let language = Arc::new(
10324 Language::new(
10325 LanguageConfig {
10326 brackets: BracketPairConfig {
10327 pairs: vec![
10328 BracketPair {
10329 start: "{".to_string(),
10330 end: "}".to_string(),
10331 close: true,
10332 surround: true,
10333 newline: true,
10334 },
10335 BracketPair {
10336 start: "/* ".to_string(),
10337 end: " */".to_string(),
10338 close: true,
10339 surround: true,
10340 newline: true,
10341 },
10342 ],
10343 ..Default::default()
10344 },
10345 ..Default::default()
10346 },
10347 Some(tree_sitter_rust::LANGUAGE.into()),
10348 )
10349 .with_indents_query("")
10350 .unwrap(),
10351 );
10352
10353 let text = concat!(
10354 "{ }\n", //
10355 " x\n", //
10356 " /* */\n", //
10357 "x\n", //
10358 "{{} }\n", //
10359 );
10360
10361 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10362 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10363 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10364 editor
10365 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10366 .await;
10367
10368 editor.update_in(cx, |editor, window, cx| {
10369 editor.change_selections(None, window, cx, |s| {
10370 s.select_display_ranges([
10371 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10372 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10373 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10374 ])
10375 });
10376 editor.newline(&Newline, window, cx);
10377
10378 assert_eq!(
10379 editor.buffer().read(cx).read(cx).text(),
10380 concat!(
10381 "{ \n", // Suppress rustfmt
10382 "\n", //
10383 "}\n", //
10384 " x\n", //
10385 " /* \n", //
10386 " \n", //
10387 " */\n", //
10388 "x\n", //
10389 "{{} \n", //
10390 "}\n", //
10391 )
10392 );
10393 });
10394}
10395
10396#[gpui::test]
10397fn test_highlighted_ranges(cx: &mut TestAppContext) {
10398 init_test(cx, |_| {});
10399
10400 let editor = cx.add_window(|window, cx| {
10401 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10402 build_editor(buffer.clone(), window, cx)
10403 });
10404
10405 _ = editor.update(cx, |editor, window, cx| {
10406 struct Type1;
10407 struct Type2;
10408
10409 let buffer = editor.buffer.read(cx).snapshot(cx);
10410
10411 let anchor_range =
10412 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10413
10414 editor.highlight_background::<Type1>(
10415 &[
10416 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10417 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10418 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10419 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10420 ],
10421 |_| Hsla::red(),
10422 cx,
10423 );
10424 editor.highlight_background::<Type2>(
10425 &[
10426 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10427 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10428 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10429 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10430 ],
10431 |_| Hsla::green(),
10432 cx,
10433 );
10434
10435 let snapshot = editor.snapshot(window, cx);
10436 let mut highlighted_ranges = editor.background_highlights_in_range(
10437 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10438 &snapshot,
10439 cx.theme().colors(),
10440 );
10441 // Enforce a consistent ordering based on color without relying on the ordering of the
10442 // highlight's `TypeId` which is non-executor.
10443 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10444 assert_eq!(
10445 highlighted_ranges,
10446 &[
10447 (
10448 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10449 Hsla::red(),
10450 ),
10451 (
10452 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10453 Hsla::red(),
10454 ),
10455 (
10456 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10457 Hsla::green(),
10458 ),
10459 (
10460 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10461 Hsla::green(),
10462 ),
10463 ]
10464 );
10465 assert_eq!(
10466 editor.background_highlights_in_range(
10467 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10468 &snapshot,
10469 cx.theme().colors(),
10470 ),
10471 &[(
10472 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10473 Hsla::red(),
10474 )]
10475 );
10476 });
10477}
10478
10479#[gpui::test]
10480async fn test_following(cx: &mut TestAppContext) {
10481 init_test(cx, |_| {});
10482
10483 let fs = FakeFs::new(cx.executor());
10484 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10485
10486 let buffer = project.update(cx, |project, cx| {
10487 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10488 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10489 });
10490 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10491 let follower = cx.update(|cx| {
10492 cx.open_window(
10493 WindowOptions {
10494 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10495 gpui::Point::new(px(0.), px(0.)),
10496 gpui::Point::new(px(10.), px(80.)),
10497 ))),
10498 ..Default::default()
10499 },
10500 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10501 )
10502 .unwrap()
10503 });
10504
10505 let is_still_following = Rc::new(RefCell::new(true));
10506 let follower_edit_event_count = Rc::new(RefCell::new(0));
10507 let pending_update = Rc::new(RefCell::new(None));
10508 let leader_entity = leader.root(cx).unwrap();
10509 let follower_entity = follower.root(cx).unwrap();
10510 _ = follower.update(cx, {
10511 let update = pending_update.clone();
10512 let is_still_following = is_still_following.clone();
10513 let follower_edit_event_count = follower_edit_event_count.clone();
10514 |_, window, cx| {
10515 cx.subscribe_in(
10516 &leader_entity,
10517 window,
10518 move |_, leader, event, window, cx| {
10519 leader.read(cx).add_event_to_update_proto(
10520 event,
10521 &mut update.borrow_mut(),
10522 window,
10523 cx,
10524 );
10525 },
10526 )
10527 .detach();
10528
10529 cx.subscribe_in(
10530 &follower_entity,
10531 window,
10532 move |_, _, event: &EditorEvent, _window, _cx| {
10533 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10534 *is_still_following.borrow_mut() = false;
10535 }
10536
10537 if let EditorEvent::BufferEdited = event {
10538 *follower_edit_event_count.borrow_mut() += 1;
10539 }
10540 },
10541 )
10542 .detach();
10543 }
10544 });
10545
10546 // Update the selections only
10547 _ = leader.update(cx, |leader, window, cx| {
10548 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10549 });
10550 follower
10551 .update(cx, |follower, window, cx| {
10552 follower.apply_update_proto(
10553 &project,
10554 pending_update.borrow_mut().take().unwrap(),
10555 window,
10556 cx,
10557 )
10558 })
10559 .unwrap()
10560 .await
10561 .unwrap();
10562 _ = follower.update(cx, |follower, _, cx| {
10563 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10564 });
10565 assert!(*is_still_following.borrow());
10566 assert_eq!(*follower_edit_event_count.borrow(), 0);
10567
10568 // Update the scroll position only
10569 _ = leader.update(cx, |leader, window, cx| {
10570 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10571 });
10572 follower
10573 .update(cx, |follower, window, cx| {
10574 follower.apply_update_proto(
10575 &project,
10576 pending_update.borrow_mut().take().unwrap(),
10577 window,
10578 cx,
10579 )
10580 })
10581 .unwrap()
10582 .await
10583 .unwrap();
10584 assert_eq!(
10585 follower
10586 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10587 .unwrap(),
10588 gpui::Point::new(1.5, 3.5)
10589 );
10590 assert!(*is_still_following.borrow());
10591 assert_eq!(*follower_edit_event_count.borrow(), 0);
10592
10593 // Update the selections and scroll position. The follower's scroll position is updated
10594 // via autoscroll, not via the leader's exact scroll position.
10595 _ = leader.update(cx, |leader, window, cx| {
10596 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10597 leader.request_autoscroll(Autoscroll::newest(), cx);
10598 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10599 });
10600 follower
10601 .update(cx, |follower, window, cx| {
10602 follower.apply_update_proto(
10603 &project,
10604 pending_update.borrow_mut().take().unwrap(),
10605 window,
10606 cx,
10607 )
10608 })
10609 .unwrap()
10610 .await
10611 .unwrap();
10612 _ = follower.update(cx, |follower, _, cx| {
10613 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10614 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10615 });
10616 assert!(*is_still_following.borrow());
10617
10618 // Creating a pending selection that precedes another selection
10619 _ = leader.update(cx, |leader, window, cx| {
10620 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10621 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10622 });
10623 follower
10624 .update(cx, |follower, window, cx| {
10625 follower.apply_update_proto(
10626 &project,
10627 pending_update.borrow_mut().take().unwrap(),
10628 window,
10629 cx,
10630 )
10631 })
10632 .unwrap()
10633 .await
10634 .unwrap();
10635 _ = follower.update(cx, |follower, _, cx| {
10636 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10637 });
10638 assert!(*is_still_following.borrow());
10639
10640 // Extend the pending selection so that it surrounds another selection
10641 _ = leader.update(cx, |leader, window, cx| {
10642 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10643 });
10644 follower
10645 .update(cx, |follower, window, cx| {
10646 follower.apply_update_proto(
10647 &project,
10648 pending_update.borrow_mut().take().unwrap(),
10649 window,
10650 cx,
10651 )
10652 })
10653 .unwrap()
10654 .await
10655 .unwrap();
10656 _ = follower.update(cx, |follower, _, cx| {
10657 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10658 });
10659
10660 // Scrolling locally breaks the follow
10661 _ = follower.update(cx, |follower, window, cx| {
10662 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10663 follower.set_scroll_anchor(
10664 ScrollAnchor {
10665 anchor: top_anchor,
10666 offset: gpui::Point::new(0.0, 0.5),
10667 },
10668 window,
10669 cx,
10670 );
10671 });
10672 assert!(!(*is_still_following.borrow()));
10673}
10674
10675#[gpui::test]
10676async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10677 init_test(cx, |_| {});
10678
10679 let fs = FakeFs::new(cx.executor());
10680 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10681 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10682 let pane = workspace
10683 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10684 .unwrap();
10685
10686 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10687
10688 let leader = pane.update_in(cx, |_, window, cx| {
10689 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10690 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10691 });
10692
10693 // Start following the editor when it has no excerpts.
10694 let mut state_message =
10695 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10696 let workspace_entity = workspace.root(cx).unwrap();
10697 let follower_1 = cx
10698 .update_window(*workspace.deref(), |_, window, cx| {
10699 Editor::from_state_proto(
10700 workspace_entity,
10701 ViewId {
10702 creator: Default::default(),
10703 id: 0,
10704 },
10705 &mut state_message,
10706 window,
10707 cx,
10708 )
10709 })
10710 .unwrap()
10711 .unwrap()
10712 .await
10713 .unwrap();
10714
10715 let update_message = Rc::new(RefCell::new(None));
10716 follower_1.update_in(cx, {
10717 let update = update_message.clone();
10718 |_, window, cx| {
10719 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10720 leader.read(cx).add_event_to_update_proto(
10721 event,
10722 &mut update.borrow_mut(),
10723 window,
10724 cx,
10725 );
10726 })
10727 .detach();
10728 }
10729 });
10730
10731 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10732 (
10733 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10734 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10735 )
10736 });
10737
10738 // Insert some excerpts.
10739 leader.update(cx, |leader, cx| {
10740 leader.buffer.update(cx, |multibuffer, cx| {
10741 let excerpt_ids = multibuffer.push_excerpts(
10742 buffer_1.clone(),
10743 [
10744 ExcerptRange {
10745 context: 1..6,
10746 primary: None,
10747 },
10748 ExcerptRange {
10749 context: 12..15,
10750 primary: None,
10751 },
10752 ExcerptRange {
10753 context: 0..3,
10754 primary: None,
10755 },
10756 ],
10757 cx,
10758 );
10759 multibuffer.insert_excerpts_after(
10760 excerpt_ids[0],
10761 buffer_2.clone(),
10762 [
10763 ExcerptRange {
10764 context: 8..12,
10765 primary: None,
10766 },
10767 ExcerptRange {
10768 context: 0..6,
10769 primary: None,
10770 },
10771 ],
10772 cx,
10773 );
10774 });
10775 });
10776
10777 // Apply the update of adding the excerpts.
10778 follower_1
10779 .update_in(cx, |follower, window, cx| {
10780 follower.apply_update_proto(
10781 &project,
10782 update_message.borrow().clone().unwrap(),
10783 window,
10784 cx,
10785 )
10786 })
10787 .await
10788 .unwrap();
10789 assert_eq!(
10790 follower_1.update(cx, |editor, cx| editor.text(cx)),
10791 leader.update(cx, |editor, cx| editor.text(cx))
10792 );
10793 update_message.borrow_mut().take();
10794
10795 // Start following separately after it already has excerpts.
10796 let mut state_message =
10797 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10798 let workspace_entity = workspace.root(cx).unwrap();
10799 let follower_2 = cx
10800 .update_window(*workspace.deref(), |_, window, cx| {
10801 Editor::from_state_proto(
10802 workspace_entity,
10803 ViewId {
10804 creator: Default::default(),
10805 id: 0,
10806 },
10807 &mut state_message,
10808 window,
10809 cx,
10810 )
10811 })
10812 .unwrap()
10813 .unwrap()
10814 .await
10815 .unwrap();
10816 assert_eq!(
10817 follower_2.update(cx, |editor, cx| editor.text(cx)),
10818 leader.update(cx, |editor, cx| editor.text(cx))
10819 );
10820
10821 // Remove some excerpts.
10822 leader.update(cx, |leader, cx| {
10823 leader.buffer.update(cx, |multibuffer, cx| {
10824 let excerpt_ids = multibuffer.excerpt_ids();
10825 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10826 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10827 });
10828 });
10829
10830 // Apply the update of removing the excerpts.
10831 follower_1
10832 .update_in(cx, |follower, window, cx| {
10833 follower.apply_update_proto(
10834 &project,
10835 update_message.borrow().clone().unwrap(),
10836 window,
10837 cx,
10838 )
10839 })
10840 .await
10841 .unwrap();
10842 follower_2
10843 .update_in(cx, |follower, window, cx| {
10844 follower.apply_update_proto(
10845 &project,
10846 update_message.borrow().clone().unwrap(),
10847 window,
10848 cx,
10849 )
10850 })
10851 .await
10852 .unwrap();
10853 update_message.borrow_mut().take();
10854 assert_eq!(
10855 follower_1.update(cx, |editor, cx| editor.text(cx)),
10856 leader.update(cx, |editor, cx| editor.text(cx))
10857 );
10858}
10859
10860#[gpui::test]
10861async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
10862 init_test(cx, |_| {});
10863
10864 let mut cx = EditorTestContext::new(cx).await;
10865 let lsp_store =
10866 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10867
10868 cx.set_state(indoc! {"
10869 ˇfn func(abc def: i32) -> u32 {
10870 }
10871 "});
10872
10873 cx.update(|_, cx| {
10874 lsp_store.update(cx, |lsp_store, cx| {
10875 lsp_store
10876 .update_diagnostics(
10877 LanguageServerId(0),
10878 lsp::PublishDiagnosticsParams {
10879 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10880 version: None,
10881 diagnostics: vec![
10882 lsp::Diagnostic {
10883 range: lsp::Range::new(
10884 lsp::Position::new(0, 11),
10885 lsp::Position::new(0, 12),
10886 ),
10887 severity: Some(lsp::DiagnosticSeverity::ERROR),
10888 ..Default::default()
10889 },
10890 lsp::Diagnostic {
10891 range: lsp::Range::new(
10892 lsp::Position::new(0, 12),
10893 lsp::Position::new(0, 15),
10894 ),
10895 severity: Some(lsp::DiagnosticSeverity::ERROR),
10896 ..Default::default()
10897 },
10898 lsp::Diagnostic {
10899 range: lsp::Range::new(
10900 lsp::Position::new(0, 25),
10901 lsp::Position::new(0, 28),
10902 ),
10903 severity: Some(lsp::DiagnosticSeverity::ERROR),
10904 ..Default::default()
10905 },
10906 ],
10907 },
10908 &[],
10909 cx,
10910 )
10911 .unwrap()
10912 });
10913 });
10914
10915 executor.run_until_parked();
10916
10917 cx.update_editor(|editor, window, cx| {
10918 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10919 });
10920
10921 cx.assert_editor_state(indoc! {"
10922 fn func(abc def: i32) -> ˇu32 {
10923 }
10924 "});
10925
10926 cx.update_editor(|editor, window, cx| {
10927 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10928 });
10929
10930 cx.assert_editor_state(indoc! {"
10931 fn func(abc ˇdef: i32) -> u32 {
10932 }
10933 "});
10934
10935 cx.update_editor(|editor, window, cx| {
10936 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10937 });
10938
10939 cx.assert_editor_state(indoc! {"
10940 fn func(abcˇ def: i32) -> u32 {
10941 }
10942 "});
10943
10944 cx.update_editor(|editor, window, cx| {
10945 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10946 });
10947
10948 cx.assert_editor_state(indoc! {"
10949 fn func(abc def: i32) -> ˇu32 {
10950 }
10951 "});
10952}
10953
10954#[gpui::test]
10955async fn cycle_through_same_place_diagnostics(
10956 executor: BackgroundExecutor,
10957 cx: &mut TestAppContext,
10958) {
10959 init_test(cx, |_| {});
10960
10961 let mut cx = EditorTestContext::new(cx).await;
10962 let lsp_store =
10963 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10964
10965 cx.set_state(indoc! {"
10966 ˇfn func(abc def: i32) -> u32 {
10967 }
10968 "});
10969
10970 cx.update(|_, cx| {
10971 lsp_store.update(cx, |lsp_store, cx| {
10972 lsp_store
10973 .update_diagnostics(
10974 LanguageServerId(0),
10975 lsp::PublishDiagnosticsParams {
10976 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10977 version: None,
10978 diagnostics: vec![
10979 lsp::Diagnostic {
10980 range: lsp::Range::new(
10981 lsp::Position::new(0, 11),
10982 lsp::Position::new(0, 12),
10983 ),
10984 severity: Some(lsp::DiagnosticSeverity::ERROR),
10985 ..Default::default()
10986 },
10987 lsp::Diagnostic {
10988 range: lsp::Range::new(
10989 lsp::Position::new(0, 12),
10990 lsp::Position::new(0, 15),
10991 ),
10992 severity: Some(lsp::DiagnosticSeverity::ERROR),
10993 ..Default::default()
10994 },
10995 lsp::Diagnostic {
10996 range: lsp::Range::new(
10997 lsp::Position::new(0, 12),
10998 lsp::Position::new(0, 15),
10999 ),
11000 severity: Some(lsp::DiagnosticSeverity::ERROR),
11001 ..Default::default()
11002 },
11003 lsp::Diagnostic {
11004 range: lsp::Range::new(
11005 lsp::Position::new(0, 25),
11006 lsp::Position::new(0, 28),
11007 ),
11008 severity: Some(lsp::DiagnosticSeverity::ERROR),
11009 ..Default::default()
11010 },
11011 ],
11012 },
11013 &[],
11014 cx,
11015 )
11016 .unwrap()
11017 });
11018 });
11019 executor.run_until_parked();
11020
11021 //// Backward
11022
11023 // Fourth diagnostic
11024 cx.update_editor(|editor, window, cx| {
11025 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
11026 });
11027 cx.assert_editor_state(indoc! {"
11028 fn func(abc def: i32) -> ˇu32 {
11029 }
11030 "});
11031
11032 // Third diagnostic
11033 cx.update_editor(|editor, window, cx| {
11034 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
11035 });
11036 cx.assert_editor_state(indoc! {"
11037 fn func(abc ˇdef: i32) -> u32 {
11038 }
11039 "});
11040
11041 // Second diagnostic, same place
11042 cx.update_editor(|editor, window, cx| {
11043 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
11044 });
11045 cx.assert_editor_state(indoc! {"
11046 fn func(abc ˇdef: i32) -> u32 {
11047 }
11048 "});
11049
11050 // First diagnostic
11051 cx.update_editor(|editor, window, cx| {
11052 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
11053 });
11054 cx.assert_editor_state(indoc! {"
11055 fn func(abcˇ def: i32) -> u32 {
11056 }
11057 "});
11058
11059 // Wrapped over, fourth diagnostic
11060 cx.update_editor(|editor, window, cx| {
11061 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
11062 });
11063 cx.assert_editor_state(indoc! {"
11064 fn func(abc def: i32) -> ˇu32 {
11065 }
11066 "});
11067
11068 cx.update_editor(|editor, window, cx| {
11069 editor.move_to_beginning(&MoveToBeginning, window, cx);
11070 });
11071 cx.assert_editor_state(indoc! {"
11072 ˇfn func(abc def: i32) -> u32 {
11073 }
11074 "});
11075
11076 //// Forward
11077
11078 // First diagnostic
11079 cx.update_editor(|editor, window, cx| {
11080 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11081 });
11082 cx.assert_editor_state(indoc! {"
11083 fn func(abcˇ def: i32) -> u32 {
11084 }
11085 "});
11086
11087 // Second diagnostic
11088 cx.update_editor(|editor, window, cx| {
11089 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11090 });
11091 cx.assert_editor_state(indoc! {"
11092 fn func(abc ˇdef: i32) -> u32 {
11093 }
11094 "});
11095
11096 // Third diagnostic, same place
11097 cx.update_editor(|editor, window, cx| {
11098 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11099 });
11100 cx.assert_editor_state(indoc! {"
11101 fn func(abc ˇdef: i32) -> u32 {
11102 }
11103 "});
11104
11105 // Fourth diagnostic
11106 cx.update_editor(|editor, window, cx| {
11107 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11108 });
11109 cx.assert_editor_state(indoc! {"
11110 fn func(abc def: i32) -> ˇu32 {
11111 }
11112 "});
11113
11114 // Wrapped around, first diagnostic
11115 cx.update_editor(|editor, window, cx| {
11116 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11117 });
11118 cx.assert_editor_state(indoc! {"
11119 fn func(abcˇ def: i32) -> u32 {
11120 }
11121 "});
11122}
11123
11124#[gpui::test]
11125async fn active_diagnostics_dismiss_after_invalidation(
11126 executor: BackgroundExecutor,
11127 cx: &mut TestAppContext,
11128) {
11129 init_test(cx, |_| {});
11130
11131 let mut cx = EditorTestContext::new(cx).await;
11132 let lsp_store =
11133 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11134
11135 cx.set_state(indoc! {"
11136 ˇfn func(abc def: i32) -> u32 {
11137 }
11138 "});
11139
11140 let message = "Something's wrong!";
11141 cx.update(|_, cx| {
11142 lsp_store.update(cx, |lsp_store, cx| {
11143 lsp_store
11144 .update_diagnostics(
11145 LanguageServerId(0),
11146 lsp::PublishDiagnosticsParams {
11147 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11148 version: None,
11149 diagnostics: vec![lsp::Diagnostic {
11150 range: lsp::Range::new(
11151 lsp::Position::new(0, 11),
11152 lsp::Position::new(0, 12),
11153 ),
11154 severity: Some(lsp::DiagnosticSeverity::ERROR),
11155 message: message.to_string(),
11156 ..Default::default()
11157 }],
11158 },
11159 &[],
11160 cx,
11161 )
11162 .unwrap()
11163 });
11164 });
11165 executor.run_until_parked();
11166
11167 cx.update_editor(|editor, window, cx| {
11168 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11169 assert_eq!(
11170 editor
11171 .active_diagnostics
11172 .as_ref()
11173 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11174 Some(message),
11175 "Should have a diagnostics group activated"
11176 );
11177 });
11178 cx.assert_editor_state(indoc! {"
11179 fn func(abcˇ def: i32) -> u32 {
11180 }
11181 "});
11182
11183 cx.update(|_, cx| {
11184 lsp_store.update(cx, |lsp_store, cx| {
11185 lsp_store
11186 .update_diagnostics(
11187 LanguageServerId(0),
11188 lsp::PublishDiagnosticsParams {
11189 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11190 version: None,
11191 diagnostics: Vec::new(),
11192 },
11193 &[],
11194 cx,
11195 )
11196 .unwrap()
11197 });
11198 });
11199 executor.run_until_parked();
11200 cx.update_editor(|editor, _, _| {
11201 assert_eq!(
11202 editor.active_diagnostics, None,
11203 "After no diagnostics set to the editor, no diagnostics should be active"
11204 );
11205 });
11206 cx.assert_editor_state(indoc! {"
11207 fn func(abcˇ def: i32) -> u32 {
11208 }
11209 "});
11210
11211 cx.update_editor(|editor, window, cx| {
11212 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11213 assert_eq!(
11214 editor.active_diagnostics, None,
11215 "Should be no diagnostics to go to and activate"
11216 );
11217 });
11218 cx.assert_editor_state(indoc! {"
11219 fn func(abcˇ def: i32) -> u32 {
11220 }
11221 "});
11222}
11223
11224#[gpui::test]
11225async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11226 init_test(cx, |_| {});
11227
11228 let mut cx = EditorTestContext::new(cx).await;
11229
11230 cx.set_state(indoc! {"
11231 fn func(abˇc def: i32) -> u32 {
11232 }
11233 "});
11234 let lsp_store =
11235 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11236
11237 cx.update(|_, cx| {
11238 lsp_store.update(cx, |lsp_store, cx| {
11239 lsp_store.update_diagnostics(
11240 LanguageServerId(0),
11241 lsp::PublishDiagnosticsParams {
11242 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11243 version: None,
11244 diagnostics: vec![lsp::Diagnostic {
11245 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11246 severity: Some(lsp::DiagnosticSeverity::ERROR),
11247 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11248 ..Default::default()
11249 }],
11250 },
11251 &[],
11252 cx,
11253 )
11254 })
11255 }).unwrap();
11256 cx.run_until_parked();
11257 cx.update_editor(|editor, window, cx| {
11258 hover_popover::hover(editor, &Default::default(), window, cx)
11259 });
11260 cx.run_until_parked();
11261 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11262}
11263
11264#[gpui::test]
11265async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11266 init_test(cx, |_| {});
11267
11268 let mut cx = EditorTestContext::new(cx).await;
11269
11270 let diff_base = r#"
11271 use some::mod;
11272
11273 const A: u32 = 42;
11274
11275 fn main() {
11276 println!("hello");
11277
11278 println!("world");
11279 }
11280 "#
11281 .unindent();
11282
11283 // Edits are modified, removed, modified, added
11284 cx.set_state(
11285 &r#"
11286 use some::modified;
11287
11288 ˇ
11289 fn main() {
11290 println!("hello there");
11291
11292 println!("around the");
11293 println!("world");
11294 }
11295 "#
11296 .unindent(),
11297 );
11298
11299 cx.set_head_text(&diff_base);
11300 executor.run_until_parked();
11301
11302 cx.update_editor(|editor, window, cx| {
11303 //Wrap around the bottom of the buffer
11304 for _ in 0..3 {
11305 editor.go_to_next_hunk(&GoToHunk, window, cx);
11306 }
11307 });
11308
11309 cx.assert_editor_state(
11310 &r#"
11311 ˇuse some::modified;
11312
11313
11314 fn main() {
11315 println!("hello there");
11316
11317 println!("around the");
11318 println!("world");
11319 }
11320 "#
11321 .unindent(),
11322 );
11323
11324 cx.update_editor(|editor, window, cx| {
11325 //Wrap around the top of the buffer
11326 for _ in 0..2 {
11327 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11328 }
11329 });
11330
11331 cx.assert_editor_state(
11332 &r#"
11333 use some::modified;
11334
11335
11336 fn main() {
11337 ˇ println!("hello there");
11338
11339 println!("around the");
11340 println!("world");
11341 }
11342 "#
11343 .unindent(),
11344 );
11345
11346 cx.update_editor(|editor, window, cx| {
11347 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11348 });
11349
11350 cx.assert_editor_state(
11351 &r#"
11352 use some::modified;
11353
11354 ˇ
11355 fn main() {
11356 println!("hello there");
11357
11358 println!("around the");
11359 println!("world");
11360 }
11361 "#
11362 .unindent(),
11363 );
11364
11365 cx.update_editor(|editor, window, cx| {
11366 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11367 });
11368
11369 cx.assert_editor_state(
11370 &r#"
11371 ˇuse some::modified;
11372
11373
11374 fn main() {
11375 println!("hello there");
11376
11377 println!("around the");
11378 println!("world");
11379 }
11380 "#
11381 .unindent(),
11382 );
11383
11384 cx.update_editor(|editor, window, cx| {
11385 for _ in 0..2 {
11386 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11387 }
11388 });
11389
11390 cx.assert_editor_state(
11391 &r#"
11392 use some::modified;
11393
11394
11395 fn main() {
11396 ˇ println!("hello there");
11397
11398 println!("around the");
11399 println!("world");
11400 }
11401 "#
11402 .unindent(),
11403 );
11404
11405 cx.update_editor(|editor, window, cx| {
11406 editor.fold(&Fold, window, cx);
11407 });
11408
11409 cx.update_editor(|editor, window, cx| {
11410 editor.go_to_next_hunk(&GoToHunk, window, cx);
11411 });
11412
11413 cx.assert_editor_state(
11414 &r#"
11415 ˇuse some::modified;
11416
11417
11418 fn main() {
11419 println!("hello there");
11420
11421 println!("around the");
11422 println!("world");
11423 }
11424 "#
11425 .unindent(),
11426 );
11427}
11428
11429#[test]
11430fn test_split_words() {
11431 fn split(text: &str) -> Vec<&str> {
11432 split_words(text).collect()
11433 }
11434
11435 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11436 assert_eq!(split("hello_world"), &["hello_", "world"]);
11437 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11438 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11439 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11440 assert_eq!(split("helloworld"), &["helloworld"]);
11441
11442 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11443}
11444
11445#[gpui::test]
11446async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11447 init_test(cx, |_| {});
11448
11449 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11450 let mut assert = |before, after| {
11451 let _state_context = cx.set_state(before);
11452 cx.run_until_parked();
11453 cx.update_editor(|editor, window, cx| {
11454 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11455 });
11456 cx.assert_editor_state(after);
11457 };
11458
11459 // Outside bracket jumps to outside of matching bracket
11460 assert("console.logˇ(var);", "console.log(var)ˇ;");
11461 assert("console.log(var)ˇ;", "console.logˇ(var);");
11462
11463 // Inside bracket jumps to inside of matching bracket
11464 assert("console.log(ˇvar);", "console.log(varˇ);");
11465 assert("console.log(varˇ);", "console.log(ˇvar);");
11466
11467 // When outside a bracket and inside, favor jumping to the inside bracket
11468 assert(
11469 "console.log('foo', [1, 2, 3]ˇ);",
11470 "console.log(ˇ'foo', [1, 2, 3]);",
11471 );
11472 assert(
11473 "console.log(ˇ'foo', [1, 2, 3]);",
11474 "console.log('foo', [1, 2, 3]ˇ);",
11475 );
11476
11477 // Bias forward if two options are equally likely
11478 assert(
11479 "let result = curried_fun()ˇ();",
11480 "let result = curried_fun()()ˇ;",
11481 );
11482
11483 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11484 assert(
11485 indoc! {"
11486 function test() {
11487 console.log('test')ˇ
11488 }"},
11489 indoc! {"
11490 function test() {
11491 console.logˇ('test')
11492 }"},
11493 );
11494}
11495
11496#[gpui::test]
11497async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11498 init_test(cx, |_| {});
11499
11500 let fs = FakeFs::new(cx.executor());
11501 fs.insert_tree(
11502 path!("/a"),
11503 json!({
11504 "main.rs": "fn main() { let a = 5; }",
11505 "other.rs": "// Test file",
11506 }),
11507 )
11508 .await;
11509 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11510
11511 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11512 language_registry.add(Arc::new(Language::new(
11513 LanguageConfig {
11514 name: "Rust".into(),
11515 matcher: LanguageMatcher {
11516 path_suffixes: vec!["rs".to_string()],
11517 ..Default::default()
11518 },
11519 brackets: BracketPairConfig {
11520 pairs: vec![BracketPair {
11521 start: "{".to_string(),
11522 end: "}".to_string(),
11523 close: true,
11524 surround: true,
11525 newline: true,
11526 }],
11527 disabled_scopes_by_bracket_ix: Vec::new(),
11528 },
11529 ..Default::default()
11530 },
11531 Some(tree_sitter_rust::LANGUAGE.into()),
11532 )));
11533 let mut fake_servers = language_registry.register_fake_lsp(
11534 "Rust",
11535 FakeLspAdapter {
11536 capabilities: lsp::ServerCapabilities {
11537 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11538 first_trigger_character: "{".to_string(),
11539 more_trigger_character: None,
11540 }),
11541 ..Default::default()
11542 },
11543 ..Default::default()
11544 },
11545 );
11546
11547 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11548
11549 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11550
11551 let worktree_id = workspace
11552 .update(cx, |workspace, _, cx| {
11553 workspace.project().update(cx, |project, cx| {
11554 project.worktrees(cx).next().unwrap().read(cx).id()
11555 })
11556 })
11557 .unwrap();
11558
11559 let buffer = project
11560 .update(cx, |project, cx| {
11561 project.open_local_buffer(path!("/a/main.rs"), cx)
11562 })
11563 .await
11564 .unwrap();
11565 let editor_handle = workspace
11566 .update(cx, |workspace, window, cx| {
11567 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11568 })
11569 .unwrap()
11570 .await
11571 .unwrap()
11572 .downcast::<Editor>()
11573 .unwrap();
11574
11575 cx.executor().start_waiting();
11576 let fake_server = fake_servers.next().await.unwrap();
11577
11578 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11579 assert_eq!(
11580 params.text_document_position.text_document.uri,
11581 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11582 );
11583 assert_eq!(
11584 params.text_document_position.position,
11585 lsp::Position::new(0, 21),
11586 );
11587
11588 Ok(Some(vec![lsp::TextEdit {
11589 new_text: "]".to_string(),
11590 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11591 }]))
11592 });
11593
11594 editor_handle.update_in(cx, |editor, window, cx| {
11595 window.focus(&editor.focus_handle(cx));
11596 editor.change_selections(None, window, cx, |s| {
11597 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11598 });
11599 editor.handle_input("{", window, cx);
11600 });
11601
11602 cx.executor().run_until_parked();
11603
11604 buffer.update(cx, |buffer, _| {
11605 assert_eq!(
11606 buffer.text(),
11607 "fn main() { let a = {5}; }",
11608 "No extra braces from on type formatting should appear in the buffer"
11609 )
11610 });
11611}
11612
11613#[gpui::test]
11614async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11615 init_test(cx, |_| {});
11616
11617 let fs = FakeFs::new(cx.executor());
11618 fs.insert_tree(
11619 path!("/a"),
11620 json!({
11621 "main.rs": "fn main() { let a = 5; }",
11622 "other.rs": "// Test file",
11623 }),
11624 )
11625 .await;
11626
11627 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11628
11629 let server_restarts = Arc::new(AtomicUsize::new(0));
11630 let closure_restarts = Arc::clone(&server_restarts);
11631 let language_server_name = "test language server";
11632 let language_name: LanguageName = "Rust".into();
11633
11634 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11635 language_registry.add(Arc::new(Language::new(
11636 LanguageConfig {
11637 name: language_name.clone(),
11638 matcher: LanguageMatcher {
11639 path_suffixes: vec!["rs".to_string()],
11640 ..Default::default()
11641 },
11642 ..Default::default()
11643 },
11644 Some(tree_sitter_rust::LANGUAGE.into()),
11645 )));
11646 let mut fake_servers = language_registry.register_fake_lsp(
11647 "Rust",
11648 FakeLspAdapter {
11649 name: language_server_name,
11650 initialization_options: Some(json!({
11651 "testOptionValue": true
11652 })),
11653 initializer: Some(Box::new(move |fake_server| {
11654 let task_restarts = Arc::clone(&closure_restarts);
11655 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11656 task_restarts.fetch_add(1, atomic::Ordering::Release);
11657 futures::future::ready(Ok(()))
11658 });
11659 })),
11660 ..Default::default()
11661 },
11662 );
11663
11664 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11665 let _buffer = project
11666 .update(cx, |project, cx| {
11667 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11668 })
11669 .await
11670 .unwrap();
11671 let _fake_server = fake_servers.next().await.unwrap();
11672 update_test_language_settings(cx, |language_settings| {
11673 language_settings.languages.insert(
11674 language_name.clone(),
11675 LanguageSettingsContent {
11676 tab_size: NonZeroU32::new(8),
11677 ..Default::default()
11678 },
11679 );
11680 });
11681 cx.executor().run_until_parked();
11682 assert_eq!(
11683 server_restarts.load(atomic::Ordering::Acquire),
11684 0,
11685 "Should not restart LSP server on an unrelated change"
11686 );
11687
11688 update_test_project_settings(cx, |project_settings| {
11689 project_settings.lsp.insert(
11690 "Some other server name".into(),
11691 LspSettings {
11692 binary: None,
11693 settings: None,
11694 initialization_options: Some(json!({
11695 "some other init value": false
11696 })),
11697 },
11698 );
11699 });
11700 cx.executor().run_until_parked();
11701 assert_eq!(
11702 server_restarts.load(atomic::Ordering::Acquire),
11703 0,
11704 "Should not restart LSP server on an unrelated LSP settings change"
11705 );
11706
11707 update_test_project_settings(cx, |project_settings| {
11708 project_settings.lsp.insert(
11709 language_server_name.into(),
11710 LspSettings {
11711 binary: None,
11712 settings: None,
11713 initialization_options: Some(json!({
11714 "anotherInitValue": false
11715 })),
11716 },
11717 );
11718 });
11719 cx.executor().run_until_parked();
11720 assert_eq!(
11721 server_restarts.load(atomic::Ordering::Acquire),
11722 1,
11723 "Should restart LSP server on a related LSP settings change"
11724 );
11725
11726 update_test_project_settings(cx, |project_settings| {
11727 project_settings.lsp.insert(
11728 language_server_name.into(),
11729 LspSettings {
11730 binary: None,
11731 settings: None,
11732 initialization_options: Some(json!({
11733 "anotherInitValue": false
11734 })),
11735 },
11736 );
11737 });
11738 cx.executor().run_until_parked();
11739 assert_eq!(
11740 server_restarts.load(atomic::Ordering::Acquire),
11741 1,
11742 "Should not restart LSP server on a related LSP settings change that is the same"
11743 );
11744
11745 update_test_project_settings(cx, |project_settings| {
11746 project_settings.lsp.insert(
11747 language_server_name.into(),
11748 LspSettings {
11749 binary: None,
11750 settings: None,
11751 initialization_options: None,
11752 },
11753 );
11754 });
11755 cx.executor().run_until_parked();
11756 assert_eq!(
11757 server_restarts.load(atomic::Ordering::Acquire),
11758 2,
11759 "Should restart LSP server on another related LSP settings change"
11760 );
11761}
11762
11763#[gpui::test]
11764async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11765 init_test(cx, |_| {});
11766
11767 let mut cx = EditorLspTestContext::new_rust(
11768 lsp::ServerCapabilities {
11769 completion_provider: Some(lsp::CompletionOptions {
11770 trigger_characters: Some(vec![".".to_string()]),
11771 resolve_provider: Some(true),
11772 ..Default::default()
11773 }),
11774 ..Default::default()
11775 },
11776 cx,
11777 )
11778 .await;
11779
11780 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11781 cx.simulate_keystroke(".");
11782 let completion_item = lsp::CompletionItem {
11783 label: "some".into(),
11784 kind: Some(lsp::CompletionItemKind::SNIPPET),
11785 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11786 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11787 kind: lsp::MarkupKind::Markdown,
11788 value: "```rust\nSome(2)\n```".to_string(),
11789 })),
11790 deprecated: Some(false),
11791 sort_text: Some("fffffff2".to_string()),
11792 filter_text: Some("some".to_string()),
11793 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11794 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11795 range: lsp::Range {
11796 start: lsp::Position {
11797 line: 0,
11798 character: 22,
11799 },
11800 end: lsp::Position {
11801 line: 0,
11802 character: 22,
11803 },
11804 },
11805 new_text: "Some(2)".to_string(),
11806 })),
11807 additional_text_edits: Some(vec![lsp::TextEdit {
11808 range: lsp::Range {
11809 start: lsp::Position {
11810 line: 0,
11811 character: 20,
11812 },
11813 end: lsp::Position {
11814 line: 0,
11815 character: 22,
11816 },
11817 },
11818 new_text: "".to_string(),
11819 }]),
11820 ..Default::default()
11821 };
11822
11823 let closure_completion_item = completion_item.clone();
11824 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11825 let task_completion_item = closure_completion_item.clone();
11826 async move {
11827 Ok(Some(lsp::CompletionResponse::Array(vec![
11828 task_completion_item,
11829 ])))
11830 }
11831 });
11832
11833 request.next().await;
11834
11835 cx.condition(|editor, _| editor.context_menu_visible())
11836 .await;
11837 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11838 editor
11839 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11840 .unwrap()
11841 });
11842 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11843
11844 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11845 let task_completion_item = completion_item.clone();
11846 async move { Ok(task_completion_item) }
11847 })
11848 .next()
11849 .await
11850 .unwrap();
11851 apply_additional_edits.await.unwrap();
11852 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11853}
11854
11855#[gpui::test]
11856async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11857 init_test(cx, |_| {});
11858
11859 let mut cx = EditorLspTestContext::new_rust(
11860 lsp::ServerCapabilities {
11861 completion_provider: Some(lsp::CompletionOptions {
11862 trigger_characters: Some(vec![".".to_string()]),
11863 resolve_provider: Some(true),
11864 ..Default::default()
11865 }),
11866 ..Default::default()
11867 },
11868 cx,
11869 )
11870 .await;
11871
11872 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11873 cx.simulate_keystroke(".");
11874
11875 let item1 = lsp::CompletionItem {
11876 label: "method id()".to_string(),
11877 filter_text: Some("id".to_string()),
11878 detail: None,
11879 documentation: None,
11880 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11881 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11882 new_text: ".id".to_string(),
11883 })),
11884 ..lsp::CompletionItem::default()
11885 };
11886
11887 let item2 = lsp::CompletionItem {
11888 label: "other".to_string(),
11889 filter_text: Some("other".to_string()),
11890 detail: None,
11891 documentation: None,
11892 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11893 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11894 new_text: ".other".to_string(),
11895 })),
11896 ..lsp::CompletionItem::default()
11897 };
11898
11899 let item1 = item1.clone();
11900 cx.handle_request::<lsp::request::Completion, _, _>({
11901 let item1 = item1.clone();
11902 move |_, _, _| {
11903 let item1 = item1.clone();
11904 let item2 = item2.clone();
11905 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11906 }
11907 })
11908 .next()
11909 .await;
11910
11911 cx.condition(|editor, _| editor.context_menu_visible())
11912 .await;
11913 cx.update_editor(|editor, _, _| {
11914 let context_menu = editor.context_menu.borrow_mut();
11915 let context_menu = context_menu
11916 .as_ref()
11917 .expect("Should have the context menu deployed");
11918 match context_menu {
11919 CodeContextMenu::Completions(completions_menu) => {
11920 let completions = completions_menu.completions.borrow_mut();
11921 assert_eq!(
11922 completions
11923 .iter()
11924 .map(|completion| &completion.label.text)
11925 .collect::<Vec<_>>(),
11926 vec!["method id()", "other"]
11927 )
11928 }
11929 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11930 }
11931 });
11932
11933 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11934 let item1 = item1.clone();
11935 move |_, item_to_resolve, _| {
11936 let item1 = item1.clone();
11937 async move {
11938 if item1 == item_to_resolve {
11939 Ok(lsp::CompletionItem {
11940 label: "method id()".to_string(),
11941 filter_text: Some("id".to_string()),
11942 detail: Some("Now resolved!".to_string()),
11943 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11944 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11945 range: lsp::Range::new(
11946 lsp::Position::new(0, 22),
11947 lsp::Position::new(0, 22),
11948 ),
11949 new_text: ".id".to_string(),
11950 })),
11951 ..lsp::CompletionItem::default()
11952 })
11953 } else {
11954 Ok(item_to_resolve)
11955 }
11956 }
11957 }
11958 })
11959 .next()
11960 .await
11961 .unwrap();
11962 cx.run_until_parked();
11963
11964 cx.update_editor(|editor, window, cx| {
11965 editor.context_menu_next(&Default::default(), window, cx);
11966 });
11967
11968 cx.update_editor(|editor, _, _| {
11969 let context_menu = editor.context_menu.borrow_mut();
11970 let context_menu = context_menu
11971 .as_ref()
11972 .expect("Should have the context menu deployed");
11973 match context_menu {
11974 CodeContextMenu::Completions(completions_menu) => {
11975 let completions = completions_menu.completions.borrow_mut();
11976 assert_eq!(
11977 completions
11978 .iter()
11979 .map(|completion| &completion.label.text)
11980 .collect::<Vec<_>>(),
11981 vec!["method id() Now resolved!", "other"],
11982 "Should update first completion label, but not second as the filter text did not match."
11983 );
11984 }
11985 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11986 }
11987 });
11988}
11989
11990#[gpui::test]
11991async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
11992 init_test(cx, |_| {});
11993
11994 let mut cx = EditorLspTestContext::new_rust(
11995 lsp::ServerCapabilities {
11996 completion_provider: Some(lsp::CompletionOptions {
11997 trigger_characters: Some(vec![".".to_string()]),
11998 resolve_provider: Some(true),
11999 ..Default::default()
12000 }),
12001 ..Default::default()
12002 },
12003 cx,
12004 )
12005 .await;
12006
12007 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12008 cx.simulate_keystroke(".");
12009
12010 let unresolved_item_1 = lsp::CompletionItem {
12011 label: "id".to_string(),
12012 filter_text: Some("id".to_string()),
12013 detail: None,
12014 documentation: None,
12015 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12016 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12017 new_text: ".id".to_string(),
12018 })),
12019 ..lsp::CompletionItem::default()
12020 };
12021 let resolved_item_1 = lsp::CompletionItem {
12022 additional_text_edits: Some(vec![lsp::TextEdit {
12023 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12024 new_text: "!!".to_string(),
12025 }]),
12026 ..unresolved_item_1.clone()
12027 };
12028 let unresolved_item_2 = lsp::CompletionItem {
12029 label: "other".to_string(),
12030 filter_text: Some("other".to_string()),
12031 detail: None,
12032 documentation: None,
12033 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12034 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12035 new_text: ".other".to_string(),
12036 })),
12037 ..lsp::CompletionItem::default()
12038 };
12039 let resolved_item_2 = lsp::CompletionItem {
12040 additional_text_edits: Some(vec![lsp::TextEdit {
12041 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12042 new_text: "??".to_string(),
12043 }]),
12044 ..unresolved_item_2.clone()
12045 };
12046
12047 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12048 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12049 cx.lsp
12050 .server
12051 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12052 let unresolved_item_1 = unresolved_item_1.clone();
12053 let resolved_item_1 = resolved_item_1.clone();
12054 let unresolved_item_2 = unresolved_item_2.clone();
12055 let resolved_item_2 = resolved_item_2.clone();
12056 let resolve_requests_1 = resolve_requests_1.clone();
12057 let resolve_requests_2 = resolve_requests_2.clone();
12058 move |unresolved_request, _| {
12059 let unresolved_item_1 = unresolved_item_1.clone();
12060 let resolved_item_1 = resolved_item_1.clone();
12061 let unresolved_item_2 = unresolved_item_2.clone();
12062 let resolved_item_2 = resolved_item_2.clone();
12063 let resolve_requests_1 = resolve_requests_1.clone();
12064 let resolve_requests_2 = resolve_requests_2.clone();
12065 async move {
12066 if unresolved_request == unresolved_item_1 {
12067 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12068 Ok(resolved_item_1.clone())
12069 } else if unresolved_request == unresolved_item_2 {
12070 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12071 Ok(resolved_item_2.clone())
12072 } else {
12073 panic!("Unexpected completion item {unresolved_request:?}")
12074 }
12075 }
12076 }
12077 })
12078 .detach();
12079
12080 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12081 let unresolved_item_1 = unresolved_item_1.clone();
12082 let unresolved_item_2 = unresolved_item_2.clone();
12083 async move {
12084 Ok(Some(lsp::CompletionResponse::Array(vec![
12085 unresolved_item_1,
12086 unresolved_item_2,
12087 ])))
12088 }
12089 })
12090 .next()
12091 .await;
12092
12093 cx.condition(|editor, _| editor.context_menu_visible())
12094 .await;
12095 cx.update_editor(|editor, _, _| {
12096 let context_menu = editor.context_menu.borrow_mut();
12097 let context_menu = context_menu
12098 .as_ref()
12099 .expect("Should have the context menu deployed");
12100 match context_menu {
12101 CodeContextMenu::Completions(completions_menu) => {
12102 let completions = completions_menu.completions.borrow_mut();
12103 assert_eq!(
12104 completions
12105 .iter()
12106 .map(|completion| &completion.label.text)
12107 .collect::<Vec<_>>(),
12108 vec!["id", "other"]
12109 )
12110 }
12111 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12112 }
12113 });
12114 cx.run_until_parked();
12115
12116 cx.update_editor(|editor, window, cx| {
12117 editor.context_menu_next(&ContextMenuNext, window, cx);
12118 });
12119 cx.run_until_parked();
12120 cx.update_editor(|editor, window, cx| {
12121 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12122 });
12123 cx.run_until_parked();
12124 cx.update_editor(|editor, window, cx| {
12125 editor.context_menu_next(&ContextMenuNext, window, cx);
12126 });
12127 cx.run_until_parked();
12128 cx.update_editor(|editor, window, cx| {
12129 editor
12130 .compose_completion(&ComposeCompletion::default(), window, cx)
12131 .expect("No task returned")
12132 })
12133 .await
12134 .expect("Completion failed");
12135 cx.run_until_parked();
12136
12137 cx.update_editor(|editor, _, cx| {
12138 assert_eq!(
12139 resolve_requests_1.load(atomic::Ordering::Acquire),
12140 1,
12141 "Should always resolve once despite multiple selections"
12142 );
12143 assert_eq!(
12144 resolve_requests_2.load(atomic::Ordering::Acquire),
12145 1,
12146 "Should always resolve once after multiple selections and applying the completion"
12147 );
12148 assert_eq!(
12149 editor.text(cx),
12150 "fn main() { let a = ??.other; }",
12151 "Should use resolved data when applying the completion"
12152 );
12153 });
12154}
12155
12156#[gpui::test]
12157async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12158 init_test(cx, |_| {});
12159
12160 let item_0 = lsp::CompletionItem {
12161 label: "abs".into(),
12162 insert_text: Some("abs".into()),
12163 data: Some(json!({ "very": "special"})),
12164 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12165 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12166 lsp::InsertReplaceEdit {
12167 new_text: "abs".to_string(),
12168 insert: lsp::Range::default(),
12169 replace: lsp::Range::default(),
12170 },
12171 )),
12172 ..lsp::CompletionItem::default()
12173 };
12174 let items = iter::once(item_0.clone())
12175 .chain((11..51).map(|i| lsp::CompletionItem {
12176 label: format!("item_{}", i),
12177 insert_text: Some(format!("item_{}", i)),
12178 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12179 ..lsp::CompletionItem::default()
12180 }))
12181 .collect::<Vec<_>>();
12182
12183 let default_commit_characters = vec!["?".to_string()];
12184 let default_data = json!({ "default": "data"});
12185 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12186 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12187 let default_edit_range = lsp::Range {
12188 start: lsp::Position {
12189 line: 0,
12190 character: 5,
12191 },
12192 end: lsp::Position {
12193 line: 0,
12194 character: 5,
12195 },
12196 };
12197
12198 let item_0_out = lsp::CompletionItem {
12199 commit_characters: Some(default_commit_characters.clone()),
12200 insert_text_format: Some(default_insert_text_format),
12201 ..item_0
12202 };
12203 let items_out = iter::once(item_0_out)
12204 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
12205 commit_characters: Some(default_commit_characters.clone()),
12206 data: Some(default_data.clone()),
12207 insert_text_mode: Some(default_insert_text_mode),
12208 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12209 range: default_edit_range,
12210 new_text: item.label.clone(),
12211 })),
12212 ..item.clone()
12213 }))
12214 .collect::<Vec<lsp::CompletionItem>>();
12215
12216 let mut cx = EditorLspTestContext::new_rust(
12217 lsp::ServerCapabilities {
12218 completion_provider: Some(lsp::CompletionOptions {
12219 trigger_characters: Some(vec![".".to_string()]),
12220 resolve_provider: Some(true),
12221 ..Default::default()
12222 }),
12223 ..Default::default()
12224 },
12225 cx,
12226 )
12227 .await;
12228
12229 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12230 cx.simulate_keystroke(".");
12231
12232 let completion_data = default_data.clone();
12233 let completion_characters = default_commit_characters.clone();
12234 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12235 let default_data = completion_data.clone();
12236 let default_commit_characters = completion_characters.clone();
12237 let items = items.clone();
12238 async move {
12239 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12240 items,
12241 item_defaults: Some(lsp::CompletionListItemDefaults {
12242 data: Some(default_data.clone()),
12243 commit_characters: Some(default_commit_characters.clone()),
12244 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12245 default_edit_range,
12246 )),
12247 insert_text_format: Some(default_insert_text_format),
12248 insert_text_mode: Some(default_insert_text_mode),
12249 }),
12250 ..lsp::CompletionList::default()
12251 })))
12252 }
12253 })
12254 .next()
12255 .await;
12256
12257 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12258 cx.lsp
12259 .server
12260 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12261 let closure_resolved_items = resolved_items.clone();
12262 move |item_to_resolve, _| {
12263 let closure_resolved_items = closure_resolved_items.clone();
12264 async move {
12265 closure_resolved_items.lock().push(item_to_resolve.clone());
12266 Ok(item_to_resolve)
12267 }
12268 }
12269 })
12270 .detach();
12271
12272 cx.condition(|editor, _| editor.context_menu_visible())
12273 .await;
12274 cx.run_until_parked();
12275 cx.update_editor(|editor, _, _| {
12276 let menu = editor.context_menu.borrow_mut();
12277 match menu.as_ref().expect("should have the completions menu") {
12278 CodeContextMenu::Completions(completions_menu) => {
12279 assert_eq!(
12280 completions_menu
12281 .entries
12282 .borrow()
12283 .iter()
12284 .map(|mat| mat.string.clone())
12285 .collect::<Vec<String>>(),
12286 items_out
12287 .iter()
12288 .map(|completion| completion.label.clone())
12289 .collect::<Vec<String>>()
12290 );
12291 }
12292 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12293 }
12294 });
12295 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12296 // with 4 from the end.
12297 assert_eq!(
12298 *resolved_items.lock(),
12299 [
12300 &items_out[0..16],
12301 &items_out[items_out.len() - 4..items_out.len()]
12302 ]
12303 .concat()
12304 .iter()
12305 .cloned()
12306 .collect::<Vec<lsp::CompletionItem>>()
12307 );
12308 resolved_items.lock().clear();
12309
12310 cx.update_editor(|editor, window, cx| {
12311 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12312 });
12313 cx.run_until_parked();
12314 // Completions that have already been resolved are skipped.
12315 assert_eq!(
12316 *resolved_items.lock(),
12317 items_out[items_out.len() - 16..items_out.len() - 4]
12318 .iter()
12319 .cloned()
12320 .collect::<Vec<lsp::CompletionItem>>()
12321 );
12322 resolved_items.lock().clear();
12323}
12324
12325#[gpui::test]
12326async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12327 init_test(cx, |_| {});
12328
12329 let mut cx = EditorLspTestContext::new(
12330 Language::new(
12331 LanguageConfig {
12332 matcher: LanguageMatcher {
12333 path_suffixes: vec!["jsx".into()],
12334 ..Default::default()
12335 },
12336 overrides: [(
12337 "element".into(),
12338 LanguageConfigOverride {
12339 word_characters: Override::Set(['-'].into_iter().collect()),
12340 ..Default::default()
12341 },
12342 )]
12343 .into_iter()
12344 .collect(),
12345 ..Default::default()
12346 },
12347 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12348 )
12349 .with_override_query("(jsx_self_closing_element) @element")
12350 .unwrap(),
12351 lsp::ServerCapabilities {
12352 completion_provider: Some(lsp::CompletionOptions {
12353 trigger_characters: Some(vec![":".to_string()]),
12354 ..Default::default()
12355 }),
12356 ..Default::default()
12357 },
12358 cx,
12359 )
12360 .await;
12361
12362 cx.lsp
12363 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12364 Ok(Some(lsp::CompletionResponse::Array(vec![
12365 lsp::CompletionItem {
12366 label: "bg-blue".into(),
12367 ..Default::default()
12368 },
12369 lsp::CompletionItem {
12370 label: "bg-red".into(),
12371 ..Default::default()
12372 },
12373 lsp::CompletionItem {
12374 label: "bg-yellow".into(),
12375 ..Default::default()
12376 },
12377 ])))
12378 });
12379
12380 cx.set_state(r#"<p class="bgˇ" />"#);
12381
12382 // Trigger completion when typing a dash, because the dash is an extra
12383 // word character in the 'element' scope, which contains the cursor.
12384 cx.simulate_keystroke("-");
12385 cx.executor().run_until_parked();
12386 cx.update_editor(|editor, _, _| {
12387 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12388 {
12389 assert_eq!(
12390 completion_menu_entries(&menu),
12391 &["bg-red", "bg-blue", "bg-yellow"]
12392 );
12393 } else {
12394 panic!("expected completion menu to be open");
12395 }
12396 });
12397
12398 cx.simulate_keystroke("l");
12399 cx.executor().run_until_parked();
12400 cx.update_editor(|editor, _, _| {
12401 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12402 {
12403 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12404 } else {
12405 panic!("expected completion menu to be open");
12406 }
12407 });
12408
12409 // When filtering completions, consider the character after the '-' to
12410 // be the start of a subword.
12411 cx.set_state(r#"<p class="yelˇ" />"#);
12412 cx.simulate_keystroke("l");
12413 cx.executor().run_until_parked();
12414 cx.update_editor(|editor, _, _| {
12415 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12416 {
12417 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12418 } else {
12419 panic!("expected completion menu to be open");
12420 }
12421 });
12422}
12423
12424fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12425 let entries = menu.entries.borrow();
12426 entries.iter().map(|mat| mat.string.clone()).collect()
12427}
12428
12429#[gpui::test]
12430async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12431 init_test(cx, |settings| {
12432 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12433 FormatterList(vec![Formatter::Prettier].into()),
12434 ))
12435 });
12436
12437 let fs = FakeFs::new(cx.executor());
12438 fs.insert_file(path!("/file.ts"), Default::default()).await;
12439
12440 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12441 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12442
12443 language_registry.add(Arc::new(Language::new(
12444 LanguageConfig {
12445 name: "TypeScript".into(),
12446 matcher: LanguageMatcher {
12447 path_suffixes: vec!["ts".to_string()],
12448 ..Default::default()
12449 },
12450 ..Default::default()
12451 },
12452 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12453 )));
12454 update_test_language_settings(cx, |settings| {
12455 settings.defaults.prettier = Some(PrettierSettings {
12456 allowed: true,
12457 ..PrettierSettings::default()
12458 });
12459 });
12460
12461 let test_plugin = "test_plugin";
12462 let _ = language_registry.register_fake_lsp(
12463 "TypeScript",
12464 FakeLspAdapter {
12465 prettier_plugins: vec![test_plugin],
12466 ..Default::default()
12467 },
12468 );
12469
12470 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12471 let buffer = project
12472 .update(cx, |project, cx| {
12473 project.open_local_buffer(path!("/file.ts"), cx)
12474 })
12475 .await
12476 .unwrap();
12477
12478 let buffer_text = "one\ntwo\nthree\n";
12479 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12480 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12481 editor.update_in(cx, |editor, window, cx| {
12482 editor.set_text(buffer_text, window, cx)
12483 });
12484
12485 editor
12486 .update_in(cx, |editor, window, cx| {
12487 editor.perform_format(
12488 project.clone(),
12489 FormatTrigger::Manual,
12490 FormatTarget::Buffers,
12491 window,
12492 cx,
12493 )
12494 })
12495 .unwrap()
12496 .await;
12497 assert_eq!(
12498 editor.update(cx, |editor, cx| editor.text(cx)),
12499 buffer_text.to_string() + prettier_format_suffix,
12500 "Test prettier formatting was not applied to the original buffer text",
12501 );
12502
12503 update_test_language_settings(cx, |settings| {
12504 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12505 });
12506 let format = editor.update_in(cx, |editor, window, cx| {
12507 editor.perform_format(
12508 project.clone(),
12509 FormatTrigger::Manual,
12510 FormatTarget::Buffers,
12511 window,
12512 cx,
12513 )
12514 });
12515 format.await.unwrap();
12516 assert_eq!(
12517 editor.update(cx, |editor, cx| editor.text(cx)),
12518 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12519 "Autoformatting (via test prettier) was not applied to the original buffer text",
12520 );
12521}
12522
12523#[gpui::test]
12524async fn test_addition_reverts(cx: &mut TestAppContext) {
12525 init_test(cx, |_| {});
12526 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12527 let base_text = indoc! {r#"
12528 struct Row;
12529 struct Row1;
12530 struct Row2;
12531
12532 struct Row4;
12533 struct Row5;
12534 struct Row6;
12535
12536 struct Row8;
12537 struct Row9;
12538 struct Row10;"#};
12539
12540 // When addition hunks are not adjacent to carets, no hunk revert is performed
12541 assert_hunk_revert(
12542 indoc! {r#"struct Row;
12543 struct Row1;
12544 struct Row1.1;
12545 struct Row1.2;
12546 struct Row2;ˇ
12547
12548 struct Row4;
12549 struct Row5;
12550 struct Row6;
12551
12552 struct Row8;
12553 ˇstruct Row9;
12554 struct Row9.1;
12555 struct Row9.2;
12556 struct Row9.3;
12557 struct Row10;"#},
12558 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12559 indoc! {r#"struct Row;
12560 struct Row1;
12561 struct Row1.1;
12562 struct Row1.2;
12563 struct Row2;ˇ
12564
12565 struct Row4;
12566 struct Row5;
12567 struct Row6;
12568
12569 struct Row8;
12570 ˇstruct Row9;
12571 struct Row9.1;
12572 struct Row9.2;
12573 struct Row9.3;
12574 struct Row10;"#},
12575 base_text,
12576 &mut cx,
12577 );
12578 // Same for selections
12579 assert_hunk_revert(
12580 indoc! {r#"struct Row;
12581 struct Row1;
12582 struct Row2;
12583 struct Row2.1;
12584 struct Row2.2;
12585 «ˇ
12586 struct Row4;
12587 struct» Row5;
12588 «struct Row6;
12589 ˇ»
12590 struct Row9.1;
12591 struct Row9.2;
12592 struct Row9.3;
12593 struct Row8;
12594 struct Row9;
12595 struct Row10;"#},
12596 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12597 indoc! {r#"struct Row;
12598 struct Row1;
12599 struct Row2;
12600 struct Row2.1;
12601 struct Row2.2;
12602 «ˇ
12603 struct Row4;
12604 struct» Row5;
12605 «struct Row6;
12606 ˇ»
12607 struct Row9.1;
12608 struct Row9.2;
12609 struct Row9.3;
12610 struct Row8;
12611 struct Row9;
12612 struct Row10;"#},
12613 base_text,
12614 &mut cx,
12615 );
12616
12617 // When carets and selections intersect the addition hunks, those are reverted.
12618 // Adjacent carets got merged.
12619 assert_hunk_revert(
12620 indoc! {r#"struct Row;
12621 ˇ// something on the top
12622 struct Row1;
12623 struct Row2;
12624 struct Roˇw3.1;
12625 struct Row2.2;
12626 struct Row2.3;ˇ
12627
12628 struct Row4;
12629 struct ˇRow5.1;
12630 struct Row5.2;
12631 struct «Rowˇ»5.3;
12632 struct Row5;
12633 struct Row6;
12634 ˇ
12635 struct Row9.1;
12636 struct «Rowˇ»9.2;
12637 struct «ˇRow»9.3;
12638 struct Row8;
12639 struct Row9;
12640 «ˇ// something on bottom»
12641 struct Row10;"#},
12642 vec![
12643 DiffHunkStatus::added_none(),
12644 DiffHunkStatus::added_none(),
12645 DiffHunkStatus::added_none(),
12646 DiffHunkStatus::added_none(),
12647 DiffHunkStatus::added_none(),
12648 ],
12649 indoc! {r#"struct Row;
12650 ˇstruct Row1;
12651 struct Row2;
12652 ˇ
12653 struct Row4;
12654 ˇstruct Row5;
12655 struct Row6;
12656 ˇ
12657 ˇstruct Row8;
12658 struct Row9;
12659 ˇstruct Row10;"#},
12660 base_text,
12661 &mut cx,
12662 );
12663}
12664
12665#[gpui::test]
12666async fn test_modification_reverts(cx: &mut TestAppContext) {
12667 init_test(cx, |_| {});
12668 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12669 let base_text = indoc! {r#"
12670 struct Row;
12671 struct Row1;
12672 struct Row2;
12673
12674 struct Row4;
12675 struct Row5;
12676 struct Row6;
12677
12678 struct Row8;
12679 struct Row9;
12680 struct Row10;"#};
12681
12682 // Modification hunks behave the same as the addition ones.
12683 assert_hunk_revert(
12684 indoc! {r#"struct Row;
12685 struct Row1;
12686 struct Row33;
12687 ˇ
12688 struct Row4;
12689 struct Row5;
12690 struct Row6;
12691 ˇ
12692 struct Row99;
12693 struct Row9;
12694 struct Row10;"#},
12695 vec![
12696 DiffHunkStatus::modified_none(),
12697 DiffHunkStatus::modified_none(),
12698 ],
12699 indoc! {r#"struct Row;
12700 struct Row1;
12701 struct Row33;
12702 ˇ
12703 struct Row4;
12704 struct Row5;
12705 struct Row6;
12706 ˇ
12707 struct Row99;
12708 struct Row9;
12709 struct Row10;"#},
12710 base_text,
12711 &mut cx,
12712 );
12713 assert_hunk_revert(
12714 indoc! {r#"struct Row;
12715 struct Row1;
12716 struct Row33;
12717 «ˇ
12718 struct Row4;
12719 struct» Row5;
12720 «struct Row6;
12721 ˇ»
12722 struct Row99;
12723 struct Row9;
12724 struct Row10;"#},
12725 vec![
12726 DiffHunkStatus::modified_none(),
12727 DiffHunkStatus::modified_none(),
12728 ],
12729 indoc! {r#"struct Row;
12730 struct Row1;
12731 struct Row33;
12732 «ˇ
12733 struct Row4;
12734 struct» Row5;
12735 «struct Row6;
12736 ˇ»
12737 struct Row99;
12738 struct Row9;
12739 struct Row10;"#},
12740 base_text,
12741 &mut cx,
12742 );
12743
12744 assert_hunk_revert(
12745 indoc! {r#"ˇstruct Row1.1;
12746 struct Row1;
12747 «ˇstr»uct Row22;
12748
12749 struct ˇRow44;
12750 struct Row5;
12751 struct «Rˇ»ow66;ˇ
12752
12753 «struˇ»ct Row88;
12754 struct Row9;
12755 struct Row1011;ˇ"#},
12756 vec![
12757 DiffHunkStatus::modified_none(),
12758 DiffHunkStatus::modified_none(),
12759 DiffHunkStatus::modified_none(),
12760 DiffHunkStatus::modified_none(),
12761 DiffHunkStatus::modified_none(),
12762 DiffHunkStatus::modified_none(),
12763 ],
12764 indoc! {r#"struct Row;
12765 ˇstruct Row1;
12766 struct Row2;
12767 ˇ
12768 struct Row4;
12769 ˇstruct Row5;
12770 struct Row6;
12771 ˇ
12772 struct Row8;
12773 ˇstruct Row9;
12774 struct Row10;ˇ"#},
12775 base_text,
12776 &mut cx,
12777 );
12778}
12779
12780#[gpui::test]
12781async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12782 init_test(cx, |_| {});
12783 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12784 let base_text = indoc! {r#"
12785 one
12786
12787 two
12788 three
12789 "#};
12790
12791 cx.set_head_text(base_text);
12792 cx.set_state("\nˇ\n");
12793 cx.executor().run_until_parked();
12794 cx.update_editor(|editor, _window, cx| {
12795 editor.expand_selected_diff_hunks(cx);
12796 });
12797 cx.executor().run_until_parked();
12798 cx.update_editor(|editor, window, cx| {
12799 editor.backspace(&Default::default(), window, cx);
12800 });
12801 cx.run_until_parked();
12802 cx.assert_state_with_diff(
12803 indoc! {r#"
12804
12805 - two
12806 - threeˇ
12807 +
12808 "#}
12809 .to_string(),
12810 );
12811}
12812
12813#[gpui::test]
12814async fn test_deletion_reverts(cx: &mut TestAppContext) {
12815 init_test(cx, |_| {});
12816 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12817 let base_text = indoc! {r#"struct Row;
12818struct Row1;
12819struct Row2;
12820
12821struct Row4;
12822struct Row5;
12823struct Row6;
12824
12825struct Row8;
12826struct Row9;
12827struct Row10;"#};
12828
12829 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12830 assert_hunk_revert(
12831 indoc! {r#"struct Row;
12832 struct Row2;
12833
12834 ˇstruct Row4;
12835 struct Row5;
12836 struct Row6;
12837 ˇ
12838 struct Row8;
12839 struct Row10;"#},
12840 vec![
12841 DiffHunkStatus::deleted_none(),
12842 DiffHunkStatus::deleted_none(),
12843 ],
12844 indoc! {r#"struct Row;
12845 struct Row2;
12846
12847 ˇstruct Row4;
12848 struct Row5;
12849 struct Row6;
12850 ˇ
12851 struct Row8;
12852 struct Row10;"#},
12853 base_text,
12854 &mut cx,
12855 );
12856 assert_hunk_revert(
12857 indoc! {r#"struct Row;
12858 struct Row2;
12859
12860 «ˇstruct Row4;
12861 struct» Row5;
12862 «struct Row6;
12863 ˇ»
12864 struct Row8;
12865 struct Row10;"#},
12866 vec![
12867 DiffHunkStatus::deleted_none(),
12868 DiffHunkStatus::deleted_none(),
12869 ],
12870 indoc! {r#"struct Row;
12871 struct Row2;
12872
12873 «ˇstruct Row4;
12874 struct» Row5;
12875 «struct Row6;
12876 ˇ»
12877 struct Row8;
12878 struct Row10;"#},
12879 base_text,
12880 &mut cx,
12881 );
12882
12883 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12884 assert_hunk_revert(
12885 indoc! {r#"struct Row;
12886 ˇstruct Row2;
12887
12888 struct Row4;
12889 struct Row5;
12890 struct Row6;
12891
12892 struct Row8;ˇ
12893 struct Row10;"#},
12894 vec![
12895 DiffHunkStatus::deleted_none(),
12896 DiffHunkStatus::deleted_none(),
12897 ],
12898 indoc! {r#"struct Row;
12899 struct Row1;
12900 ˇstruct Row2;
12901
12902 struct Row4;
12903 struct Row5;
12904 struct Row6;
12905
12906 struct Row8;ˇ
12907 struct Row9;
12908 struct Row10;"#},
12909 base_text,
12910 &mut cx,
12911 );
12912 assert_hunk_revert(
12913 indoc! {r#"struct Row;
12914 struct Row2«ˇ;
12915 struct Row4;
12916 struct» Row5;
12917 «struct Row6;
12918
12919 struct Row8;ˇ»
12920 struct Row10;"#},
12921 vec![
12922 DiffHunkStatus::deleted_none(),
12923 DiffHunkStatus::deleted_none(),
12924 DiffHunkStatus::deleted_none(),
12925 ],
12926 indoc! {r#"struct Row;
12927 struct Row1;
12928 struct Row2«ˇ;
12929
12930 struct Row4;
12931 struct» Row5;
12932 «struct Row6;
12933
12934 struct Row8;ˇ»
12935 struct Row9;
12936 struct Row10;"#},
12937 base_text,
12938 &mut cx,
12939 );
12940}
12941
12942#[gpui::test]
12943async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
12944 init_test(cx, |_| {});
12945
12946 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12947 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12948 let base_text_3 =
12949 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12950
12951 let text_1 = edit_first_char_of_every_line(base_text_1);
12952 let text_2 = edit_first_char_of_every_line(base_text_2);
12953 let text_3 = edit_first_char_of_every_line(base_text_3);
12954
12955 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12956 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12957 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12958
12959 let multibuffer = cx.new(|cx| {
12960 let mut multibuffer = MultiBuffer::new(ReadWrite);
12961 multibuffer.push_excerpts(
12962 buffer_1.clone(),
12963 [
12964 ExcerptRange {
12965 context: Point::new(0, 0)..Point::new(3, 0),
12966 primary: None,
12967 },
12968 ExcerptRange {
12969 context: Point::new(5, 0)..Point::new(7, 0),
12970 primary: None,
12971 },
12972 ExcerptRange {
12973 context: Point::new(9, 0)..Point::new(10, 4),
12974 primary: None,
12975 },
12976 ],
12977 cx,
12978 );
12979 multibuffer.push_excerpts(
12980 buffer_2.clone(),
12981 [
12982 ExcerptRange {
12983 context: Point::new(0, 0)..Point::new(3, 0),
12984 primary: None,
12985 },
12986 ExcerptRange {
12987 context: Point::new(5, 0)..Point::new(7, 0),
12988 primary: None,
12989 },
12990 ExcerptRange {
12991 context: Point::new(9, 0)..Point::new(10, 4),
12992 primary: None,
12993 },
12994 ],
12995 cx,
12996 );
12997 multibuffer.push_excerpts(
12998 buffer_3.clone(),
12999 [
13000 ExcerptRange {
13001 context: Point::new(0, 0)..Point::new(3, 0),
13002 primary: None,
13003 },
13004 ExcerptRange {
13005 context: Point::new(5, 0)..Point::new(7, 0),
13006 primary: None,
13007 },
13008 ExcerptRange {
13009 context: Point::new(9, 0)..Point::new(10, 4),
13010 primary: None,
13011 },
13012 ],
13013 cx,
13014 );
13015 multibuffer
13016 });
13017
13018 let fs = FakeFs::new(cx.executor());
13019 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13020 let (editor, cx) = cx
13021 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13022 editor.update_in(cx, |editor, _window, cx| {
13023 for (buffer, diff_base) in [
13024 (buffer_1.clone(), base_text_1),
13025 (buffer_2.clone(), base_text_2),
13026 (buffer_3.clone(), base_text_3),
13027 ] {
13028 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13029 editor
13030 .buffer
13031 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13032 }
13033 });
13034 cx.executor().run_until_parked();
13035
13036 editor.update_in(cx, |editor, window, cx| {
13037 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}");
13038 editor.select_all(&SelectAll, window, cx);
13039 editor.git_restore(&Default::default(), window, cx);
13040 });
13041 cx.executor().run_until_parked();
13042
13043 // When all ranges are selected, all buffer hunks are reverted.
13044 editor.update(cx, |editor, cx| {
13045 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");
13046 });
13047 buffer_1.update(cx, |buffer, _| {
13048 assert_eq!(buffer.text(), base_text_1);
13049 });
13050 buffer_2.update(cx, |buffer, _| {
13051 assert_eq!(buffer.text(), base_text_2);
13052 });
13053 buffer_3.update(cx, |buffer, _| {
13054 assert_eq!(buffer.text(), base_text_3);
13055 });
13056
13057 editor.update_in(cx, |editor, window, cx| {
13058 editor.undo(&Default::default(), window, cx);
13059 });
13060
13061 editor.update_in(cx, |editor, window, cx| {
13062 editor.change_selections(None, window, cx, |s| {
13063 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13064 });
13065 editor.git_restore(&Default::default(), window, cx);
13066 });
13067
13068 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13069 // but not affect buffer_2 and its related excerpts.
13070 editor.update(cx, |editor, cx| {
13071 assert_eq!(
13072 editor.text(cx),
13073 "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}"
13074 );
13075 });
13076 buffer_1.update(cx, |buffer, _| {
13077 assert_eq!(buffer.text(), base_text_1);
13078 });
13079 buffer_2.update(cx, |buffer, _| {
13080 assert_eq!(
13081 buffer.text(),
13082 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13083 );
13084 });
13085 buffer_3.update(cx, |buffer, _| {
13086 assert_eq!(
13087 buffer.text(),
13088 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13089 );
13090 });
13091
13092 fn edit_first_char_of_every_line(text: &str) -> String {
13093 text.split('\n')
13094 .map(|line| format!("X{}", &line[1..]))
13095 .collect::<Vec<_>>()
13096 .join("\n")
13097 }
13098}
13099
13100#[gpui::test]
13101async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13102 init_test(cx, |_| {});
13103
13104 let cols = 4;
13105 let rows = 10;
13106 let sample_text_1 = sample_text(rows, cols, 'a');
13107 assert_eq!(
13108 sample_text_1,
13109 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13110 );
13111 let sample_text_2 = sample_text(rows, cols, 'l');
13112 assert_eq!(
13113 sample_text_2,
13114 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13115 );
13116 let sample_text_3 = sample_text(rows, cols, 'v');
13117 assert_eq!(
13118 sample_text_3,
13119 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13120 );
13121
13122 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13123 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13124 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13125
13126 let multi_buffer = cx.new(|cx| {
13127 let mut multibuffer = MultiBuffer::new(ReadWrite);
13128 multibuffer.push_excerpts(
13129 buffer_1.clone(),
13130 [
13131 ExcerptRange {
13132 context: Point::new(0, 0)..Point::new(3, 0),
13133 primary: None,
13134 },
13135 ExcerptRange {
13136 context: Point::new(5, 0)..Point::new(7, 0),
13137 primary: None,
13138 },
13139 ExcerptRange {
13140 context: Point::new(9, 0)..Point::new(10, 4),
13141 primary: None,
13142 },
13143 ],
13144 cx,
13145 );
13146 multibuffer.push_excerpts(
13147 buffer_2.clone(),
13148 [
13149 ExcerptRange {
13150 context: Point::new(0, 0)..Point::new(3, 0),
13151 primary: None,
13152 },
13153 ExcerptRange {
13154 context: Point::new(5, 0)..Point::new(7, 0),
13155 primary: None,
13156 },
13157 ExcerptRange {
13158 context: Point::new(9, 0)..Point::new(10, 4),
13159 primary: None,
13160 },
13161 ],
13162 cx,
13163 );
13164 multibuffer.push_excerpts(
13165 buffer_3.clone(),
13166 [
13167 ExcerptRange {
13168 context: Point::new(0, 0)..Point::new(3, 0),
13169 primary: None,
13170 },
13171 ExcerptRange {
13172 context: Point::new(5, 0)..Point::new(7, 0),
13173 primary: None,
13174 },
13175 ExcerptRange {
13176 context: Point::new(9, 0)..Point::new(10, 4),
13177 primary: None,
13178 },
13179 ],
13180 cx,
13181 );
13182 multibuffer
13183 });
13184
13185 let fs = FakeFs::new(cx.executor());
13186 fs.insert_tree(
13187 "/a",
13188 json!({
13189 "main.rs": sample_text_1,
13190 "other.rs": sample_text_2,
13191 "lib.rs": sample_text_3,
13192 }),
13193 )
13194 .await;
13195 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13196 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13197 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13198 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13199 Editor::new(
13200 EditorMode::Full,
13201 multi_buffer,
13202 Some(project.clone()),
13203 true,
13204 window,
13205 cx,
13206 )
13207 });
13208 let multibuffer_item_id = workspace
13209 .update(cx, |workspace, window, cx| {
13210 assert!(
13211 workspace.active_item(cx).is_none(),
13212 "active item should be None before the first item is added"
13213 );
13214 workspace.add_item_to_active_pane(
13215 Box::new(multi_buffer_editor.clone()),
13216 None,
13217 true,
13218 window,
13219 cx,
13220 );
13221 let active_item = workspace
13222 .active_item(cx)
13223 .expect("should have an active item after adding the multi buffer");
13224 assert!(
13225 !active_item.is_singleton(cx),
13226 "A multi buffer was expected to active after adding"
13227 );
13228 active_item.item_id()
13229 })
13230 .unwrap();
13231 cx.executor().run_until_parked();
13232
13233 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13234 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13235 s.select_ranges(Some(1..2))
13236 });
13237 editor.open_excerpts(&OpenExcerpts, window, cx);
13238 });
13239 cx.executor().run_until_parked();
13240 let first_item_id = workspace
13241 .update(cx, |workspace, window, cx| {
13242 let active_item = workspace
13243 .active_item(cx)
13244 .expect("should have an active item after navigating into the 1st buffer");
13245 let first_item_id = active_item.item_id();
13246 assert_ne!(
13247 first_item_id, multibuffer_item_id,
13248 "Should navigate into the 1st buffer and activate it"
13249 );
13250 assert!(
13251 active_item.is_singleton(cx),
13252 "New active item should be a singleton buffer"
13253 );
13254 assert_eq!(
13255 active_item
13256 .act_as::<Editor>(cx)
13257 .expect("should have navigated into an editor for the 1st buffer")
13258 .read(cx)
13259 .text(cx),
13260 sample_text_1
13261 );
13262
13263 workspace
13264 .go_back(workspace.active_pane().downgrade(), window, cx)
13265 .detach_and_log_err(cx);
13266
13267 first_item_id
13268 })
13269 .unwrap();
13270 cx.executor().run_until_parked();
13271 workspace
13272 .update(cx, |workspace, _, cx| {
13273 let active_item = workspace
13274 .active_item(cx)
13275 .expect("should have an active item after navigating back");
13276 assert_eq!(
13277 active_item.item_id(),
13278 multibuffer_item_id,
13279 "Should navigate back to the multi buffer"
13280 );
13281 assert!(!active_item.is_singleton(cx));
13282 })
13283 .unwrap();
13284
13285 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13286 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13287 s.select_ranges(Some(39..40))
13288 });
13289 editor.open_excerpts(&OpenExcerpts, window, cx);
13290 });
13291 cx.executor().run_until_parked();
13292 let second_item_id = workspace
13293 .update(cx, |workspace, window, cx| {
13294 let active_item = workspace
13295 .active_item(cx)
13296 .expect("should have an active item after navigating into the 2nd buffer");
13297 let second_item_id = active_item.item_id();
13298 assert_ne!(
13299 second_item_id, multibuffer_item_id,
13300 "Should navigate away from the multibuffer"
13301 );
13302 assert_ne!(
13303 second_item_id, first_item_id,
13304 "Should navigate into the 2nd buffer and activate it"
13305 );
13306 assert!(
13307 active_item.is_singleton(cx),
13308 "New active item should be a singleton buffer"
13309 );
13310 assert_eq!(
13311 active_item
13312 .act_as::<Editor>(cx)
13313 .expect("should have navigated into an editor")
13314 .read(cx)
13315 .text(cx),
13316 sample_text_2
13317 );
13318
13319 workspace
13320 .go_back(workspace.active_pane().downgrade(), window, cx)
13321 .detach_and_log_err(cx);
13322
13323 second_item_id
13324 })
13325 .unwrap();
13326 cx.executor().run_until_parked();
13327 workspace
13328 .update(cx, |workspace, _, cx| {
13329 let active_item = workspace
13330 .active_item(cx)
13331 .expect("should have an active item after navigating back from the 2nd buffer");
13332 assert_eq!(
13333 active_item.item_id(),
13334 multibuffer_item_id,
13335 "Should navigate back from the 2nd buffer to the multi buffer"
13336 );
13337 assert!(!active_item.is_singleton(cx));
13338 })
13339 .unwrap();
13340
13341 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13342 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13343 s.select_ranges(Some(70..70))
13344 });
13345 editor.open_excerpts(&OpenExcerpts, window, cx);
13346 });
13347 cx.executor().run_until_parked();
13348 workspace
13349 .update(cx, |workspace, window, cx| {
13350 let active_item = workspace
13351 .active_item(cx)
13352 .expect("should have an active item after navigating into the 3rd buffer");
13353 let third_item_id = active_item.item_id();
13354 assert_ne!(
13355 third_item_id, multibuffer_item_id,
13356 "Should navigate into the 3rd buffer and activate it"
13357 );
13358 assert_ne!(third_item_id, first_item_id);
13359 assert_ne!(third_item_id, second_item_id);
13360 assert!(
13361 active_item.is_singleton(cx),
13362 "New active item should be a singleton buffer"
13363 );
13364 assert_eq!(
13365 active_item
13366 .act_as::<Editor>(cx)
13367 .expect("should have navigated into an editor")
13368 .read(cx)
13369 .text(cx),
13370 sample_text_3
13371 );
13372
13373 workspace
13374 .go_back(workspace.active_pane().downgrade(), window, cx)
13375 .detach_and_log_err(cx);
13376 })
13377 .unwrap();
13378 cx.executor().run_until_parked();
13379 workspace
13380 .update(cx, |workspace, _, cx| {
13381 let active_item = workspace
13382 .active_item(cx)
13383 .expect("should have an active item after navigating back from the 3rd buffer");
13384 assert_eq!(
13385 active_item.item_id(),
13386 multibuffer_item_id,
13387 "Should navigate back from the 3rd buffer to the multi buffer"
13388 );
13389 assert!(!active_item.is_singleton(cx));
13390 })
13391 .unwrap();
13392}
13393
13394#[gpui::test]
13395async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13396 init_test(cx, |_| {});
13397
13398 let mut cx = EditorTestContext::new(cx).await;
13399
13400 let diff_base = r#"
13401 use some::mod;
13402
13403 const A: u32 = 42;
13404
13405 fn main() {
13406 println!("hello");
13407
13408 println!("world");
13409 }
13410 "#
13411 .unindent();
13412
13413 cx.set_state(
13414 &r#"
13415 use some::modified;
13416
13417 ˇ
13418 fn main() {
13419 println!("hello there");
13420
13421 println!("around the");
13422 println!("world");
13423 }
13424 "#
13425 .unindent(),
13426 );
13427
13428 cx.set_head_text(&diff_base);
13429 executor.run_until_parked();
13430
13431 cx.update_editor(|editor, window, cx| {
13432 editor.go_to_next_hunk(&GoToHunk, window, cx);
13433 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13434 });
13435 executor.run_until_parked();
13436 cx.assert_state_with_diff(
13437 r#"
13438 use some::modified;
13439
13440
13441 fn main() {
13442 - println!("hello");
13443 + ˇ println!("hello there");
13444
13445 println!("around the");
13446 println!("world");
13447 }
13448 "#
13449 .unindent(),
13450 );
13451
13452 cx.update_editor(|editor, window, cx| {
13453 for _ in 0..2 {
13454 editor.go_to_next_hunk(&GoToHunk, window, cx);
13455 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13456 }
13457 });
13458 executor.run_until_parked();
13459 cx.assert_state_with_diff(
13460 r#"
13461 - use some::mod;
13462 + ˇuse some::modified;
13463
13464
13465 fn main() {
13466 - println!("hello");
13467 + println!("hello there");
13468
13469 + println!("around the");
13470 println!("world");
13471 }
13472 "#
13473 .unindent(),
13474 );
13475
13476 cx.update_editor(|editor, window, cx| {
13477 editor.go_to_next_hunk(&GoToHunk, window, cx);
13478 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13479 });
13480 executor.run_until_parked();
13481 cx.assert_state_with_diff(
13482 r#"
13483 - use some::mod;
13484 + use some::modified;
13485
13486 - const A: u32 = 42;
13487 ˇ
13488 fn main() {
13489 - println!("hello");
13490 + println!("hello there");
13491
13492 + println!("around the");
13493 println!("world");
13494 }
13495 "#
13496 .unindent(),
13497 );
13498
13499 cx.update_editor(|editor, window, cx| {
13500 editor.cancel(&Cancel, window, cx);
13501 });
13502
13503 cx.assert_state_with_diff(
13504 r#"
13505 use some::modified;
13506
13507 ˇ
13508 fn main() {
13509 println!("hello there");
13510
13511 println!("around the");
13512 println!("world");
13513 }
13514 "#
13515 .unindent(),
13516 );
13517}
13518
13519#[gpui::test]
13520async fn test_diff_base_change_with_expanded_diff_hunks(
13521 executor: BackgroundExecutor,
13522 cx: &mut TestAppContext,
13523) {
13524 init_test(cx, |_| {});
13525
13526 let mut cx = EditorTestContext::new(cx).await;
13527
13528 let diff_base = r#"
13529 use some::mod1;
13530 use some::mod2;
13531
13532 const A: u32 = 42;
13533 const B: u32 = 42;
13534 const C: u32 = 42;
13535
13536 fn main() {
13537 println!("hello");
13538
13539 println!("world");
13540 }
13541 "#
13542 .unindent();
13543
13544 cx.set_state(
13545 &r#"
13546 use some::mod2;
13547
13548 const A: u32 = 42;
13549 const C: u32 = 42;
13550
13551 fn main(ˇ) {
13552 //println!("hello");
13553
13554 println!("world");
13555 //
13556 //
13557 }
13558 "#
13559 .unindent(),
13560 );
13561
13562 cx.set_head_text(&diff_base);
13563 executor.run_until_parked();
13564
13565 cx.update_editor(|editor, window, cx| {
13566 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13567 });
13568 executor.run_until_parked();
13569 cx.assert_state_with_diff(
13570 r#"
13571 - use some::mod1;
13572 use some::mod2;
13573
13574 const A: u32 = 42;
13575 - const B: u32 = 42;
13576 const C: u32 = 42;
13577
13578 fn main(ˇ) {
13579 - println!("hello");
13580 + //println!("hello");
13581
13582 println!("world");
13583 + //
13584 + //
13585 }
13586 "#
13587 .unindent(),
13588 );
13589
13590 cx.set_head_text("new diff base!");
13591 executor.run_until_parked();
13592 cx.assert_state_with_diff(
13593 r#"
13594 - new diff base!
13595 + use some::mod2;
13596 +
13597 + const A: u32 = 42;
13598 + const C: u32 = 42;
13599 +
13600 + fn main(ˇ) {
13601 + //println!("hello");
13602 +
13603 + println!("world");
13604 + //
13605 + //
13606 + }
13607 "#
13608 .unindent(),
13609 );
13610}
13611
13612#[gpui::test]
13613async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13614 init_test(cx, |_| {});
13615
13616 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13617 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13618 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13619 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13620 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13621 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13622
13623 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13624 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13625 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13626
13627 let multi_buffer = cx.new(|cx| {
13628 let mut multibuffer = MultiBuffer::new(ReadWrite);
13629 multibuffer.push_excerpts(
13630 buffer_1.clone(),
13631 [
13632 ExcerptRange {
13633 context: Point::new(0, 0)..Point::new(3, 0),
13634 primary: None,
13635 },
13636 ExcerptRange {
13637 context: Point::new(5, 0)..Point::new(7, 0),
13638 primary: None,
13639 },
13640 ExcerptRange {
13641 context: Point::new(9, 0)..Point::new(10, 3),
13642 primary: None,
13643 },
13644 ],
13645 cx,
13646 );
13647 multibuffer.push_excerpts(
13648 buffer_2.clone(),
13649 [
13650 ExcerptRange {
13651 context: Point::new(0, 0)..Point::new(3, 0),
13652 primary: None,
13653 },
13654 ExcerptRange {
13655 context: Point::new(5, 0)..Point::new(7, 0),
13656 primary: None,
13657 },
13658 ExcerptRange {
13659 context: Point::new(9, 0)..Point::new(10, 3),
13660 primary: None,
13661 },
13662 ],
13663 cx,
13664 );
13665 multibuffer.push_excerpts(
13666 buffer_3.clone(),
13667 [
13668 ExcerptRange {
13669 context: Point::new(0, 0)..Point::new(3, 0),
13670 primary: None,
13671 },
13672 ExcerptRange {
13673 context: Point::new(5, 0)..Point::new(7, 0),
13674 primary: None,
13675 },
13676 ExcerptRange {
13677 context: Point::new(9, 0)..Point::new(10, 3),
13678 primary: None,
13679 },
13680 ],
13681 cx,
13682 );
13683 multibuffer
13684 });
13685
13686 let editor = cx.add_window(|window, cx| {
13687 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13688 });
13689 editor
13690 .update(cx, |editor, _window, cx| {
13691 for (buffer, diff_base) in [
13692 (buffer_1.clone(), file_1_old),
13693 (buffer_2.clone(), file_2_old),
13694 (buffer_3.clone(), file_3_old),
13695 ] {
13696 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13697 editor
13698 .buffer
13699 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13700 }
13701 })
13702 .unwrap();
13703
13704 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13705 cx.run_until_parked();
13706
13707 cx.assert_editor_state(
13708 &"
13709 ˇaaa
13710 ccc
13711 ddd
13712
13713 ggg
13714 hhh
13715
13716
13717 lll
13718 mmm
13719 NNN
13720
13721 qqq
13722 rrr
13723
13724 uuu
13725 111
13726 222
13727 333
13728
13729 666
13730 777
13731
13732 000
13733 !!!"
13734 .unindent(),
13735 );
13736
13737 cx.update_editor(|editor, window, cx| {
13738 editor.select_all(&SelectAll, window, cx);
13739 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13740 });
13741 cx.executor().run_until_parked();
13742
13743 cx.assert_state_with_diff(
13744 "
13745 «aaa
13746 - bbb
13747 ccc
13748 ddd
13749
13750 ggg
13751 hhh
13752
13753
13754 lll
13755 mmm
13756 - nnn
13757 + NNN
13758
13759 qqq
13760 rrr
13761
13762 uuu
13763 111
13764 222
13765 333
13766
13767 + 666
13768 777
13769
13770 000
13771 !!!ˇ»"
13772 .unindent(),
13773 );
13774}
13775
13776#[gpui::test]
13777async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13778 init_test(cx, |_| {});
13779
13780 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13781 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13782
13783 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13784 let multi_buffer = cx.new(|cx| {
13785 let mut multibuffer = MultiBuffer::new(ReadWrite);
13786 multibuffer.push_excerpts(
13787 buffer.clone(),
13788 [
13789 ExcerptRange {
13790 context: Point::new(0, 0)..Point::new(2, 0),
13791 primary: None,
13792 },
13793 ExcerptRange {
13794 context: Point::new(4, 0)..Point::new(7, 0),
13795 primary: None,
13796 },
13797 ExcerptRange {
13798 context: Point::new(9, 0)..Point::new(10, 0),
13799 primary: None,
13800 },
13801 ],
13802 cx,
13803 );
13804 multibuffer
13805 });
13806
13807 let editor = cx.add_window(|window, cx| {
13808 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13809 });
13810 editor
13811 .update(cx, |editor, _window, cx| {
13812 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13813 editor
13814 .buffer
13815 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13816 })
13817 .unwrap();
13818
13819 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13820 cx.run_until_parked();
13821
13822 cx.update_editor(|editor, window, cx| {
13823 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13824 });
13825 cx.executor().run_until_parked();
13826
13827 // When the start of a hunk coincides with the start of its excerpt,
13828 // the hunk is expanded. When the start of a a hunk is earlier than
13829 // the start of its excerpt, the hunk is not expanded.
13830 cx.assert_state_with_diff(
13831 "
13832 ˇaaa
13833 - bbb
13834 + BBB
13835
13836 - ddd
13837 - eee
13838 + DDD
13839 + EEE
13840 fff
13841
13842 iii
13843 "
13844 .unindent(),
13845 );
13846}
13847
13848#[gpui::test]
13849async fn test_edits_around_expanded_insertion_hunks(
13850 executor: BackgroundExecutor,
13851 cx: &mut TestAppContext,
13852) {
13853 init_test(cx, |_| {});
13854
13855 let mut cx = EditorTestContext::new(cx).await;
13856
13857 let diff_base = r#"
13858 use some::mod1;
13859 use some::mod2;
13860
13861 const A: u32 = 42;
13862
13863 fn main() {
13864 println!("hello");
13865
13866 println!("world");
13867 }
13868 "#
13869 .unindent();
13870 executor.run_until_parked();
13871 cx.set_state(
13872 &r#"
13873 use some::mod1;
13874 use some::mod2;
13875
13876 const A: u32 = 42;
13877 const B: u32 = 42;
13878 const C: u32 = 42;
13879 ˇ
13880
13881 fn main() {
13882 println!("hello");
13883
13884 println!("world");
13885 }
13886 "#
13887 .unindent(),
13888 );
13889
13890 cx.set_head_text(&diff_base);
13891 executor.run_until_parked();
13892
13893 cx.update_editor(|editor, window, cx| {
13894 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13895 });
13896 executor.run_until_parked();
13897
13898 cx.assert_state_with_diff(
13899 r#"
13900 use some::mod1;
13901 use some::mod2;
13902
13903 const A: u32 = 42;
13904 + const B: u32 = 42;
13905 + const C: u32 = 42;
13906 + ˇ
13907
13908 fn main() {
13909 println!("hello");
13910
13911 println!("world");
13912 }
13913 "#
13914 .unindent(),
13915 );
13916
13917 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13918 executor.run_until_parked();
13919
13920 cx.assert_state_with_diff(
13921 r#"
13922 use some::mod1;
13923 use some::mod2;
13924
13925 const A: u32 = 42;
13926 + const B: u32 = 42;
13927 + const C: u32 = 42;
13928 + const D: u32 = 42;
13929 + ˇ
13930
13931 fn main() {
13932 println!("hello");
13933
13934 println!("world");
13935 }
13936 "#
13937 .unindent(),
13938 );
13939
13940 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13941 executor.run_until_parked();
13942
13943 cx.assert_state_with_diff(
13944 r#"
13945 use some::mod1;
13946 use some::mod2;
13947
13948 const A: u32 = 42;
13949 + const B: u32 = 42;
13950 + const C: u32 = 42;
13951 + const D: u32 = 42;
13952 + const E: u32 = 42;
13953 + ˇ
13954
13955 fn main() {
13956 println!("hello");
13957
13958 println!("world");
13959 }
13960 "#
13961 .unindent(),
13962 );
13963
13964 cx.update_editor(|editor, window, cx| {
13965 editor.delete_line(&DeleteLine, window, cx);
13966 });
13967 executor.run_until_parked();
13968
13969 cx.assert_state_with_diff(
13970 r#"
13971 use some::mod1;
13972 use some::mod2;
13973
13974 const A: u32 = 42;
13975 + const B: u32 = 42;
13976 + const C: u32 = 42;
13977 + const D: u32 = 42;
13978 + const E: u32 = 42;
13979 ˇ
13980 fn main() {
13981 println!("hello");
13982
13983 println!("world");
13984 }
13985 "#
13986 .unindent(),
13987 );
13988
13989 cx.update_editor(|editor, window, cx| {
13990 editor.move_up(&MoveUp, window, cx);
13991 editor.delete_line(&DeleteLine, window, cx);
13992 editor.move_up(&MoveUp, window, cx);
13993 editor.delete_line(&DeleteLine, window, cx);
13994 editor.move_up(&MoveUp, window, cx);
13995 editor.delete_line(&DeleteLine, window, cx);
13996 });
13997 executor.run_until_parked();
13998 cx.assert_state_with_diff(
13999 r#"
14000 use some::mod1;
14001 use some::mod2;
14002
14003 const A: u32 = 42;
14004 + const B: u32 = 42;
14005 ˇ
14006 fn main() {
14007 println!("hello");
14008
14009 println!("world");
14010 }
14011 "#
14012 .unindent(),
14013 );
14014
14015 cx.update_editor(|editor, window, cx| {
14016 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14017 editor.delete_line(&DeleteLine, window, cx);
14018 });
14019 executor.run_until_parked();
14020 cx.assert_state_with_diff(
14021 r#"
14022 ˇ
14023 fn main() {
14024 println!("hello");
14025
14026 println!("world");
14027 }
14028 "#
14029 .unindent(),
14030 );
14031}
14032
14033#[gpui::test]
14034async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14035 init_test(cx, |_| {});
14036
14037 let mut cx = EditorTestContext::new(cx).await;
14038 cx.set_head_text(indoc! { "
14039 one
14040 two
14041 three
14042 four
14043 five
14044 "
14045 });
14046 cx.set_state(indoc! { "
14047 one
14048 ˇthree
14049 five
14050 "});
14051 cx.run_until_parked();
14052 cx.update_editor(|editor, window, cx| {
14053 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14054 });
14055 cx.assert_state_with_diff(
14056 indoc! { "
14057 one
14058 - two
14059 ˇthree
14060 - four
14061 five
14062 "}
14063 .to_string(),
14064 );
14065 cx.update_editor(|editor, window, cx| {
14066 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14067 });
14068
14069 cx.assert_state_with_diff(
14070 indoc! { "
14071 one
14072 ˇthree
14073 five
14074 "}
14075 .to_string(),
14076 );
14077
14078 cx.set_state(indoc! { "
14079 one
14080 ˇTWO
14081 three
14082 four
14083 five
14084 "});
14085 cx.run_until_parked();
14086 cx.update_editor(|editor, window, cx| {
14087 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14088 });
14089
14090 cx.assert_state_with_diff(
14091 indoc! { "
14092 one
14093 - two
14094 + ˇTWO
14095 three
14096 four
14097 five
14098 "}
14099 .to_string(),
14100 );
14101 cx.update_editor(|editor, window, cx| {
14102 editor.move_up(&Default::default(), window, cx);
14103 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14104 });
14105 cx.assert_state_with_diff(
14106 indoc! { "
14107 one
14108 ˇTWO
14109 three
14110 four
14111 five
14112 "}
14113 .to_string(),
14114 );
14115}
14116
14117#[gpui::test]
14118async fn test_edits_around_expanded_deletion_hunks(
14119 executor: BackgroundExecutor,
14120 cx: &mut TestAppContext,
14121) {
14122 init_test(cx, |_| {});
14123
14124 let mut cx = EditorTestContext::new(cx).await;
14125
14126 let diff_base = r#"
14127 use some::mod1;
14128 use some::mod2;
14129
14130 const A: u32 = 42;
14131 const B: u32 = 42;
14132 const C: u32 = 42;
14133
14134
14135 fn main() {
14136 println!("hello");
14137
14138 println!("world");
14139 }
14140 "#
14141 .unindent();
14142 executor.run_until_parked();
14143 cx.set_state(
14144 &r#"
14145 use some::mod1;
14146 use some::mod2;
14147
14148 ˇconst B: u32 = 42;
14149 const C: u32 = 42;
14150
14151
14152 fn main() {
14153 println!("hello");
14154
14155 println!("world");
14156 }
14157 "#
14158 .unindent(),
14159 );
14160
14161 cx.set_head_text(&diff_base);
14162 executor.run_until_parked();
14163
14164 cx.update_editor(|editor, window, cx| {
14165 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14166 });
14167 executor.run_until_parked();
14168
14169 cx.assert_state_with_diff(
14170 r#"
14171 use some::mod1;
14172 use some::mod2;
14173
14174 - const A: u32 = 42;
14175 ˇconst B: u32 = 42;
14176 const C: u32 = 42;
14177
14178
14179 fn main() {
14180 println!("hello");
14181
14182 println!("world");
14183 }
14184 "#
14185 .unindent(),
14186 );
14187
14188 cx.update_editor(|editor, window, cx| {
14189 editor.delete_line(&DeleteLine, window, cx);
14190 });
14191 executor.run_until_parked();
14192 cx.assert_state_with_diff(
14193 r#"
14194 use some::mod1;
14195 use some::mod2;
14196
14197 - const A: u32 = 42;
14198 - const B: u32 = 42;
14199 ˇconst C: u32 = 42;
14200
14201
14202 fn main() {
14203 println!("hello");
14204
14205 println!("world");
14206 }
14207 "#
14208 .unindent(),
14209 );
14210
14211 cx.update_editor(|editor, window, cx| {
14212 editor.delete_line(&DeleteLine, window, cx);
14213 });
14214 executor.run_until_parked();
14215 cx.assert_state_with_diff(
14216 r#"
14217 use some::mod1;
14218 use some::mod2;
14219
14220 - const A: u32 = 42;
14221 - const B: u32 = 42;
14222 - const C: u32 = 42;
14223 ˇ
14224
14225 fn main() {
14226 println!("hello");
14227
14228 println!("world");
14229 }
14230 "#
14231 .unindent(),
14232 );
14233
14234 cx.update_editor(|editor, window, cx| {
14235 editor.handle_input("replacement", window, cx);
14236 });
14237 executor.run_until_parked();
14238 cx.assert_state_with_diff(
14239 r#"
14240 use some::mod1;
14241 use some::mod2;
14242
14243 - const A: u32 = 42;
14244 - const B: u32 = 42;
14245 - const C: u32 = 42;
14246 -
14247 + replacementˇ
14248
14249 fn main() {
14250 println!("hello");
14251
14252 println!("world");
14253 }
14254 "#
14255 .unindent(),
14256 );
14257}
14258
14259#[gpui::test]
14260async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14261 init_test(cx, |_| {});
14262
14263 let mut cx = EditorTestContext::new(cx).await;
14264
14265 let base_text = r#"
14266 one
14267 two
14268 three
14269 four
14270 five
14271 "#
14272 .unindent();
14273 executor.run_until_parked();
14274 cx.set_state(
14275 &r#"
14276 one
14277 two
14278 fˇour
14279 five
14280 "#
14281 .unindent(),
14282 );
14283
14284 cx.set_head_text(&base_text);
14285 executor.run_until_parked();
14286
14287 cx.update_editor(|editor, window, cx| {
14288 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14289 });
14290 executor.run_until_parked();
14291
14292 cx.assert_state_with_diff(
14293 r#"
14294 one
14295 two
14296 - three
14297 fˇour
14298 five
14299 "#
14300 .unindent(),
14301 );
14302
14303 cx.update_editor(|editor, window, cx| {
14304 editor.backspace(&Backspace, window, cx);
14305 editor.backspace(&Backspace, window, cx);
14306 });
14307 executor.run_until_parked();
14308 cx.assert_state_with_diff(
14309 r#"
14310 one
14311 two
14312 - threeˇ
14313 - four
14314 + our
14315 five
14316 "#
14317 .unindent(),
14318 );
14319}
14320
14321#[gpui::test]
14322async fn test_edit_after_expanded_modification_hunk(
14323 executor: BackgroundExecutor,
14324 cx: &mut TestAppContext,
14325) {
14326 init_test(cx, |_| {});
14327
14328 let mut cx = EditorTestContext::new(cx).await;
14329
14330 let diff_base = r#"
14331 use some::mod1;
14332 use some::mod2;
14333
14334 const A: u32 = 42;
14335 const B: u32 = 42;
14336 const C: u32 = 42;
14337 const D: u32 = 42;
14338
14339
14340 fn main() {
14341 println!("hello");
14342
14343 println!("world");
14344 }"#
14345 .unindent();
14346
14347 cx.set_state(
14348 &r#"
14349 use some::mod1;
14350 use some::mod2;
14351
14352 const A: u32 = 42;
14353 const B: u32 = 42;
14354 const C: u32 = 43ˇ
14355 const D: u32 = 42;
14356
14357
14358 fn main() {
14359 println!("hello");
14360
14361 println!("world");
14362 }"#
14363 .unindent(),
14364 );
14365
14366 cx.set_head_text(&diff_base);
14367 executor.run_until_parked();
14368 cx.update_editor(|editor, window, cx| {
14369 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14370 });
14371 executor.run_until_parked();
14372
14373 cx.assert_state_with_diff(
14374 r#"
14375 use some::mod1;
14376 use some::mod2;
14377
14378 const A: u32 = 42;
14379 const B: u32 = 42;
14380 - const C: u32 = 42;
14381 + const C: u32 = 43ˇ
14382 const D: u32 = 42;
14383
14384
14385 fn main() {
14386 println!("hello");
14387
14388 println!("world");
14389 }"#
14390 .unindent(),
14391 );
14392
14393 cx.update_editor(|editor, window, cx| {
14394 editor.handle_input("\nnew_line\n", window, cx);
14395 });
14396 executor.run_until_parked();
14397
14398 cx.assert_state_with_diff(
14399 r#"
14400 use some::mod1;
14401 use some::mod2;
14402
14403 const A: u32 = 42;
14404 const B: u32 = 42;
14405 - const C: u32 = 42;
14406 + const C: u32 = 43
14407 + new_line
14408 + ˇ
14409 const D: u32 = 42;
14410
14411
14412 fn main() {
14413 println!("hello");
14414
14415 println!("world");
14416 }"#
14417 .unindent(),
14418 );
14419}
14420
14421#[gpui::test]
14422async fn test_stage_and_unstage_added_file_hunk(
14423 executor: BackgroundExecutor,
14424 cx: &mut TestAppContext,
14425) {
14426 init_test(cx, |_| {});
14427
14428 let mut cx = EditorTestContext::new(cx).await;
14429 cx.update_editor(|editor, _, cx| {
14430 editor.set_expand_all_diff_hunks(cx);
14431 });
14432
14433 let working_copy = r#"
14434 ˇfn main() {
14435 println!("hello, world!");
14436 }
14437 "#
14438 .unindent();
14439
14440 cx.set_state(&working_copy);
14441 executor.run_until_parked();
14442
14443 cx.assert_state_with_diff(
14444 r#"
14445 + ˇfn main() {
14446 + println!("hello, world!");
14447 + }
14448 "#
14449 .unindent(),
14450 );
14451 cx.assert_index_text(None);
14452
14453 cx.update_editor(|editor, window, cx| {
14454 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14455 });
14456 executor.run_until_parked();
14457 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14458 cx.assert_state_with_diff(
14459 r#"
14460 + ˇfn main() {
14461 + println!("hello, world!");
14462 + }
14463 "#
14464 .unindent(),
14465 );
14466
14467 cx.update_editor(|editor, window, cx| {
14468 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14469 });
14470 executor.run_until_parked();
14471 cx.assert_index_text(None);
14472}
14473
14474async fn setup_indent_guides_editor(
14475 text: &str,
14476 cx: &mut TestAppContext,
14477) -> (BufferId, EditorTestContext) {
14478 init_test(cx, |_| {});
14479
14480 let mut cx = EditorTestContext::new(cx).await;
14481
14482 let buffer_id = cx.update_editor(|editor, window, cx| {
14483 editor.set_text(text, window, cx);
14484 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14485
14486 buffer_ids[0]
14487 });
14488
14489 (buffer_id, cx)
14490}
14491
14492fn assert_indent_guides(
14493 range: Range<u32>,
14494 expected: Vec<IndentGuide>,
14495 active_indices: Option<Vec<usize>>,
14496 cx: &mut EditorTestContext,
14497) {
14498 let indent_guides = cx.update_editor(|editor, window, cx| {
14499 let snapshot = editor.snapshot(window, cx).display_snapshot;
14500 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14501 editor,
14502 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14503 true,
14504 &snapshot,
14505 cx,
14506 );
14507
14508 indent_guides.sort_by(|a, b| {
14509 a.depth.cmp(&b.depth).then(
14510 a.start_row
14511 .cmp(&b.start_row)
14512 .then(a.end_row.cmp(&b.end_row)),
14513 )
14514 });
14515 indent_guides
14516 });
14517
14518 if let Some(expected) = active_indices {
14519 let active_indices = cx.update_editor(|editor, window, cx| {
14520 let snapshot = editor.snapshot(window, cx).display_snapshot;
14521 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14522 });
14523
14524 assert_eq!(
14525 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14526 expected,
14527 "Active indent guide indices do not match"
14528 );
14529 }
14530
14531 assert_eq!(indent_guides, expected, "Indent guides do not match");
14532}
14533
14534fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14535 IndentGuide {
14536 buffer_id,
14537 start_row: MultiBufferRow(start_row),
14538 end_row: MultiBufferRow(end_row),
14539 depth,
14540 tab_size: 4,
14541 settings: IndentGuideSettings {
14542 enabled: true,
14543 line_width: 1,
14544 active_line_width: 1,
14545 ..Default::default()
14546 },
14547 }
14548}
14549
14550#[gpui::test]
14551async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14552 let (buffer_id, mut cx) = setup_indent_guides_editor(
14553 &"
14554 fn main() {
14555 let a = 1;
14556 }"
14557 .unindent(),
14558 cx,
14559 )
14560 .await;
14561
14562 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14563}
14564
14565#[gpui::test]
14566async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14567 let (buffer_id, mut cx) = setup_indent_guides_editor(
14568 &"
14569 fn main() {
14570 let a = 1;
14571 let b = 2;
14572 }"
14573 .unindent(),
14574 cx,
14575 )
14576 .await;
14577
14578 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14579}
14580
14581#[gpui::test]
14582async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14583 let (buffer_id, mut cx) = setup_indent_guides_editor(
14584 &"
14585 fn main() {
14586 let a = 1;
14587 if a == 3 {
14588 let b = 2;
14589 } else {
14590 let c = 3;
14591 }
14592 }"
14593 .unindent(),
14594 cx,
14595 )
14596 .await;
14597
14598 assert_indent_guides(
14599 0..8,
14600 vec![
14601 indent_guide(buffer_id, 1, 6, 0),
14602 indent_guide(buffer_id, 3, 3, 1),
14603 indent_guide(buffer_id, 5, 5, 1),
14604 ],
14605 None,
14606 &mut cx,
14607 );
14608}
14609
14610#[gpui::test]
14611async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14612 let (buffer_id, mut cx) = setup_indent_guides_editor(
14613 &"
14614 fn main() {
14615 let a = 1;
14616 let b = 2;
14617 let c = 3;
14618 }"
14619 .unindent(),
14620 cx,
14621 )
14622 .await;
14623
14624 assert_indent_guides(
14625 0..5,
14626 vec![
14627 indent_guide(buffer_id, 1, 3, 0),
14628 indent_guide(buffer_id, 2, 2, 1),
14629 ],
14630 None,
14631 &mut cx,
14632 );
14633}
14634
14635#[gpui::test]
14636async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14637 let (buffer_id, mut cx) = setup_indent_guides_editor(
14638 &"
14639 fn main() {
14640 let a = 1;
14641
14642 let c = 3;
14643 }"
14644 .unindent(),
14645 cx,
14646 )
14647 .await;
14648
14649 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14650}
14651
14652#[gpui::test]
14653async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14654 let (buffer_id, mut cx) = setup_indent_guides_editor(
14655 &"
14656 fn main() {
14657 let a = 1;
14658
14659 let c = 3;
14660
14661 if a == 3 {
14662 let b = 2;
14663 } else {
14664 let c = 3;
14665 }
14666 }"
14667 .unindent(),
14668 cx,
14669 )
14670 .await;
14671
14672 assert_indent_guides(
14673 0..11,
14674 vec![
14675 indent_guide(buffer_id, 1, 9, 0),
14676 indent_guide(buffer_id, 6, 6, 1),
14677 indent_guide(buffer_id, 8, 8, 1),
14678 ],
14679 None,
14680 &mut cx,
14681 );
14682}
14683
14684#[gpui::test]
14685async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14686 let (buffer_id, mut cx) = setup_indent_guides_editor(
14687 &"
14688 fn main() {
14689 let a = 1;
14690
14691 let c = 3;
14692
14693 if a == 3 {
14694 let b = 2;
14695 } else {
14696 let c = 3;
14697 }
14698 }"
14699 .unindent(),
14700 cx,
14701 )
14702 .await;
14703
14704 assert_indent_guides(
14705 1..11,
14706 vec![
14707 indent_guide(buffer_id, 1, 9, 0),
14708 indent_guide(buffer_id, 6, 6, 1),
14709 indent_guide(buffer_id, 8, 8, 1),
14710 ],
14711 None,
14712 &mut cx,
14713 );
14714}
14715
14716#[gpui::test]
14717async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14718 let (buffer_id, mut cx) = setup_indent_guides_editor(
14719 &"
14720 fn main() {
14721 let a = 1;
14722
14723 let c = 3;
14724
14725 if a == 3 {
14726 let b = 2;
14727 } else {
14728 let c = 3;
14729 }
14730 }"
14731 .unindent(),
14732 cx,
14733 )
14734 .await;
14735
14736 assert_indent_guides(
14737 1..10,
14738 vec![
14739 indent_guide(buffer_id, 1, 9, 0),
14740 indent_guide(buffer_id, 6, 6, 1),
14741 indent_guide(buffer_id, 8, 8, 1),
14742 ],
14743 None,
14744 &mut cx,
14745 );
14746}
14747
14748#[gpui::test]
14749async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14750 let (buffer_id, mut cx) = setup_indent_guides_editor(
14751 &"
14752 block1
14753 block2
14754 block3
14755 block4
14756 block2
14757 block1
14758 block1"
14759 .unindent(),
14760 cx,
14761 )
14762 .await;
14763
14764 assert_indent_guides(
14765 1..10,
14766 vec![
14767 indent_guide(buffer_id, 1, 4, 0),
14768 indent_guide(buffer_id, 2, 3, 1),
14769 indent_guide(buffer_id, 3, 3, 2),
14770 ],
14771 None,
14772 &mut cx,
14773 );
14774}
14775
14776#[gpui::test]
14777async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14778 let (buffer_id, mut cx) = setup_indent_guides_editor(
14779 &"
14780 block1
14781 block2
14782 block3
14783
14784 block1
14785 block1"
14786 .unindent(),
14787 cx,
14788 )
14789 .await;
14790
14791 assert_indent_guides(
14792 0..6,
14793 vec![
14794 indent_guide(buffer_id, 1, 2, 0),
14795 indent_guide(buffer_id, 2, 2, 1),
14796 ],
14797 None,
14798 &mut cx,
14799 );
14800}
14801
14802#[gpui::test]
14803async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14804 let (buffer_id, mut cx) = setup_indent_guides_editor(
14805 &"
14806 block1
14807
14808
14809
14810 block2
14811 "
14812 .unindent(),
14813 cx,
14814 )
14815 .await;
14816
14817 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14818}
14819
14820#[gpui::test]
14821async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14822 let (buffer_id, mut cx) = setup_indent_guides_editor(
14823 &"
14824 def a:
14825 \tb = 3
14826 \tif True:
14827 \t\tc = 4
14828 \t\td = 5
14829 \tprint(b)
14830 "
14831 .unindent(),
14832 cx,
14833 )
14834 .await;
14835
14836 assert_indent_guides(
14837 0..6,
14838 vec![
14839 indent_guide(buffer_id, 1, 6, 0),
14840 indent_guide(buffer_id, 3, 4, 1),
14841 ],
14842 None,
14843 &mut cx,
14844 );
14845}
14846
14847#[gpui::test]
14848async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14849 let (buffer_id, mut cx) = setup_indent_guides_editor(
14850 &"
14851 fn main() {
14852 let a = 1;
14853 }"
14854 .unindent(),
14855 cx,
14856 )
14857 .await;
14858
14859 cx.update_editor(|editor, window, cx| {
14860 editor.change_selections(None, window, cx, |s| {
14861 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14862 });
14863 });
14864
14865 assert_indent_guides(
14866 0..3,
14867 vec![indent_guide(buffer_id, 1, 1, 0)],
14868 Some(vec![0]),
14869 &mut cx,
14870 );
14871}
14872
14873#[gpui::test]
14874async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14875 let (buffer_id, mut cx) = setup_indent_guides_editor(
14876 &"
14877 fn main() {
14878 if 1 == 2 {
14879 let a = 1;
14880 }
14881 }"
14882 .unindent(),
14883 cx,
14884 )
14885 .await;
14886
14887 cx.update_editor(|editor, window, cx| {
14888 editor.change_selections(None, window, cx, |s| {
14889 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14890 });
14891 });
14892
14893 assert_indent_guides(
14894 0..4,
14895 vec![
14896 indent_guide(buffer_id, 1, 3, 0),
14897 indent_guide(buffer_id, 2, 2, 1),
14898 ],
14899 Some(vec![1]),
14900 &mut cx,
14901 );
14902
14903 cx.update_editor(|editor, window, cx| {
14904 editor.change_selections(None, window, cx, |s| {
14905 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14906 });
14907 });
14908
14909 assert_indent_guides(
14910 0..4,
14911 vec![
14912 indent_guide(buffer_id, 1, 3, 0),
14913 indent_guide(buffer_id, 2, 2, 1),
14914 ],
14915 Some(vec![1]),
14916 &mut cx,
14917 );
14918
14919 cx.update_editor(|editor, window, cx| {
14920 editor.change_selections(None, window, cx, |s| {
14921 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14922 });
14923 });
14924
14925 assert_indent_guides(
14926 0..4,
14927 vec![
14928 indent_guide(buffer_id, 1, 3, 0),
14929 indent_guide(buffer_id, 2, 2, 1),
14930 ],
14931 Some(vec![0]),
14932 &mut cx,
14933 );
14934}
14935
14936#[gpui::test]
14937async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
14938 let (buffer_id, mut cx) = setup_indent_guides_editor(
14939 &"
14940 fn main() {
14941 let a = 1;
14942
14943 let b = 2;
14944 }"
14945 .unindent(),
14946 cx,
14947 )
14948 .await;
14949
14950 cx.update_editor(|editor, window, cx| {
14951 editor.change_selections(None, window, cx, |s| {
14952 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14953 });
14954 });
14955
14956 assert_indent_guides(
14957 0..5,
14958 vec![indent_guide(buffer_id, 1, 3, 0)],
14959 Some(vec![0]),
14960 &mut cx,
14961 );
14962}
14963
14964#[gpui::test]
14965async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
14966 let (buffer_id, mut cx) = setup_indent_guides_editor(
14967 &"
14968 def m:
14969 a = 1
14970 pass"
14971 .unindent(),
14972 cx,
14973 )
14974 .await;
14975
14976 cx.update_editor(|editor, window, cx| {
14977 editor.change_selections(None, window, cx, |s| {
14978 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14979 });
14980 });
14981
14982 assert_indent_guides(
14983 0..3,
14984 vec![indent_guide(buffer_id, 1, 2, 0)],
14985 Some(vec![0]),
14986 &mut cx,
14987 );
14988}
14989
14990#[gpui::test]
14991async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
14992 init_test(cx, |_| {});
14993 let mut cx = EditorTestContext::new(cx).await;
14994 let text = indoc! {
14995 "
14996 impl A {
14997 fn b() {
14998 0;
14999 3;
15000 5;
15001 6;
15002 7;
15003 }
15004 }
15005 "
15006 };
15007 let base_text = indoc! {
15008 "
15009 impl A {
15010 fn b() {
15011 0;
15012 1;
15013 2;
15014 3;
15015 4;
15016 }
15017 fn c() {
15018 5;
15019 6;
15020 7;
15021 }
15022 }
15023 "
15024 };
15025
15026 cx.update_editor(|editor, window, cx| {
15027 editor.set_text(text, window, cx);
15028
15029 editor.buffer().update(cx, |multibuffer, cx| {
15030 let buffer = multibuffer.as_singleton().unwrap();
15031 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15032
15033 multibuffer.set_all_diff_hunks_expanded(cx);
15034 multibuffer.add_diff(diff, cx);
15035
15036 buffer.read(cx).remote_id()
15037 })
15038 });
15039 cx.run_until_parked();
15040
15041 cx.assert_state_with_diff(
15042 indoc! { "
15043 impl A {
15044 fn b() {
15045 0;
15046 - 1;
15047 - 2;
15048 3;
15049 - 4;
15050 - }
15051 - fn c() {
15052 5;
15053 6;
15054 7;
15055 }
15056 }
15057 ˇ"
15058 }
15059 .to_string(),
15060 );
15061
15062 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15063 editor
15064 .snapshot(window, cx)
15065 .buffer_snapshot
15066 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15067 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15068 .collect::<Vec<_>>()
15069 });
15070 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15071 assert_eq!(
15072 actual_guides,
15073 vec![
15074 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15075 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15076 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15077 ]
15078 );
15079}
15080
15081#[gpui::test]
15082async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15083 init_test(cx, |_| {});
15084 let mut cx = EditorTestContext::new(cx).await;
15085
15086 let diff_base = r#"
15087 a
15088 b
15089 c
15090 "#
15091 .unindent();
15092
15093 cx.set_state(
15094 &r#"
15095 ˇA
15096 b
15097 C
15098 "#
15099 .unindent(),
15100 );
15101 cx.set_head_text(&diff_base);
15102 cx.update_editor(|editor, window, cx| {
15103 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15104 });
15105 executor.run_until_parked();
15106
15107 let both_hunks_expanded = r#"
15108 - a
15109 + ˇA
15110 b
15111 - c
15112 + C
15113 "#
15114 .unindent();
15115
15116 cx.assert_state_with_diff(both_hunks_expanded.clone());
15117
15118 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15119 let snapshot = editor.snapshot(window, cx);
15120 let hunks = editor
15121 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15122 .collect::<Vec<_>>();
15123 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15124 let buffer_id = hunks[0].buffer_id;
15125 hunks
15126 .into_iter()
15127 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15128 .collect::<Vec<_>>()
15129 });
15130 assert_eq!(hunk_ranges.len(), 2);
15131
15132 cx.update_editor(|editor, _, cx| {
15133 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15134 });
15135 executor.run_until_parked();
15136
15137 let second_hunk_expanded = r#"
15138 ˇA
15139 b
15140 - c
15141 + C
15142 "#
15143 .unindent();
15144
15145 cx.assert_state_with_diff(second_hunk_expanded);
15146
15147 cx.update_editor(|editor, _, cx| {
15148 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15149 });
15150 executor.run_until_parked();
15151
15152 cx.assert_state_with_diff(both_hunks_expanded.clone());
15153
15154 cx.update_editor(|editor, _, cx| {
15155 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15156 });
15157 executor.run_until_parked();
15158
15159 let first_hunk_expanded = r#"
15160 - a
15161 + ˇA
15162 b
15163 C
15164 "#
15165 .unindent();
15166
15167 cx.assert_state_with_diff(first_hunk_expanded);
15168
15169 cx.update_editor(|editor, _, cx| {
15170 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15171 });
15172 executor.run_until_parked();
15173
15174 cx.assert_state_with_diff(both_hunks_expanded);
15175
15176 cx.set_state(
15177 &r#"
15178 ˇA
15179 b
15180 "#
15181 .unindent(),
15182 );
15183 cx.run_until_parked();
15184
15185 // TODO this cursor position seems bad
15186 cx.assert_state_with_diff(
15187 r#"
15188 - ˇa
15189 + A
15190 b
15191 "#
15192 .unindent(),
15193 );
15194
15195 cx.update_editor(|editor, window, cx| {
15196 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15197 });
15198
15199 cx.assert_state_with_diff(
15200 r#"
15201 - ˇa
15202 + A
15203 b
15204 - c
15205 "#
15206 .unindent(),
15207 );
15208
15209 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15210 let snapshot = editor.snapshot(window, cx);
15211 let hunks = editor
15212 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15213 .collect::<Vec<_>>();
15214 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15215 let buffer_id = hunks[0].buffer_id;
15216 hunks
15217 .into_iter()
15218 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15219 .collect::<Vec<_>>()
15220 });
15221 assert_eq!(hunk_ranges.len(), 2);
15222
15223 cx.update_editor(|editor, _, cx| {
15224 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15225 });
15226 executor.run_until_parked();
15227
15228 cx.assert_state_with_diff(
15229 r#"
15230 - ˇa
15231 + A
15232 b
15233 "#
15234 .unindent(),
15235 );
15236}
15237
15238#[gpui::test]
15239async fn test_toggle_deletion_hunk_at_start_of_file(
15240 executor: BackgroundExecutor,
15241 cx: &mut TestAppContext,
15242) {
15243 init_test(cx, |_| {});
15244 let mut cx = EditorTestContext::new(cx).await;
15245
15246 let diff_base = r#"
15247 a
15248 b
15249 c
15250 "#
15251 .unindent();
15252
15253 cx.set_state(
15254 &r#"
15255 ˇb
15256 c
15257 "#
15258 .unindent(),
15259 );
15260 cx.set_head_text(&diff_base);
15261 cx.update_editor(|editor, window, cx| {
15262 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15263 });
15264 executor.run_until_parked();
15265
15266 let hunk_expanded = r#"
15267 - a
15268 ˇb
15269 c
15270 "#
15271 .unindent();
15272
15273 cx.assert_state_with_diff(hunk_expanded.clone());
15274
15275 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15276 let snapshot = editor.snapshot(window, cx);
15277 let hunks = editor
15278 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15279 .collect::<Vec<_>>();
15280 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15281 let buffer_id = hunks[0].buffer_id;
15282 hunks
15283 .into_iter()
15284 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15285 .collect::<Vec<_>>()
15286 });
15287 assert_eq!(hunk_ranges.len(), 1);
15288
15289 cx.update_editor(|editor, _, cx| {
15290 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15291 });
15292 executor.run_until_parked();
15293
15294 let hunk_collapsed = r#"
15295 ˇb
15296 c
15297 "#
15298 .unindent();
15299
15300 cx.assert_state_with_diff(hunk_collapsed);
15301
15302 cx.update_editor(|editor, _, cx| {
15303 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15304 });
15305 executor.run_until_parked();
15306
15307 cx.assert_state_with_diff(hunk_expanded.clone());
15308}
15309
15310#[gpui::test]
15311async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15312 init_test(cx, |_| {});
15313
15314 let fs = FakeFs::new(cx.executor());
15315 fs.insert_tree(
15316 path!("/test"),
15317 json!({
15318 ".git": {},
15319 "file-1": "ONE\n",
15320 "file-2": "TWO\n",
15321 "file-3": "THREE\n",
15322 }),
15323 )
15324 .await;
15325
15326 fs.set_head_for_repo(
15327 path!("/test/.git").as_ref(),
15328 &[
15329 ("file-1".into(), "one\n".into()),
15330 ("file-2".into(), "two\n".into()),
15331 ("file-3".into(), "three\n".into()),
15332 ],
15333 );
15334
15335 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15336 let mut buffers = vec![];
15337 for i in 1..=3 {
15338 let buffer = project
15339 .update(cx, |project, cx| {
15340 let path = format!(path!("/test/file-{}"), i);
15341 project.open_local_buffer(path, cx)
15342 })
15343 .await
15344 .unwrap();
15345 buffers.push(buffer);
15346 }
15347
15348 let multibuffer = cx.new(|cx| {
15349 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15350 multibuffer.set_all_diff_hunks_expanded(cx);
15351 for buffer in &buffers {
15352 let snapshot = buffer.read(cx).snapshot();
15353 multibuffer.set_excerpts_for_path(
15354 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15355 buffer.clone(),
15356 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15357 DEFAULT_MULTIBUFFER_CONTEXT,
15358 cx,
15359 );
15360 }
15361 multibuffer
15362 });
15363
15364 let editor = cx.add_window(|window, cx| {
15365 Editor::new(
15366 EditorMode::Full,
15367 multibuffer,
15368 Some(project),
15369 true,
15370 window,
15371 cx,
15372 )
15373 });
15374 cx.run_until_parked();
15375
15376 let snapshot = editor
15377 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15378 .unwrap();
15379 let hunks = snapshot
15380 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15381 .map(|hunk| match hunk {
15382 DisplayDiffHunk::Unfolded {
15383 display_row_range, ..
15384 } => display_row_range,
15385 DisplayDiffHunk::Folded { .. } => unreachable!(),
15386 })
15387 .collect::<Vec<_>>();
15388 assert_eq!(
15389 hunks,
15390 [
15391 DisplayRow(3)..DisplayRow(5),
15392 DisplayRow(10)..DisplayRow(12),
15393 DisplayRow(17)..DisplayRow(19),
15394 ]
15395 );
15396}
15397
15398#[gpui::test]
15399async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15400 init_test(cx, |_| {});
15401
15402 let mut cx = EditorTestContext::new(cx).await;
15403 cx.set_head_text(indoc! { "
15404 one
15405 two
15406 three
15407 four
15408 five
15409 "
15410 });
15411 cx.set_index_text(indoc! { "
15412 one
15413 two
15414 three
15415 four
15416 five
15417 "
15418 });
15419 cx.set_state(indoc! {"
15420 one
15421 TWO
15422 ˇTHREE
15423 FOUR
15424 five
15425 "});
15426 cx.run_until_parked();
15427 cx.update_editor(|editor, window, cx| {
15428 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15429 });
15430 cx.run_until_parked();
15431 cx.assert_index_text(Some(indoc! {"
15432 one
15433 TWO
15434 THREE
15435 FOUR
15436 five
15437 "}));
15438 cx.set_state(indoc! { "
15439 one
15440 TWO
15441 ˇTHREE-HUNDRED
15442 FOUR
15443 five
15444 "});
15445 cx.run_until_parked();
15446 cx.update_editor(|editor, window, cx| {
15447 let snapshot = editor.snapshot(window, cx);
15448 let hunks = editor
15449 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15450 .collect::<Vec<_>>();
15451 assert_eq!(hunks.len(), 1);
15452 assert_eq!(
15453 hunks[0].status(),
15454 DiffHunkStatus {
15455 kind: DiffHunkStatusKind::Modified,
15456 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15457 }
15458 );
15459
15460 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15461 });
15462 cx.run_until_parked();
15463 cx.assert_index_text(Some(indoc! {"
15464 one
15465 TWO
15466 THREE-HUNDRED
15467 FOUR
15468 five
15469 "}));
15470}
15471
15472#[gpui::test]
15473fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15474 init_test(cx, |_| {});
15475
15476 let editor = cx.add_window(|window, cx| {
15477 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15478 build_editor(buffer, window, cx)
15479 });
15480
15481 let render_args = Arc::new(Mutex::new(None));
15482 let snapshot = editor
15483 .update(cx, |editor, window, cx| {
15484 let snapshot = editor.buffer().read(cx).snapshot(cx);
15485 let range =
15486 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15487
15488 struct RenderArgs {
15489 row: MultiBufferRow,
15490 folded: bool,
15491 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15492 }
15493
15494 let crease = Crease::inline(
15495 range,
15496 FoldPlaceholder::test(),
15497 {
15498 let toggle_callback = render_args.clone();
15499 move |row, folded, callback, _window, _cx| {
15500 *toggle_callback.lock() = Some(RenderArgs {
15501 row,
15502 folded,
15503 callback,
15504 });
15505 div()
15506 }
15507 },
15508 |_row, _folded, _window, _cx| div(),
15509 );
15510
15511 editor.insert_creases(Some(crease), cx);
15512 let snapshot = editor.snapshot(window, cx);
15513 let _div = snapshot.render_crease_toggle(
15514 MultiBufferRow(1),
15515 false,
15516 cx.entity().clone(),
15517 window,
15518 cx,
15519 );
15520 snapshot
15521 })
15522 .unwrap();
15523
15524 let render_args = render_args.lock().take().unwrap();
15525 assert_eq!(render_args.row, MultiBufferRow(1));
15526 assert!(!render_args.folded);
15527 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15528
15529 cx.update_window(*editor, |_, window, cx| {
15530 (render_args.callback)(true, window, cx)
15531 })
15532 .unwrap();
15533 let snapshot = editor
15534 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15535 .unwrap();
15536 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15537
15538 cx.update_window(*editor, |_, window, cx| {
15539 (render_args.callback)(false, window, cx)
15540 })
15541 .unwrap();
15542 let snapshot = editor
15543 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15544 .unwrap();
15545 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15546}
15547
15548#[gpui::test]
15549async fn test_input_text(cx: &mut TestAppContext) {
15550 init_test(cx, |_| {});
15551 let mut cx = EditorTestContext::new(cx).await;
15552
15553 cx.set_state(
15554 &r#"ˇone
15555 two
15556
15557 three
15558 fourˇ
15559 five
15560
15561 siˇx"#
15562 .unindent(),
15563 );
15564
15565 cx.dispatch_action(HandleInput(String::new()));
15566 cx.assert_editor_state(
15567 &r#"ˇone
15568 two
15569
15570 three
15571 fourˇ
15572 five
15573
15574 siˇx"#
15575 .unindent(),
15576 );
15577
15578 cx.dispatch_action(HandleInput("AAAA".to_string()));
15579 cx.assert_editor_state(
15580 &r#"AAAAˇone
15581 two
15582
15583 three
15584 fourAAAAˇ
15585 five
15586
15587 siAAAAˇx"#
15588 .unindent(),
15589 );
15590}
15591
15592#[gpui::test]
15593async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15594 init_test(cx, |_| {});
15595
15596 let mut cx = EditorTestContext::new(cx).await;
15597 cx.set_state(
15598 r#"let foo = 1;
15599let foo = 2;
15600let foo = 3;
15601let fooˇ = 4;
15602let foo = 5;
15603let foo = 6;
15604let foo = 7;
15605let foo = 8;
15606let foo = 9;
15607let foo = 10;
15608let foo = 11;
15609let foo = 12;
15610let foo = 13;
15611let foo = 14;
15612let foo = 15;"#,
15613 );
15614
15615 cx.update_editor(|e, window, cx| {
15616 assert_eq!(
15617 e.next_scroll_position,
15618 NextScrollCursorCenterTopBottom::Center,
15619 "Default next scroll direction is center",
15620 );
15621
15622 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15623 assert_eq!(
15624 e.next_scroll_position,
15625 NextScrollCursorCenterTopBottom::Top,
15626 "After center, next scroll direction should be top",
15627 );
15628
15629 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15630 assert_eq!(
15631 e.next_scroll_position,
15632 NextScrollCursorCenterTopBottom::Bottom,
15633 "After top, next scroll direction should be bottom",
15634 );
15635
15636 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15637 assert_eq!(
15638 e.next_scroll_position,
15639 NextScrollCursorCenterTopBottom::Center,
15640 "After bottom, scrolling should start over",
15641 );
15642
15643 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15644 assert_eq!(
15645 e.next_scroll_position,
15646 NextScrollCursorCenterTopBottom::Top,
15647 "Scrolling continues if retriggered fast enough"
15648 );
15649 });
15650
15651 cx.executor()
15652 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15653 cx.executor().run_until_parked();
15654 cx.update_editor(|e, _, _| {
15655 assert_eq!(
15656 e.next_scroll_position,
15657 NextScrollCursorCenterTopBottom::Center,
15658 "If scrolling is not triggered fast enough, it should reset"
15659 );
15660 });
15661}
15662
15663#[gpui::test]
15664async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15665 init_test(cx, |_| {});
15666 let mut cx = EditorLspTestContext::new_rust(
15667 lsp::ServerCapabilities {
15668 definition_provider: Some(lsp::OneOf::Left(true)),
15669 references_provider: Some(lsp::OneOf::Left(true)),
15670 ..lsp::ServerCapabilities::default()
15671 },
15672 cx,
15673 )
15674 .await;
15675
15676 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15677 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15678 move |params, _| async move {
15679 if empty_go_to_definition {
15680 Ok(None)
15681 } else {
15682 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15683 uri: params.text_document_position_params.text_document.uri,
15684 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15685 })))
15686 }
15687 },
15688 );
15689 let references =
15690 cx.lsp
15691 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15692 Ok(Some(vec![lsp::Location {
15693 uri: params.text_document_position.text_document.uri,
15694 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15695 }]))
15696 });
15697 (go_to_definition, references)
15698 };
15699
15700 cx.set_state(
15701 &r#"fn one() {
15702 let mut a = ˇtwo();
15703 }
15704
15705 fn two() {}"#
15706 .unindent(),
15707 );
15708 set_up_lsp_handlers(false, &mut cx);
15709 let navigated = cx
15710 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15711 .await
15712 .expect("Failed to navigate to definition");
15713 assert_eq!(
15714 navigated,
15715 Navigated::Yes,
15716 "Should have navigated to definition from the GetDefinition response"
15717 );
15718 cx.assert_editor_state(
15719 &r#"fn one() {
15720 let mut a = two();
15721 }
15722
15723 fn «twoˇ»() {}"#
15724 .unindent(),
15725 );
15726
15727 let editors = cx.update_workspace(|workspace, _, cx| {
15728 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15729 });
15730 cx.update_editor(|_, _, test_editor_cx| {
15731 assert_eq!(
15732 editors.len(),
15733 1,
15734 "Initially, only one, test, editor should be open in the workspace"
15735 );
15736 assert_eq!(
15737 test_editor_cx.entity(),
15738 editors.last().expect("Asserted len is 1").clone()
15739 );
15740 });
15741
15742 set_up_lsp_handlers(true, &mut cx);
15743 let navigated = cx
15744 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15745 .await
15746 .expect("Failed to navigate to lookup references");
15747 assert_eq!(
15748 navigated,
15749 Navigated::Yes,
15750 "Should have navigated to references as a fallback after empty GoToDefinition response"
15751 );
15752 // We should not change the selections in the existing file,
15753 // if opening another milti buffer with the references
15754 cx.assert_editor_state(
15755 &r#"fn one() {
15756 let mut a = two();
15757 }
15758
15759 fn «twoˇ»() {}"#
15760 .unindent(),
15761 );
15762 let editors = cx.update_workspace(|workspace, _, cx| {
15763 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15764 });
15765 cx.update_editor(|_, _, test_editor_cx| {
15766 assert_eq!(
15767 editors.len(),
15768 2,
15769 "After falling back to references search, we open a new editor with the results"
15770 );
15771 let references_fallback_text = editors
15772 .into_iter()
15773 .find(|new_editor| *new_editor != test_editor_cx.entity())
15774 .expect("Should have one non-test editor now")
15775 .read(test_editor_cx)
15776 .text(test_editor_cx);
15777 assert_eq!(
15778 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15779 "Should use the range from the references response and not the GoToDefinition one"
15780 );
15781 });
15782}
15783
15784#[gpui::test]
15785async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15786 init_test(cx, |_| {});
15787
15788 let language = Arc::new(Language::new(
15789 LanguageConfig::default(),
15790 Some(tree_sitter_rust::LANGUAGE.into()),
15791 ));
15792
15793 let text = r#"
15794 #[cfg(test)]
15795 mod tests() {
15796 #[test]
15797 fn runnable_1() {
15798 let a = 1;
15799 }
15800
15801 #[test]
15802 fn runnable_2() {
15803 let a = 1;
15804 let b = 2;
15805 }
15806 }
15807 "#
15808 .unindent();
15809
15810 let fs = FakeFs::new(cx.executor());
15811 fs.insert_file("/file.rs", Default::default()).await;
15812
15813 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15814 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15815 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15816 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15817 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15818
15819 let editor = cx.new_window_entity(|window, cx| {
15820 Editor::new(
15821 EditorMode::Full,
15822 multi_buffer,
15823 Some(project.clone()),
15824 true,
15825 window,
15826 cx,
15827 )
15828 });
15829
15830 editor.update_in(cx, |editor, window, cx| {
15831 let snapshot = editor.buffer().read(cx).snapshot(cx);
15832 editor.tasks.insert(
15833 (buffer.read(cx).remote_id(), 3),
15834 RunnableTasks {
15835 templates: vec![],
15836 offset: snapshot.anchor_before(43),
15837 column: 0,
15838 extra_variables: HashMap::default(),
15839 context_range: BufferOffset(43)..BufferOffset(85),
15840 },
15841 );
15842 editor.tasks.insert(
15843 (buffer.read(cx).remote_id(), 8),
15844 RunnableTasks {
15845 templates: vec![],
15846 offset: snapshot.anchor_before(86),
15847 column: 0,
15848 extra_variables: HashMap::default(),
15849 context_range: BufferOffset(86)..BufferOffset(191),
15850 },
15851 );
15852
15853 // Test finding task when cursor is inside function body
15854 editor.change_selections(None, window, cx, |s| {
15855 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15856 });
15857 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15858 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15859
15860 // Test finding task when cursor is on function name
15861 editor.change_selections(None, window, cx, |s| {
15862 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15863 });
15864 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15865 assert_eq!(row, 8, "Should find task when cursor is on function name");
15866 });
15867}
15868
15869#[gpui::test]
15870async fn test_folding_buffers(cx: &mut TestAppContext) {
15871 init_test(cx, |_| {});
15872
15873 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15874 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15875 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15876
15877 let fs = FakeFs::new(cx.executor());
15878 fs.insert_tree(
15879 path!("/a"),
15880 json!({
15881 "first.rs": sample_text_1,
15882 "second.rs": sample_text_2,
15883 "third.rs": sample_text_3,
15884 }),
15885 )
15886 .await;
15887 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15888 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15889 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15890 let worktree = project.update(cx, |project, cx| {
15891 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15892 assert_eq!(worktrees.len(), 1);
15893 worktrees.pop().unwrap()
15894 });
15895 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15896
15897 let buffer_1 = project
15898 .update(cx, |project, cx| {
15899 project.open_buffer((worktree_id, "first.rs"), cx)
15900 })
15901 .await
15902 .unwrap();
15903 let buffer_2 = project
15904 .update(cx, |project, cx| {
15905 project.open_buffer((worktree_id, "second.rs"), cx)
15906 })
15907 .await
15908 .unwrap();
15909 let buffer_3 = project
15910 .update(cx, |project, cx| {
15911 project.open_buffer((worktree_id, "third.rs"), cx)
15912 })
15913 .await
15914 .unwrap();
15915
15916 let multi_buffer = cx.new(|cx| {
15917 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15918 multi_buffer.push_excerpts(
15919 buffer_1.clone(),
15920 [
15921 ExcerptRange {
15922 context: Point::new(0, 0)..Point::new(3, 0),
15923 primary: None,
15924 },
15925 ExcerptRange {
15926 context: Point::new(5, 0)..Point::new(7, 0),
15927 primary: None,
15928 },
15929 ExcerptRange {
15930 context: Point::new(9, 0)..Point::new(10, 4),
15931 primary: None,
15932 },
15933 ],
15934 cx,
15935 );
15936 multi_buffer.push_excerpts(
15937 buffer_2.clone(),
15938 [
15939 ExcerptRange {
15940 context: Point::new(0, 0)..Point::new(3, 0),
15941 primary: None,
15942 },
15943 ExcerptRange {
15944 context: Point::new(5, 0)..Point::new(7, 0),
15945 primary: None,
15946 },
15947 ExcerptRange {
15948 context: Point::new(9, 0)..Point::new(10, 4),
15949 primary: None,
15950 },
15951 ],
15952 cx,
15953 );
15954 multi_buffer.push_excerpts(
15955 buffer_3.clone(),
15956 [
15957 ExcerptRange {
15958 context: Point::new(0, 0)..Point::new(3, 0),
15959 primary: None,
15960 },
15961 ExcerptRange {
15962 context: Point::new(5, 0)..Point::new(7, 0),
15963 primary: None,
15964 },
15965 ExcerptRange {
15966 context: Point::new(9, 0)..Point::new(10, 4),
15967 primary: None,
15968 },
15969 ],
15970 cx,
15971 );
15972 multi_buffer
15973 });
15974 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15975 Editor::new(
15976 EditorMode::Full,
15977 multi_buffer.clone(),
15978 Some(project.clone()),
15979 true,
15980 window,
15981 cx,
15982 )
15983 });
15984
15985 assert_eq!(
15986 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15987 "\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",
15988 );
15989
15990 multi_buffer_editor.update(cx, |editor, cx| {
15991 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15992 });
15993 assert_eq!(
15994 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15995 "\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",
15996 "After folding the first buffer, its text should not be displayed"
15997 );
15998
15999 multi_buffer_editor.update(cx, |editor, cx| {
16000 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16001 });
16002 assert_eq!(
16003 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16004 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16005 "After folding the second buffer, its text should not be displayed"
16006 );
16007
16008 multi_buffer_editor.update(cx, |editor, cx| {
16009 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16010 });
16011 assert_eq!(
16012 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16013 "\n\n\n\n\n",
16014 "After folding the third buffer, its text should not be displayed"
16015 );
16016
16017 // Emulate selection inside the fold logic, that should work
16018 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16019 editor
16020 .snapshot(window, cx)
16021 .next_line_boundary(Point::new(0, 4));
16022 });
16023
16024 multi_buffer_editor.update(cx, |editor, cx| {
16025 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16026 });
16027 assert_eq!(
16028 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16029 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16030 "After unfolding the second buffer, its text should be displayed"
16031 );
16032
16033 // Typing inside of buffer 1 causes that buffer to be unfolded.
16034 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16035 assert_eq!(
16036 multi_buffer
16037 .read(cx)
16038 .snapshot(cx)
16039 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16040 .collect::<String>(),
16041 "bbbb"
16042 );
16043 editor.change_selections(None, window, cx, |selections| {
16044 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16045 });
16046 editor.handle_input("B", window, cx);
16047 });
16048
16049 assert_eq!(
16050 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16051 "\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",
16052 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16053 );
16054
16055 multi_buffer_editor.update(cx, |editor, cx| {
16056 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16057 });
16058 assert_eq!(
16059 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16060 "\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",
16061 "After unfolding the all buffers, all original text should be displayed"
16062 );
16063}
16064
16065#[gpui::test]
16066async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16067 init_test(cx, |_| {});
16068
16069 let sample_text_1 = "1111\n2222\n3333".to_string();
16070 let sample_text_2 = "4444\n5555\n6666".to_string();
16071 let sample_text_3 = "7777\n8888\n9999".to_string();
16072
16073 let fs = FakeFs::new(cx.executor());
16074 fs.insert_tree(
16075 path!("/a"),
16076 json!({
16077 "first.rs": sample_text_1,
16078 "second.rs": sample_text_2,
16079 "third.rs": sample_text_3,
16080 }),
16081 )
16082 .await;
16083 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16084 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16085 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16086 let worktree = project.update(cx, |project, cx| {
16087 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16088 assert_eq!(worktrees.len(), 1);
16089 worktrees.pop().unwrap()
16090 });
16091 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16092
16093 let buffer_1 = project
16094 .update(cx, |project, cx| {
16095 project.open_buffer((worktree_id, "first.rs"), cx)
16096 })
16097 .await
16098 .unwrap();
16099 let buffer_2 = project
16100 .update(cx, |project, cx| {
16101 project.open_buffer((worktree_id, "second.rs"), cx)
16102 })
16103 .await
16104 .unwrap();
16105 let buffer_3 = project
16106 .update(cx, |project, cx| {
16107 project.open_buffer((worktree_id, "third.rs"), cx)
16108 })
16109 .await
16110 .unwrap();
16111
16112 let multi_buffer = cx.new(|cx| {
16113 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16114 multi_buffer.push_excerpts(
16115 buffer_1.clone(),
16116 [ExcerptRange {
16117 context: Point::new(0, 0)..Point::new(3, 0),
16118 primary: None,
16119 }],
16120 cx,
16121 );
16122 multi_buffer.push_excerpts(
16123 buffer_2.clone(),
16124 [ExcerptRange {
16125 context: Point::new(0, 0)..Point::new(3, 0),
16126 primary: None,
16127 }],
16128 cx,
16129 );
16130 multi_buffer.push_excerpts(
16131 buffer_3.clone(),
16132 [ExcerptRange {
16133 context: Point::new(0, 0)..Point::new(3, 0),
16134 primary: None,
16135 }],
16136 cx,
16137 );
16138 multi_buffer
16139 });
16140
16141 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16142 Editor::new(
16143 EditorMode::Full,
16144 multi_buffer,
16145 Some(project.clone()),
16146 true,
16147 window,
16148 cx,
16149 )
16150 });
16151
16152 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16153 assert_eq!(
16154 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16155 full_text,
16156 );
16157
16158 multi_buffer_editor.update(cx, |editor, cx| {
16159 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16160 });
16161 assert_eq!(
16162 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16163 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16164 "After folding the first buffer, its text should not be displayed"
16165 );
16166
16167 multi_buffer_editor.update(cx, |editor, cx| {
16168 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16169 });
16170
16171 assert_eq!(
16172 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16173 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16174 "After folding the second buffer, its text should not be displayed"
16175 );
16176
16177 multi_buffer_editor.update(cx, |editor, cx| {
16178 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16179 });
16180 assert_eq!(
16181 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16182 "\n\n\n\n\n",
16183 "After folding the third buffer, its text should not be displayed"
16184 );
16185
16186 multi_buffer_editor.update(cx, |editor, cx| {
16187 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16188 });
16189 assert_eq!(
16190 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16191 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16192 "After unfolding the second buffer, its text should be displayed"
16193 );
16194
16195 multi_buffer_editor.update(cx, |editor, cx| {
16196 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16197 });
16198 assert_eq!(
16199 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16200 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16201 "After unfolding the first buffer, its text should be displayed"
16202 );
16203
16204 multi_buffer_editor.update(cx, |editor, cx| {
16205 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16206 });
16207 assert_eq!(
16208 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16209 full_text,
16210 "After unfolding all buffers, all original text should be displayed"
16211 );
16212}
16213
16214#[gpui::test]
16215async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16216 init_test(cx, |_| {});
16217
16218 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16219
16220 let fs = FakeFs::new(cx.executor());
16221 fs.insert_tree(
16222 path!("/a"),
16223 json!({
16224 "main.rs": sample_text,
16225 }),
16226 )
16227 .await;
16228 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16229 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16230 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16231 let worktree = project.update(cx, |project, cx| {
16232 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16233 assert_eq!(worktrees.len(), 1);
16234 worktrees.pop().unwrap()
16235 });
16236 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16237
16238 let buffer_1 = project
16239 .update(cx, |project, cx| {
16240 project.open_buffer((worktree_id, "main.rs"), cx)
16241 })
16242 .await
16243 .unwrap();
16244
16245 let multi_buffer = cx.new(|cx| {
16246 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16247 multi_buffer.push_excerpts(
16248 buffer_1.clone(),
16249 [ExcerptRange {
16250 context: Point::new(0, 0)
16251 ..Point::new(
16252 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16253 0,
16254 ),
16255 primary: None,
16256 }],
16257 cx,
16258 );
16259 multi_buffer
16260 });
16261 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16262 Editor::new(
16263 EditorMode::Full,
16264 multi_buffer,
16265 Some(project.clone()),
16266 true,
16267 window,
16268 cx,
16269 )
16270 });
16271
16272 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16273 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16274 enum TestHighlight {}
16275 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16276 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16277 editor.highlight_text::<TestHighlight>(
16278 vec![highlight_range.clone()],
16279 HighlightStyle::color(Hsla::green()),
16280 cx,
16281 );
16282 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16283 });
16284
16285 let full_text = format!("\n\n\n{sample_text}\n");
16286 assert_eq!(
16287 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16288 full_text,
16289 );
16290}
16291
16292#[gpui::test]
16293async fn test_inline_completion_text(cx: &mut TestAppContext) {
16294 init_test(cx, |_| {});
16295
16296 // Simple insertion
16297 assert_highlighted_edits(
16298 "Hello, world!",
16299 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16300 true,
16301 cx,
16302 |highlighted_edits, cx| {
16303 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16304 assert_eq!(highlighted_edits.highlights.len(), 1);
16305 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16306 assert_eq!(
16307 highlighted_edits.highlights[0].1.background_color,
16308 Some(cx.theme().status().created_background)
16309 );
16310 },
16311 )
16312 .await;
16313
16314 // Replacement
16315 assert_highlighted_edits(
16316 "This is a test.",
16317 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16318 false,
16319 cx,
16320 |highlighted_edits, cx| {
16321 assert_eq!(highlighted_edits.text, "That is a test.");
16322 assert_eq!(highlighted_edits.highlights.len(), 1);
16323 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16324 assert_eq!(
16325 highlighted_edits.highlights[0].1.background_color,
16326 Some(cx.theme().status().created_background)
16327 );
16328 },
16329 )
16330 .await;
16331
16332 // Multiple edits
16333 assert_highlighted_edits(
16334 "Hello, world!",
16335 vec![
16336 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16337 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16338 ],
16339 false,
16340 cx,
16341 |highlighted_edits, cx| {
16342 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16343 assert_eq!(highlighted_edits.highlights.len(), 2);
16344 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16345 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16346 assert_eq!(
16347 highlighted_edits.highlights[0].1.background_color,
16348 Some(cx.theme().status().created_background)
16349 );
16350 assert_eq!(
16351 highlighted_edits.highlights[1].1.background_color,
16352 Some(cx.theme().status().created_background)
16353 );
16354 },
16355 )
16356 .await;
16357
16358 // Multiple lines with edits
16359 assert_highlighted_edits(
16360 "First line\nSecond line\nThird line\nFourth line",
16361 vec![
16362 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16363 (
16364 Point::new(2, 0)..Point::new(2, 10),
16365 "New third line".to_string(),
16366 ),
16367 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16368 ],
16369 false,
16370 cx,
16371 |highlighted_edits, cx| {
16372 assert_eq!(
16373 highlighted_edits.text,
16374 "Second modified\nNew third line\nFourth updated line"
16375 );
16376 assert_eq!(highlighted_edits.highlights.len(), 3);
16377 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16378 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16379 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16380 for highlight in &highlighted_edits.highlights {
16381 assert_eq!(
16382 highlight.1.background_color,
16383 Some(cx.theme().status().created_background)
16384 );
16385 }
16386 },
16387 )
16388 .await;
16389}
16390
16391#[gpui::test]
16392async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16393 init_test(cx, |_| {});
16394
16395 // Deletion
16396 assert_highlighted_edits(
16397 "Hello, world!",
16398 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16399 true,
16400 cx,
16401 |highlighted_edits, cx| {
16402 assert_eq!(highlighted_edits.text, "Hello, world!");
16403 assert_eq!(highlighted_edits.highlights.len(), 1);
16404 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16405 assert_eq!(
16406 highlighted_edits.highlights[0].1.background_color,
16407 Some(cx.theme().status().deleted_background)
16408 );
16409 },
16410 )
16411 .await;
16412
16413 // Insertion
16414 assert_highlighted_edits(
16415 "Hello, world!",
16416 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16417 true,
16418 cx,
16419 |highlighted_edits, cx| {
16420 assert_eq!(highlighted_edits.highlights.len(), 1);
16421 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16422 assert_eq!(
16423 highlighted_edits.highlights[0].1.background_color,
16424 Some(cx.theme().status().created_background)
16425 );
16426 },
16427 )
16428 .await;
16429}
16430
16431async fn assert_highlighted_edits(
16432 text: &str,
16433 edits: Vec<(Range<Point>, String)>,
16434 include_deletions: bool,
16435 cx: &mut TestAppContext,
16436 assertion_fn: impl Fn(HighlightedText, &App),
16437) {
16438 let window = cx.add_window(|window, cx| {
16439 let buffer = MultiBuffer::build_simple(text, cx);
16440 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16441 });
16442 let cx = &mut VisualTestContext::from_window(*window, cx);
16443
16444 let (buffer, snapshot) = window
16445 .update(cx, |editor, _window, cx| {
16446 (
16447 editor.buffer().clone(),
16448 editor.buffer().read(cx).snapshot(cx),
16449 )
16450 })
16451 .unwrap();
16452
16453 let edits = edits
16454 .into_iter()
16455 .map(|(range, edit)| {
16456 (
16457 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16458 edit,
16459 )
16460 })
16461 .collect::<Vec<_>>();
16462
16463 let text_anchor_edits = edits
16464 .clone()
16465 .into_iter()
16466 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16467 .collect::<Vec<_>>();
16468
16469 let edit_preview = window
16470 .update(cx, |_, _window, cx| {
16471 buffer
16472 .read(cx)
16473 .as_singleton()
16474 .unwrap()
16475 .read(cx)
16476 .preview_edits(text_anchor_edits.into(), cx)
16477 })
16478 .unwrap()
16479 .await;
16480
16481 cx.update(|_window, cx| {
16482 let highlighted_edits = inline_completion_edit_text(
16483 &snapshot.as_singleton().unwrap().2,
16484 &edits,
16485 &edit_preview,
16486 include_deletions,
16487 cx,
16488 );
16489 assertion_fn(highlighted_edits, cx)
16490 });
16491}
16492
16493#[gpui::test]
16494async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16495 init_test(cx, |_| {});
16496 let capabilities = lsp::ServerCapabilities {
16497 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16498 prepare_provider: Some(true),
16499 work_done_progress_options: Default::default(),
16500 })),
16501 ..Default::default()
16502 };
16503 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16504
16505 cx.set_state(indoc! {"
16506 struct Fˇoo {}
16507 "});
16508
16509 cx.update_editor(|editor, _, cx| {
16510 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16511 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16512 editor.highlight_background::<DocumentHighlightRead>(
16513 &[highlight_range],
16514 |c| c.editor_document_highlight_read_background,
16515 cx,
16516 );
16517 });
16518
16519 let mut prepare_rename_handler =
16520 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16521 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16522 start: lsp::Position {
16523 line: 0,
16524 character: 7,
16525 },
16526 end: lsp::Position {
16527 line: 0,
16528 character: 10,
16529 },
16530 })))
16531 });
16532 let prepare_rename_task = cx
16533 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16534 .expect("Prepare rename was not started");
16535 prepare_rename_handler.next().await.unwrap();
16536 prepare_rename_task.await.expect("Prepare rename failed");
16537
16538 let mut rename_handler =
16539 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16540 let edit = lsp::TextEdit {
16541 range: lsp::Range {
16542 start: lsp::Position {
16543 line: 0,
16544 character: 7,
16545 },
16546 end: lsp::Position {
16547 line: 0,
16548 character: 10,
16549 },
16550 },
16551 new_text: "FooRenamed".to_string(),
16552 };
16553 Ok(Some(lsp::WorkspaceEdit::new(
16554 // Specify the same edit twice
16555 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16556 )))
16557 });
16558 let rename_task = cx
16559 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16560 .expect("Confirm rename was not started");
16561 rename_handler.next().await.unwrap();
16562 rename_task.await.expect("Confirm rename failed");
16563 cx.run_until_parked();
16564
16565 // Despite two edits, only one is actually applied as those are identical
16566 cx.assert_editor_state(indoc! {"
16567 struct FooRenamedˇ {}
16568 "});
16569}
16570
16571#[gpui::test]
16572async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16573 init_test(cx, |_| {});
16574 // These capabilities indicate that the server does not support prepare rename.
16575 let capabilities = lsp::ServerCapabilities {
16576 rename_provider: Some(lsp::OneOf::Left(true)),
16577 ..Default::default()
16578 };
16579 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16580
16581 cx.set_state(indoc! {"
16582 struct Fˇoo {}
16583 "});
16584
16585 cx.update_editor(|editor, _window, cx| {
16586 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16587 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16588 editor.highlight_background::<DocumentHighlightRead>(
16589 &[highlight_range],
16590 |c| c.editor_document_highlight_read_background,
16591 cx,
16592 );
16593 });
16594
16595 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16596 .expect("Prepare rename was not started")
16597 .await
16598 .expect("Prepare rename failed");
16599
16600 let mut rename_handler =
16601 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16602 let edit = lsp::TextEdit {
16603 range: lsp::Range {
16604 start: lsp::Position {
16605 line: 0,
16606 character: 7,
16607 },
16608 end: lsp::Position {
16609 line: 0,
16610 character: 10,
16611 },
16612 },
16613 new_text: "FooRenamed".to_string(),
16614 };
16615 Ok(Some(lsp::WorkspaceEdit::new(
16616 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16617 )))
16618 });
16619 let rename_task = cx
16620 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16621 .expect("Confirm rename was not started");
16622 rename_handler.next().await.unwrap();
16623 rename_task.await.expect("Confirm rename failed");
16624 cx.run_until_parked();
16625
16626 // Correct range is renamed, as `surrounding_word` is used to find it.
16627 cx.assert_editor_state(indoc! {"
16628 struct FooRenamedˇ {}
16629 "});
16630}
16631
16632#[gpui::test]
16633async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16634 init_test(cx, |_| {});
16635 let mut cx = EditorTestContext::new(cx).await;
16636
16637 let language = Arc::new(
16638 Language::new(
16639 LanguageConfig::default(),
16640 Some(tree_sitter_html::LANGUAGE.into()),
16641 )
16642 .with_brackets_query(
16643 r#"
16644 ("<" @open "/>" @close)
16645 ("</" @open ">" @close)
16646 ("<" @open ">" @close)
16647 ("\"" @open "\"" @close)
16648 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16649 "#,
16650 )
16651 .unwrap(),
16652 );
16653 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16654
16655 cx.set_state(indoc! {"
16656 <span>ˇ</span>
16657 "});
16658 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16659 cx.assert_editor_state(indoc! {"
16660 <span>
16661 ˇ
16662 </span>
16663 "});
16664
16665 cx.set_state(indoc! {"
16666 <span><span></span>ˇ</span>
16667 "});
16668 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16669 cx.assert_editor_state(indoc! {"
16670 <span><span></span>
16671 ˇ</span>
16672 "});
16673
16674 cx.set_state(indoc! {"
16675 <span>ˇ
16676 </span>
16677 "});
16678 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16679 cx.assert_editor_state(indoc! {"
16680 <span>
16681 ˇ
16682 </span>
16683 "});
16684}
16685
16686fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16687 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16688 point..point
16689}
16690
16691fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16692 let (text, ranges) = marked_text_ranges(marked_text, true);
16693 assert_eq!(editor.text(cx), text);
16694 assert_eq!(
16695 editor.selections.ranges(cx),
16696 ranges,
16697 "Assert selections are {}",
16698 marked_text
16699 );
16700}
16701
16702pub fn handle_signature_help_request(
16703 cx: &mut EditorLspTestContext,
16704 mocked_response: lsp::SignatureHelp,
16705) -> impl Future<Output = ()> {
16706 let mut request =
16707 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16708 let mocked_response = mocked_response.clone();
16709 async move { Ok(Some(mocked_response)) }
16710 });
16711
16712 async move {
16713 request.next().await;
16714 }
16715}
16716
16717/// Handle completion request passing a marked string specifying where the completion
16718/// should be triggered from using '|' character, what range should be replaced, and what completions
16719/// should be returned using '<' and '>' to delimit the range
16720pub fn handle_completion_request(
16721 cx: &mut EditorLspTestContext,
16722 marked_string: &str,
16723 completions: Vec<&'static str>,
16724 counter: Arc<AtomicUsize>,
16725) -> impl Future<Output = ()> {
16726 let complete_from_marker: TextRangeMarker = '|'.into();
16727 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16728 let (_, mut marked_ranges) = marked_text_ranges_by(
16729 marked_string,
16730 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16731 );
16732
16733 let complete_from_position =
16734 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16735 let replace_range =
16736 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16737
16738 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16739 let completions = completions.clone();
16740 counter.fetch_add(1, atomic::Ordering::Release);
16741 async move {
16742 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16743 assert_eq!(
16744 params.text_document_position.position,
16745 complete_from_position
16746 );
16747 Ok(Some(lsp::CompletionResponse::Array(
16748 completions
16749 .iter()
16750 .map(|completion_text| lsp::CompletionItem {
16751 label: completion_text.to_string(),
16752 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16753 range: replace_range,
16754 new_text: completion_text.to_string(),
16755 })),
16756 ..Default::default()
16757 })
16758 .collect(),
16759 )))
16760 }
16761 });
16762
16763 async move {
16764 request.next().await;
16765 }
16766}
16767
16768fn handle_resolve_completion_request(
16769 cx: &mut EditorLspTestContext,
16770 edits: Option<Vec<(&'static str, &'static str)>>,
16771) -> impl Future<Output = ()> {
16772 let edits = edits.map(|edits| {
16773 edits
16774 .iter()
16775 .map(|(marked_string, new_text)| {
16776 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16777 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16778 lsp::TextEdit::new(replace_range, new_text.to_string())
16779 })
16780 .collect::<Vec<_>>()
16781 });
16782
16783 let mut request =
16784 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16785 let edits = edits.clone();
16786 async move {
16787 Ok(lsp::CompletionItem {
16788 additional_text_edits: edits,
16789 ..Default::default()
16790 })
16791 }
16792 });
16793
16794 async move {
16795 request.next().await;
16796 }
16797}
16798
16799pub(crate) fn update_test_language_settings(
16800 cx: &mut TestAppContext,
16801 f: impl Fn(&mut AllLanguageSettingsContent),
16802) {
16803 cx.update(|cx| {
16804 SettingsStore::update_global(cx, |store, cx| {
16805 store.update_user_settings::<AllLanguageSettings>(cx, f);
16806 });
16807 });
16808}
16809
16810pub(crate) fn update_test_project_settings(
16811 cx: &mut TestAppContext,
16812 f: impl Fn(&mut ProjectSettings),
16813) {
16814 cx.update(|cx| {
16815 SettingsStore::update_global(cx, |store, cx| {
16816 store.update_user_settings::<ProjectSettings>(cx, f);
16817 });
16818 });
16819}
16820
16821pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16822 cx.update(|cx| {
16823 assets::Assets.load_test_fonts(cx);
16824 let store = SettingsStore::test(cx);
16825 cx.set_global(store);
16826 theme::init(theme::LoadThemes::JustBase, cx);
16827 release_channel::init(SemanticVersion::default(), cx);
16828 client::init_settings(cx);
16829 language::init(cx);
16830 Project::init_settings(cx);
16831 workspace::init_settings(cx);
16832 crate::init(cx);
16833 });
16834
16835 update_test_language_settings(cx, f);
16836}
16837
16838#[track_caller]
16839fn assert_hunk_revert(
16840 not_reverted_text_with_selections: &str,
16841 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
16842 expected_reverted_text_with_selections: &str,
16843 base_text: &str,
16844 cx: &mut EditorLspTestContext,
16845) {
16846 cx.set_state(not_reverted_text_with_selections);
16847 cx.set_head_text(base_text);
16848 cx.clear_index_text();
16849 cx.executor().run_until_parked();
16850
16851 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16852 let snapshot = editor.snapshot(window, cx);
16853 let reverted_hunk_statuses = snapshot
16854 .buffer_snapshot
16855 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16856 .map(|hunk| hunk.status())
16857 .collect::<Vec<_>>();
16858
16859 editor.git_restore(&Default::default(), window, cx);
16860 reverted_hunk_statuses
16861 });
16862 cx.executor().run_until_parked();
16863 cx.assert_editor_state(expected_reverted_text_with_selections);
16864 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16865}