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};
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;
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 unindent::Unindent;
40use util::{
41 assert_set_eq, path,
42 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
43 uri,
44};
45use workspace::{
46 item::{FollowEvent, FollowableItem, Item, ItemHandle},
47 NavigationEntry, ViewId,
48};
49
50#[gpui::test]
51fn test_edit_events(cx: &mut TestAppContext) {
52 init_test(cx, |_| {});
53
54 let buffer = cx.new(|cx| {
55 let mut buffer = language::Buffer::local("123456", cx);
56 buffer.set_group_interval(Duration::from_secs(1));
57 buffer
58 });
59
60 let events = Rc::new(RefCell::new(Vec::new()));
61 let editor1 = cx.add_window({
62 let events = events.clone();
63 |window, cx| {
64 let entity = cx.entity().clone();
65 cx.subscribe_in(
66 &entity,
67 window,
68 move |_, _, event: &EditorEvent, _, _| match event {
69 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
70 EditorEvent::BufferEdited => {
71 events.borrow_mut().push(("editor1", "buffer edited"))
72 }
73 _ => {}
74 },
75 )
76 .detach();
77 Editor::for_buffer(buffer.clone(), None, window, cx)
78 }
79 });
80
81 let editor2 = cx.add_window({
82 let events = events.clone();
83 |window, cx| {
84 cx.subscribe_in(
85 &cx.entity().clone(),
86 window,
87 move |_, _, event: &EditorEvent, _, _| match event {
88 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
89 EditorEvent::BufferEdited => {
90 events.borrow_mut().push(("editor2", "buffer edited"))
91 }
92 _ => {}
93 },
94 )
95 .detach();
96 Editor::for_buffer(buffer.clone(), None, window, cx)
97 }
98 });
99
100 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
101
102 // Mutating editor 1 will emit an `Edited` event only for that editor.
103 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
104 assert_eq!(
105 mem::take(&mut *events.borrow_mut()),
106 [
107 ("editor1", "edited"),
108 ("editor1", "buffer edited"),
109 ("editor2", "buffer edited"),
110 ]
111 );
112
113 // Mutating editor 2 will emit an `Edited` event only for that editor.
114 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
115 assert_eq!(
116 mem::take(&mut *events.borrow_mut()),
117 [
118 ("editor2", "edited"),
119 ("editor1", "buffer edited"),
120 ("editor2", "buffer edited"),
121 ]
122 );
123
124 // Undoing on editor 1 will emit an `Edited` event only for that editor.
125 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
126 assert_eq!(
127 mem::take(&mut *events.borrow_mut()),
128 [
129 ("editor1", "edited"),
130 ("editor1", "buffer edited"),
131 ("editor2", "buffer edited"),
132 ]
133 );
134
135 // Redoing on editor 1 will emit an `Edited` event only for that editor.
136 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
137 assert_eq!(
138 mem::take(&mut *events.borrow_mut()),
139 [
140 ("editor1", "edited"),
141 ("editor1", "buffer edited"),
142 ("editor2", "buffer edited"),
143 ]
144 );
145
146 // Undoing on editor 2 will emit an `Edited` event only for that editor.
147 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
148 assert_eq!(
149 mem::take(&mut *events.borrow_mut()),
150 [
151 ("editor2", "edited"),
152 ("editor1", "buffer edited"),
153 ("editor2", "buffer edited"),
154 ]
155 );
156
157 // Redoing on editor 2 will emit an `Edited` event only for that editor.
158 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
159 assert_eq!(
160 mem::take(&mut *events.borrow_mut()),
161 [
162 ("editor2", "edited"),
163 ("editor1", "buffer edited"),
164 ("editor2", "buffer edited"),
165 ]
166 );
167
168 // No event is emitted when the mutation is a no-op.
169 _ = editor2.update(cx, |editor, window, cx| {
170 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
171
172 editor.backspace(&Backspace, window, cx);
173 });
174 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
175}
176
177#[gpui::test]
178fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
179 init_test(cx, |_| {});
180
181 let mut now = Instant::now();
182 let group_interval = Duration::from_millis(1);
183 let buffer = cx.new(|cx| {
184 let mut buf = language::Buffer::local("123456", cx);
185 buf.set_group_interval(group_interval);
186 buf
187 });
188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
189 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
190
191 _ = editor.update(cx, |editor, window, cx| {
192 editor.start_transaction_at(now, window, cx);
193 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
194
195 editor.insert("cd", window, cx);
196 editor.end_transaction_at(now, cx);
197 assert_eq!(editor.text(cx), "12cd56");
198 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
199
200 editor.start_transaction_at(now, window, cx);
201 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
202 editor.insert("e", window, cx);
203 editor.end_transaction_at(now, cx);
204 assert_eq!(editor.text(cx), "12cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
206
207 now += group_interval + Duration::from_millis(1);
208 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
209
210 // Simulate an edit in another editor
211 buffer.update(cx, |buffer, cx| {
212 buffer.start_transaction_at(now, cx);
213 buffer.edit([(0..1, "a")], None, cx);
214 buffer.edit([(1..1, "b")], None, cx);
215 buffer.end_transaction_at(now, cx);
216 });
217
218 assert_eq!(editor.text(cx), "ab2cde6");
219 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
220
221 // Last transaction happened past the group interval in a different editor.
222 // Undo it individually and don't restore selections.
223 editor.undo(&Undo, window, cx);
224 assert_eq!(editor.text(cx), "12cde6");
225 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
226
227 // First two transactions happened within the group interval in this editor.
228 // Undo them together and restore selections.
229 editor.undo(&Undo, window, cx);
230 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
231 assert_eq!(editor.text(cx), "123456");
232 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
233
234 // Redo the first two transactions together.
235 editor.redo(&Redo, window, cx);
236 assert_eq!(editor.text(cx), "12cde6");
237 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
238
239 // Redo the last transaction on its own.
240 editor.redo(&Redo, window, cx);
241 assert_eq!(editor.text(cx), "ab2cde6");
242 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
243
244 // Test empty transactions.
245 editor.start_transaction_at(now, window, cx);
246 editor.end_transaction_at(now, cx);
247 editor.undo(&Undo, window, cx);
248 assert_eq!(editor.text(cx), "12cde6");
249 });
250}
251
252#[gpui::test]
253fn test_ime_composition(cx: &mut TestAppContext) {
254 init_test(cx, |_| {});
255
256 let buffer = cx.new(|cx| {
257 let mut buffer = language::Buffer::local("abcde", cx);
258 // Ensure automatic grouping doesn't occur.
259 buffer.set_group_interval(Duration::ZERO);
260 buffer
261 });
262
263 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
264 cx.add_window(|window, cx| {
265 let mut editor = build_editor(buffer.clone(), window, cx);
266
267 // Start a new IME composition.
268 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
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 assert_eq!(editor.text(cx), "äbcde");
272 assert_eq!(
273 editor.marked_text_ranges(cx),
274 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
275 );
276
277 // Finalize IME composition.
278 editor.replace_text_in_range(None, "ā", window, cx);
279 assert_eq!(editor.text(cx), "ābcde");
280 assert_eq!(editor.marked_text_ranges(cx), None);
281
282 // IME composition edits are grouped and are undone/redone at once.
283 editor.undo(&Default::default(), window, cx);
284 assert_eq!(editor.text(cx), "abcde");
285 assert_eq!(editor.marked_text_ranges(cx), None);
286 editor.redo(&Default::default(), window, cx);
287 assert_eq!(editor.text(cx), "ābcde");
288 assert_eq!(editor.marked_text_ranges(cx), None);
289
290 // Start a new IME composition.
291 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
295 );
296
297 // Undoing during an IME composition cancels it.
298 editor.undo(&Default::default(), window, cx);
299 assert_eq!(editor.text(cx), "ābcde");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
303 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
304 assert_eq!(editor.text(cx), "ābcdè");
305 assert_eq!(
306 editor.marked_text_ranges(cx),
307 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
308 );
309
310 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
311 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
312 assert_eq!(editor.text(cx), "ābcdę");
313 assert_eq!(editor.marked_text_ranges(cx), None);
314
315 // Start a new IME composition with multiple cursors.
316 editor.change_selections(None, window, cx, |s| {
317 s.select_ranges([
318 OffsetUtf16(1)..OffsetUtf16(1),
319 OffsetUtf16(3)..OffsetUtf16(3),
320 OffsetUtf16(5)..OffsetUtf16(5),
321 ])
322 });
323 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
324 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
325 assert_eq!(
326 editor.marked_text_ranges(cx),
327 Some(vec![
328 OffsetUtf16(0)..OffsetUtf16(3),
329 OffsetUtf16(4)..OffsetUtf16(7),
330 OffsetUtf16(8)..OffsetUtf16(11)
331 ])
332 );
333
334 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
335 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
336 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
337 assert_eq!(
338 editor.marked_text_ranges(cx),
339 Some(vec![
340 OffsetUtf16(1)..OffsetUtf16(2),
341 OffsetUtf16(5)..OffsetUtf16(6),
342 OffsetUtf16(9)..OffsetUtf16(10)
343 ])
344 );
345
346 // Finalize IME composition with multiple cursors.
347 editor.replace_text_in_range(Some(9..10), "2", window, cx);
348 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
349 assert_eq!(editor.marked_text_ranges(cx), None);
350
351 editor
352 });
353}
354
355#[gpui::test]
356fn test_selection_with_mouse(cx: &mut TestAppContext) {
357 init_test(cx, |_| {});
358
359 let editor = cx.add_window(|window, cx| {
360 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
361 build_editor(buffer, window, cx)
362 });
363
364 _ = editor.update(cx, |editor, window, cx| {
365 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
366 });
367 assert_eq!(
368 editor
369 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
370 .unwrap(),
371 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
372 );
373
374 _ = editor.update(cx, |editor, window, cx| {
375 editor.update_selection(
376 DisplayPoint::new(DisplayRow(3), 3),
377 0,
378 gpui::Point::<f32>::default(),
379 window,
380 cx,
381 );
382 });
383
384 assert_eq!(
385 editor
386 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
387 .unwrap(),
388 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
389 );
390
391 _ = editor.update(cx, |editor, window, cx| {
392 editor.update_selection(
393 DisplayPoint::new(DisplayRow(1), 1),
394 0,
395 gpui::Point::<f32>::default(),
396 window,
397 cx,
398 );
399 });
400
401 assert_eq!(
402 editor
403 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
404 .unwrap(),
405 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
406 );
407
408 _ = editor.update(cx, |editor, window, cx| {
409 editor.end_selection(window, cx);
410 editor.update_selection(
411 DisplayPoint::new(DisplayRow(3), 3),
412 0,
413 gpui::Point::<f32>::default(),
414 window,
415 cx,
416 );
417 });
418
419 assert_eq!(
420 editor
421 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
422 .unwrap(),
423 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
424 );
425
426 _ = editor.update(cx, |editor, window, cx| {
427 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
428 editor.update_selection(
429 DisplayPoint::new(DisplayRow(0), 0),
430 0,
431 gpui::Point::<f32>::default(),
432 window,
433 cx,
434 );
435 });
436
437 assert_eq!(
438 editor
439 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
440 .unwrap(),
441 [
442 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
443 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
444 ]
445 );
446
447 _ = editor.update(cx, |editor, window, cx| {
448 editor.end_selection(window, cx);
449 });
450
451 assert_eq!(
452 editor
453 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
454 .unwrap(),
455 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
456 );
457}
458
459#[gpui::test]
460fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
461 init_test(cx, |_| {});
462
463 let editor = cx.add_window(|window, cx| {
464 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
465 build_editor(buffer, window, cx)
466 });
467
468 _ = editor.update(cx, |editor, window, cx| {
469 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
470 });
471
472 _ = editor.update(cx, |editor, window, cx| {
473 editor.end_selection(window, cx);
474 });
475
476 _ = editor.update(cx, |editor, window, cx| {
477 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
478 });
479
480 _ = editor.update(cx, |editor, window, cx| {
481 editor.end_selection(window, cx);
482 });
483
484 assert_eq!(
485 editor
486 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
487 .unwrap(),
488 [
489 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
490 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
491 ]
492 );
493
494 _ = editor.update(cx, |editor, window, cx| {
495 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
496 });
497
498 _ = editor.update(cx, |editor, window, cx| {
499 editor.end_selection(window, cx);
500 });
501
502 assert_eq!(
503 editor
504 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
505 .unwrap(),
506 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
507 );
508}
509
510#[gpui::test]
511fn test_canceling_pending_selection(cx: &mut TestAppContext) {
512 init_test(cx, |_| {});
513
514 let editor = cx.add_window(|window, cx| {
515 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
516 build_editor(buffer, window, cx)
517 });
518
519 _ = editor.update(cx, |editor, window, cx| {
520 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
521 assert_eq!(
522 editor.selections.display_ranges(cx),
523 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
524 );
525 });
526
527 _ = editor.update(cx, |editor, window, cx| {
528 editor.update_selection(
529 DisplayPoint::new(DisplayRow(3), 3),
530 0,
531 gpui::Point::<f32>::default(),
532 window,
533 cx,
534 );
535 assert_eq!(
536 editor.selections.display_ranges(cx),
537 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
538 );
539 });
540
541 _ = editor.update(cx, |editor, window, cx| {
542 editor.cancel(&Cancel, window, cx);
543 editor.update_selection(
544 DisplayPoint::new(DisplayRow(1), 1),
545 0,
546 gpui::Point::<f32>::default(),
547 window,
548 cx,
549 );
550 assert_eq!(
551 editor.selections.display_ranges(cx),
552 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
553 );
554 });
555}
556
557#[gpui::test]
558fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
559 init_test(cx, |_| {});
560
561 let editor = cx.add_window(|window, cx| {
562 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
563 build_editor(buffer, window, cx)
564 });
565
566 _ = editor.update(cx, |editor, window, cx| {
567 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
568 assert_eq!(
569 editor.selections.display_ranges(cx),
570 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
571 );
572
573 editor.move_down(&Default::default(), window, cx);
574 assert_eq!(
575 editor.selections.display_ranges(cx),
576 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
577 );
578
579 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
580 assert_eq!(
581 editor.selections.display_ranges(cx),
582 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
583 );
584
585 editor.move_up(&Default::default(), window, cx);
586 assert_eq!(
587 editor.selections.display_ranges(cx),
588 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
589 );
590 });
591}
592
593#[gpui::test]
594fn test_clone(cx: &mut TestAppContext) {
595 init_test(cx, |_| {});
596
597 let (text, selection_ranges) = marked_text_ranges(
598 indoc! {"
599 one
600 two
601 threeˇ
602 four
603 fiveˇ
604 "},
605 true,
606 );
607
608 let editor = cx.add_window(|window, cx| {
609 let buffer = MultiBuffer::build_simple(&text, cx);
610 build_editor(buffer, window, cx)
611 });
612
613 _ = editor.update(cx, |editor, window, cx| {
614 editor.change_selections(None, window, cx, |s| {
615 s.select_ranges(selection_ranges.clone())
616 });
617 editor.fold_creases(
618 vec![
619 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
620 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
621 ],
622 true,
623 window,
624 cx,
625 );
626 });
627
628 let cloned_editor = editor
629 .update(cx, |editor, _, cx| {
630 cx.open_window(Default::default(), |window, cx| {
631 cx.new(|cx| editor.clone(window, cx))
632 })
633 })
634 .unwrap()
635 .unwrap();
636
637 let snapshot = editor
638 .update(cx, |e, window, cx| e.snapshot(window, cx))
639 .unwrap();
640 let cloned_snapshot = cloned_editor
641 .update(cx, |e, window, cx| e.snapshot(window, cx))
642 .unwrap();
643
644 assert_eq!(
645 cloned_editor
646 .update(cx, |e, _, cx| e.display_text(cx))
647 .unwrap(),
648 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
649 );
650 assert_eq!(
651 cloned_snapshot
652 .folds_in_range(0..text.len())
653 .collect::<Vec<_>>(),
654 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
655 );
656 assert_set_eq!(
657 cloned_editor
658 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
659 .unwrap(),
660 editor
661 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
662 .unwrap()
663 );
664 assert_set_eq!(
665 cloned_editor
666 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
667 .unwrap(),
668 editor
669 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
670 .unwrap()
671 );
672}
673
674#[gpui::test]
675async fn test_navigation_history(cx: &mut TestAppContext) {
676 init_test(cx, |_| {});
677
678 use workspace::item::Item;
679
680 let fs = FakeFs::new(cx.executor());
681 let project = Project::test(fs, [], cx).await;
682 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
683 let pane = workspace
684 .update(cx, |workspace, _, _| workspace.active_pane().clone())
685 .unwrap();
686
687 _ = workspace.update(cx, |_v, window, cx| {
688 cx.new(|cx| {
689 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
690 let mut editor = build_editor(buffer.clone(), window, cx);
691 let handle = cx.entity();
692 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
693
694 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
695 editor.nav_history.as_mut().unwrap().pop_backward(cx)
696 }
697
698 // Move the cursor a small distance.
699 // Nothing is added to the navigation history.
700 editor.change_selections(None, window, cx, |s| {
701 s.select_display_ranges([
702 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
703 ])
704 });
705 editor.change_selections(None, window, cx, |s| {
706 s.select_display_ranges([
707 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
708 ])
709 });
710 assert!(pop_history(&mut editor, cx).is_none());
711
712 // Move the cursor a large distance.
713 // The history can jump back to the previous position.
714 editor.change_selections(None, window, cx, |s| {
715 s.select_display_ranges([
716 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
717 ])
718 });
719 let nav_entry = pop_history(&mut editor, cx).unwrap();
720 editor.navigate(nav_entry.data.unwrap(), window, cx);
721 assert_eq!(nav_entry.item.id(), cx.entity_id());
722 assert_eq!(
723 editor.selections.display_ranges(cx),
724 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
725 );
726 assert!(pop_history(&mut editor, cx).is_none());
727
728 // Move the cursor a small distance via the mouse.
729 // Nothing is added to the navigation history.
730 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
731 editor.end_selection(window, cx);
732 assert_eq!(
733 editor.selections.display_ranges(cx),
734 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
735 );
736 assert!(pop_history(&mut editor, cx).is_none());
737
738 // Move the cursor a large distance via the mouse.
739 // The history can jump back to the previous position.
740 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
741 editor.end_selection(window, cx);
742 assert_eq!(
743 editor.selections.display_ranges(cx),
744 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
745 );
746 let nav_entry = pop_history(&mut editor, cx).unwrap();
747 editor.navigate(nav_entry.data.unwrap(), window, cx);
748 assert_eq!(nav_entry.item.id(), cx.entity_id());
749 assert_eq!(
750 editor.selections.display_ranges(cx),
751 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
752 );
753 assert!(pop_history(&mut editor, cx).is_none());
754
755 // Set scroll position to check later
756 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
757 let original_scroll_position = editor.scroll_manager.anchor();
758
759 // Jump to the end of the document and adjust scroll
760 editor.move_to_end(&MoveToEnd, window, cx);
761 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
762 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
763
764 let nav_entry = pop_history(&mut editor, cx).unwrap();
765 editor.navigate(nav_entry.data.unwrap(), window, cx);
766 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
767
768 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
769 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
770 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
771 let invalid_point = Point::new(9999, 0);
772 editor.navigate(
773 Box::new(NavigationData {
774 cursor_anchor: invalid_anchor,
775 cursor_position: invalid_point,
776 scroll_anchor: ScrollAnchor {
777 anchor: invalid_anchor,
778 offset: Default::default(),
779 },
780 scroll_top_row: invalid_point.row,
781 }),
782 window,
783 cx,
784 );
785 assert_eq!(
786 editor.selections.display_ranges(cx),
787 &[editor.max_point(cx)..editor.max_point(cx)]
788 );
789 assert_eq!(
790 editor.scroll_position(cx),
791 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
792 );
793
794 editor
795 })
796 });
797}
798
799#[gpui::test]
800fn test_cancel(cx: &mut TestAppContext) {
801 init_test(cx, |_| {});
802
803 let editor = cx.add_window(|window, cx| {
804 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
805 build_editor(buffer, window, cx)
806 });
807
808 _ = editor.update(cx, |editor, window, cx| {
809 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
810 editor.update_selection(
811 DisplayPoint::new(DisplayRow(1), 1),
812 0,
813 gpui::Point::<f32>::default(),
814 window,
815 cx,
816 );
817 editor.end_selection(window, cx);
818
819 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
820 editor.update_selection(
821 DisplayPoint::new(DisplayRow(0), 3),
822 0,
823 gpui::Point::<f32>::default(),
824 window,
825 cx,
826 );
827 editor.end_selection(window, cx);
828 assert_eq!(
829 editor.selections.display_ranges(cx),
830 [
831 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
832 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
833 ]
834 );
835 });
836
837 _ = editor.update(cx, |editor, window, cx| {
838 editor.cancel(&Cancel, window, cx);
839 assert_eq!(
840 editor.selections.display_ranges(cx),
841 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
842 );
843 });
844
845 _ = editor.update(cx, |editor, window, cx| {
846 editor.cancel(&Cancel, window, cx);
847 assert_eq!(
848 editor.selections.display_ranges(cx),
849 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
850 );
851 });
852}
853
854#[gpui::test]
855fn test_fold_action(cx: &mut TestAppContext) {
856 init_test(cx, |_| {});
857
858 let editor = cx.add_window(|window, cx| {
859 let buffer = MultiBuffer::build_simple(
860 &"
861 impl Foo {
862 // Hello!
863
864 fn a() {
865 1
866 }
867
868 fn b() {
869 2
870 }
871
872 fn c() {
873 3
874 }
875 }
876 "
877 .unindent(),
878 cx,
879 );
880 build_editor(buffer.clone(), window, cx)
881 });
882
883 _ = editor.update(cx, |editor, window, cx| {
884 editor.change_selections(None, window, cx, |s| {
885 s.select_display_ranges([
886 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
887 ]);
888 });
889 editor.fold(&Fold, window, cx);
890 assert_eq!(
891 editor.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 editor.fold(&Fold, window, cx);
911 assert_eq!(
912 editor.display_text(cx),
913 "
914 impl Foo {⋯
915 }
916 "
917 .unindent(),
918 );
919
920 editor.unfold_lines(&UnfoldLines, window, cx);
921 assert_eq!(
922 editor.display_text(cx),
923 "
924 impl Foo {
925 // Hello!
926
927 fn a() {
928 1
929 }
930
931 fn b() {⋯
932 }
933
934 fn c() {⋯
935 }
936 }
937 "
938 .unindent(),
939 );
940
941 editor.unfold_lines(&UnfoldLines, window, cx);
942 assert_eq!(
943 editor.display_text(cx),
944 editor.buffer.read(cx).read(cx).text()
945 );
946 });
947}
948
949#[gpui::test]
950fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
951 init_test(cx, |_| {});
952
953 let editor = cx.add_window(|window, cx| {
954 let buffer = MultiBuffer::build_simple(
955 &"
956 class Foo:
957 # Hello!
958
959 def a():
960 print(1)
961
962 def b():
963 print(2)
964
965 def c():
966 print(3)
967 "
968 .unindent(),
969 cx,
970 );
971 build_editor(buffer.clone(), window, cx)
972 });
973
974 _ = editor.update(cx, |editor, window, cx| {
975 editor.change_selections(None, window, cx, |s| {
976 s.select_display_ranges([
977 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
978 ]);
979 });
980 editor.fold(&Fold, window, cx);
981 assert_eq!(
982 editor.display_text(cx),
983 "
984 class Foo:
985 # Hello!
986
987 def a():
988 print(1)
989
990 def b():⋯
991
992 def c():⋯
993 "
994 .unindent(),
995 );
996
997 editor.fold(&Fold, window, cx);
998 assert_eq!(
999 editor.display_text(cx),
1000 "
1001 class Foo:⋯
1002 "
1003 .unindent(),
1004 );
1005
1006 editor.unfold_lines(&UnfoldLines, window, cx);
1007 assert_eq!(
1008 editor.display_text(cx),
1009 "
1010 class Foo:
1011 # Hello!
1012
1013 def a():
1014 print(1)
1015
1016 def b():⋯
1017
1018 def c():⋯
1019 "
1020 .unindent(),
1021 );
1022
1023 editor.unfold_lines(&UnfoldLines, window, cx);
1024 assert_eq!(
1025 editor.display_text(cx),
1026 editor.buffer.read(cx).read(cx).text()
1027 );
1028 });
1029}
1030
1031#[gpui::test]
1032fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1033 init_test(cx, |_| {});
1034
1035 let editor = cx.add_window(|window, cx| {
1036 let buffer = MultiBuffer::build_simple(
1037 &"
1038 class Foo:
1039 # Hello!
1040
1041 def a():
1042 print(1)
1043
1044 def b():
1045 print(2)
1046
1047
1048 def c():
1049 print(3)
1050
1051
1052 "
1053 .unindent(),
1054 cx,
1055 );
1056 build_editor(buffer.clone(), window, cx)
1057 });
1058
1059 _ = editor.update(cx, |editor, window, cx| {
1060 editor.change_selections(None, window, cx, |s| {
1061 s.select_display_ranges([
1062 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1063 ]);
1064 });
1065 editor.fold(&Fold, window, cx);
1066 assert_eq!(
1067 editor.display_text(cx),
1068 "
1069 class Foo:
1070 # Hello!
1071
1072 def a():
1073 print(1)
1074
1075 def b():⋯
1076
1077
1078 def c():⋯
1079
1080
1081 "
1082 .unindent(),
1083 );
1084
1085 editor.fold(&Fold, window, cx);
1086 assert_eq!(
1087 editor.display_text(cx),
1088 "
1089 class Foo:⋯
1090
1091
1092 "
1093 .unindent(),
1094 );
1095
1096 editor.unfold_lines(&UnfoldLines, window, cx);
1097 assert_eq!(
1098 editor.display_text(cx),
1099 "
1100 class Foo:
1101 # Hello!
1102
1103 def a():
1104 print(1)
1105
1106 def b():⋯
1107
1108
1109 def c():⋯
1110
1111
1112 "
1113 .unindent(),
1114 );
1115
1116 editor.unfold_lines(&UnfoldLines, window, cx);
1117 assert_eq!(
1118 editor.display_text(cx),
1119 editor.buffer.read(cx).read(cx).text()
1120 );
1121 });
1122}
1123
1124#[gpui::test]
1125fn test_fold_at_level(cx: &mut TestAppContext) {
1126 init_test(cx, |_| {});
1127
1128 let editor = cx.add_window(|window, cx| {
1129 let buffer = MultiBuffer::build_simple(
1130 &"
1131 class Foo:
1132 # Hello!
1133
1134 def a():
1135 print(1)
1136
1137 def b():
1138 print(2)
1139
1140
1141 class Bar:
1142 # World!
1143
1144 def a():
1145 print(1)
1146
1147 def b():
1148 print(2)
1149
1150
1151 "
1152 .unindent(),
1153 cx,
1154 );
1155 build_editor(buffer.clone(), window, cx)
1156 });
1157
1158 _ = editor.update(cx, |editor, window, cx| {
1159 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1160 assert_eq!(
1161 editor.display_text(cx),
1162 "
1163 class Foo:
1164 # Hello!
1165
1166 def a():⋯
1167
1168 def b():⋯
1169
1170
1171 class Bar:
1172 # World!
1173
1174 def a():⋯
1175
1176 def b():⋯
1177
1178
1179 "
1180 .unindent(),
1181 );
1182
1183 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1184 assert_eq!(
1185 editor.display_text(cx),
1186 "
1187 class Foo:⋯
1188
1189
1190 class Bar:⋯
1191
1192
1193 "
1194 .unindent(),
1195 );
1196
1197 editor.unfold_all(&UnfoldAll, window, cx);
1198 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1199 assert_eq!(
1200 editor.display_text(cx),
1201 "
1202 class Foo:
1203 # Hello!
1204
1205 def a():
1206 print(1)
1207
1208 def b():
1209 print(2)
1210
1211
1212 class Bar:
1213 # World!
1214
1215 def a():
1216 print(1)
1217
1218 def b():
1219 print(2)
1220
1221
1222 "
1223 .unindent(),
1224 );
1225
1226 assert_eq!(
1227 editor.display_text(cx),
1228 editor.buffer.read(cx).read(cx).text()
1229 );
1230 });
1231}
1232
1233#[gpui::test]
1234fn test_move_cursor(cx: &mut TestAppContext) {
1235 init_test(cx, |_| {});
1236
1237 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1238 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1239
1240 buffer.update(cx, |buffer, cx| {
1241 buffer.edit(
1242 vec![
1243 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1244 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1245 ],
1246 None,
1247 cx,
1248 );
1249 });
1250 _ = editor.update(cx, |editor, window, cx| {
1251 assert_eq!(
1252 editor.selections.display_ranges(cx),
1253 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1254 );
1255
1256 editor.move_down(&MoveDown, window, cx);
1257 assert_eq!(
1258 editor.selections.display_ranges(cx),
1259 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1260 );
1261
1262 editor.move_right(&MoveRight, window, cx);
1263 assert_eq!(
1264 editor.selections.display_ranges(cx),
1265 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1266 );
1267
1268 editor.move_left(&MoveLeft, window, cx);
1269 assert_eq!(
1270 editor.selections.display_ranges(cx),
1271 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1272 );
1273
1274 editor.move_up(&MoveUp, window, cx);
1275 assert_eq!(
1276 editor.selections.display_ranges(cx),
1277 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1278 );
1279
1280 editor.move_to_end(&MoveToEnd, window, cx);
1281 assert_eq!(
1282 editor.selections.display_ranges(cx),
1283 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1284 );
1285
1286 editor.move_to_beginning(&MoveToBeginning, window, cx);
1287 assert_eq!(
1288 editor.selections.display_ranges(cx),
1289 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1290 );
1291
1292 editor.change_selections(None, window, cx, |s| {
1293 s.select_display_ranges([
1294 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1295 ]);
1296 });
1297 editor.select_to_beginning(&SelectToBeginning, window, cx);
1298 assert_eq!(
1299 editor.selections.display_ranges(cx),
1300 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1301 );
1302
1303 editor.select_to_end(&SelectToEnd, window, cx);
1304 assert_eq!(
1305 editor.selections.display_ranges(cx),
1306 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1307 );
1308 });
1309}
1310
1311// TODO: Re-enable this test
1312#[cfg(target_os = "macos")]
1313#[gpui::test]
1314fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1315 init_test(cx, |_| {});
1316
1317 let editor = cx.add_window(|window, cx| {
1318 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1319 build_editor(buffer.clone(), window, cx)
1320 });
1321
1322 assert_eq!('🟥'.len_utf8(), 4);
1323 assert_eq!('α'.len_utf8(), 2);
1324
1325 _ = editor.update(cx, |editor, window, cx| {
1326 editor.fold_creases(
1327 vec![
1328 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1329 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1330 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1331 ],
1332 true,
1333 window,
1334 cx,
1335 );
1336 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1337
1338 editor.move_right(&MoveRight, window, cx);
1339 assert_eq!(
1340 editor.selections.display_ranges(cx),
1341 &[empty_range(0, "🟥".len())]
1342 );
1343 editor.move_right(&MoveRight, window, cx);
1344 assert_eq!(
1345 editor.selections.display_ranges(cx),
1346 &[empty_range(0, "🟥🟧".len())]
1347 );
1348 editor.move_right(&MoveRight, window, cx);
1349 assert_eq!(
1350 editor.selections.display_ranges(cx),
1351 &[empty_range(0, "🟥🟧⋯".len())]
1352 );
1353
1354 editor.move_down(&MoveDown, window, cx);
1355 assert_eq!(
1356 editor.selections.display_ranges(cx),
1357 &[empty_range(1, "ab⋯e".len())]
1358 );
1359 editor.move_left(&MoveLeft, window, cx);
1360 assert_eq!(
1361 editor.selections.display_ranges(cx),
1362 &[empty_range(1, "ab⋯".len())]
1363 );
1364 editor.move_left(&MoveLeft, window, cx);
1365 assert_eq!(
1366 editor.selections.display_ranges(cx),
1367 &[empty_range(1, "ab".len())]
1368 );
1369 editor.move_left(&MoveLeft, window, cx);
1370 assert_eq!(
1371 editor.selections.display_ranges(cx),
1372 &[empty_range(1, "a".len())]
1373 );
1374
1375 editor.move_down(&MoveDown, window, cx);
1376 assert_eq!(
1377 editor.selections.display_ranges(cx),
1378 &[empty_range(2, "α".len())]
1379 );
1380 editor.move_right(&MoveRight, window, cx);
1381 assert_eq!(
1382 editor.selections.display_ranges(cx),
1383 &[empty_range(2, "αβ".len())]
1384 );
1385 editor.move_right(&MoveRight, window, cx);
1386 assert_eq!(
1387 editor.selections.display_ranges(cx),
1388 &[empty_range(2, "αβ⋯".len())]
1389 );
1390 editor.move_right(&MoveRight, window, cx);
1391 assert_eq!(
1392 editor.selections.display_ranges(cx),
1393 &[empty_range(2, "αβ⋯ε".len())]
1394 );
1395
1396 editor.move_up(&MoveUp, window, cx);
1397 assert_eq!(
1398 editor.selections.display_ranges(cx),
1399 &[empty_range(1, "ab⋯e".len())]
1400 );
1401 editor.move_down(&MoveDown, window, cx);
1402 assert_eq!(
1403 editor.selections.display_ranges(cx),
1404 &[empty_range(2, "αβ⋯ε".len())]
1405 );
1406 editor.move_up(&MoveUp, window, cx);
1407 assert_eq!(
1408 editor.selections.display_ranges(cx),
1409 &[empty_range(1, "ab⋯e".len())]
1410 );
1411
1412 editor.move_up(&MoveUp, window, cx);
1413 assert_eq!(
1414 editor.selections.display_ranges(cx),
1415 &[empty_range(0, "🟥🟧".len())]
1416 );
1417 editor.move_left(&MoveLeft, window, cx);
1418 assert_eq!(
1419 editor.selections.display_ranges(cx),
1420 &[empty_range(0, "🟥".len())]
1421 );
1422 editor.move_left(&MoveLeft, window, cx);
1423 assert_eq!(
1424 editor.selections.display_ranges(cx),
1425 &[empty_range(0, "".len())]
1426 );
1427 });
1428}
1429
1430#[gpui::test]
1431fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1432 init_test(cx, |_| {});
1433
1434 let editor = cx.add_window(|window, cx| {
1435 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1436 build_editor(buffer.clone(), window, cx)
1437 });
1438 _ = editor.update(cx, |editor, window, cx| {
1439 editor.change_selections(None, window, cx, |s| {
1440 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1441 });
1442
1443 // moving above start of document should move selection to start of document,
1444 // but the next move down should still be at the original goal_x
1445 editor.move_up(&MoveUp, window, cx);
1446 assert_eq!(
1447 editor.selections.display_ranges(cx),
1448 &[empty_range(0, "".len())]
1449 );
1450
1451 editor.move_down(&MoveDown, window, cx);
1452 assert_eq!(
1453 editor.selections.display_ranges(cx),
1454 &[empty_range(1, "abcd".len())]
1455 );
1456
1457 editor.move_down(&MoveDown, window, cx);
1458 assert_eq!(
1459 editor.selections.display_ranges(cx),
1460 &[empty_range(2, "αβγ".len())]
1461 );
1462
1463 editor.move_down(&MoveDown, window, cx);
1464 assert_eq!(
1465 editor.selections.display_ranges(cx),
1466 &[empty_range(3, "abcd".len())]
1467 );
1468
1469 editor.move_down(&MoveDown, window, cx);
1470 assert_eq!(
1471 editor.selections.display_ranges(cx),
1472 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1473 );
1474
1475 // moving past end of document should not change goal_x
1476 editor.move_down(&MoveDown, window, cx);
1477 assert_eq!(
1478 editor.selections.display_ranges(cx),
1479 &[empty_range(5, "".len())]
1480 );
1481
1482 editor.move_down(&MoveDown, window, cx);
1483 assert_eq!(
1484 editor.selections.display_ranges(cx),
1485 &[empty_range(5, "".len())]
1486 );
1487
1488 editor.move_up(&MoveUp, window, cx);
1489 assert_eq!(
1490 editor.selections.display_ranges(cx),
1491 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1492 );
1493
1494 editor.move_up(&MoveUp, window, cx);
1495 assert_eq!(
1496 editor.selections.display_ranges(cx),
1497 &[empty_range(3, "abcd".len())]
1498 );
1499
1500 editor.move_up(&MoveUp, window, cx);
1501 assert_eq!(
1502 editor.selections.display_ranges(cx),
1503 &[empty_range(2, "αβγ".len())]
1504 );
1505 });
1506}
1507
1508#[gpui::test]
1509fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1510 init_test(cx, |_| {});
1511 let move_to_beg = MoveToBeginningOfLine {
1512 stop_at_soft_wraps: true,
1513 };
1514
1515 let move_to_end = MoveToEndOfLine {
1516 stop_at_soft_wraps: true,
1517 };
1518
1519 let editor = cx.add_window(|window, cx| {
1520 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1521 build_editor(buffer, window, cx)
1522 });
1523 _ = editor.update(cx, |editor, window, cx| {
1524 editor.change_selections(None, window, cx, |s| {
1525 s.select_display_ranges([
1526 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1527 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1528 ]);
1529 });
1530 });
1531
1532 _ = editor.update(cx, |editor, window, cx| {
1533 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1534 assert_eq!(
1535 editor.selections.display_ranges(cx),
1536 &[
1537 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1538 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1539 ]
1540 );
1541 });
1542
1543 _ = editor.update(cx, |editor, window, cx| {
1544 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1545 assert_eq!(
1546 editor.selections.display_ranges(cx),
1547 &[
1548 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1549 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1550 ]
1551 );
1552 });
1553
1554 _ = editor.update(cx, |editor, window, cx| {
1555 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1556 assert_eq!(
1557 editor.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1561 ]
1562 );
1563 });
1564
1565 _ = editor.update(cx, |editor, window, cx| {
1566 editor.move_to_end_of_line(&move_to_end, window, cx);
1567 assert_eq!(
1568 editor.selections.display_ranges(cx),
1569 &[
1570 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1571 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1572 ]
1573 );
1574 });
1575
1576 // Moving to the end of line again is a no-op.
1577 _ = editor.update(cx, |editor, window, cx| {
1578 editor.move_to_end_of_line(&move_to_end, window, cx);
1579 assert_eq!(
1580 editor.selections.display_ranges(cx),
1581 &[
1582 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1583 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1584 ]
1585 );
1586 });
1587
1588 _ = editor.update(cx, |editor, window, cx| {
1589 editor.move_left(&MoveLeft, window, cx);
1590 editor.select_to_beginning_of_line(
1591 &SelectToBeginningOfLine {
1592 stop_at_soft_wraps: true,
1593 },
1594 window,
1595 cx,
1596 );
1597 assert_eq!(
1598 editor.selections.display_ranges(cx),
1599 &[
1600 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1601 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1602 ]
1603 );
1604 });
1605
1606 _ = editor.update(cx, |editor, window, cx| {
1607 editor.select_to_beginning_of_line(
1608 &SelectToBeginningOfLine {
1609 stop_at_soft_wraps: true,
1610 },
1611 window,
1612 cx,
1613 );
1614 assert_eq!(
1615 editor.selections.display_ranges(cx),
1616 &[
1617 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1618 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1619 ]
1620 );
1621 });
1622
1623 _ = editor.update(cx, |editor, window, cx| {
1624 editor.select_to_beginning_of_line(
1625 &SelectToBeginningOfLine {
1626 stop_at_soft_wraps: true,
1627 },
1628 window,
1629 cx,
1630 );
1631 assert_eq!(
1632 editor.selections.display_ranges(cx),
1633 &[
1634 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1635 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1636 ]
1637 );
1638 });
1639
1640 _ = editor.update(cx, |editor, window, cx| {
1641 editor.select_to_end_of_line(
1642 &SelectToEndOfLine {
1643 stop_at_soft_wraps: true,
1644 },
1645 window,
1646 cx,
1647 );
1648 assert_eq!(
1649 editor.selections.display_ranges(cx),
1650 &[
1651 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1652 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1653 ]
1654 );
1655 });
1656
1657 _ = editor.update(cx, |editor, window, cx| {
1658 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1659 assert_eq!(editor.display_text(cx), "ab\n de");
1660 assert_eq!(
1661 editor.selections.display_ranges(cx),
1662 &[
1663 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1664 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1665 ]
1666 );
1667 });
1668
1669 _ = editor.update(cx, |editor, window, cx| {
1670 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
1671 assert_eq!(editor.display_text(cx), "\n");
1672 assert_eq!(
1673 editor.selections.display_ranges(cx),
1674 &[
1675 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1676 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1677 ]
1678 );
1679 });
1680}
1681
1682#[gpui::test]
1683fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1684 init_test(cx, |_| {});
1685 let move_to_beg = MoveToBeginningOfLine {
1686 stop_at_soft_wraps: false,
1687 };
1688
1689 let move_to_end = MoveToEndOfLine {
1690 stop_at_soft_wraps: false,
1691 };
1692
1693 let editor = cx.add_window(|window, cx| {
1694 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1695 build_editor(buffer, window, cx)
1696 });
1697
1698 _ = editor.update(cx, |editor, window, cx| {
1699 editor.set_wrap_width(Some(140.0.into()), cx);
1700
1701 // We expect the following lines after wrapping
1702 // ```
1703 // thequickbrownfox
1704 // jumpedoverthelazydo
1705 // gs
1706 // ```
1707 // The final `gs` was soft-wrapped onto a new line.
1708 assert_eq!(
1709 "thequickbrownfox\njumpedoverthelaz\nydogs",
1710 editor.display_text(cx),
1711 );
1712
1713 // First, let's assert behavior on the first line, that was not soft-wrapped.
1714 // Start the cursor at the `k` on the first line
1715 editor.change_selections(None, window, cx, |s| {
1716 s.select_display_ranges([
1717 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1718 ]);
1719 });
1720
1721 // Moving to the beginning of the line should put us at the beginning of the line.
1722 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1723 assert_eq!(
1724 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1725 editor.selections.display_ranges(cx)
1726 );
1727
1728 // Moving to the end of the line should put us at the end of the line.
1729 editor.move_to_end_of_line(&move_to_end, window, cx);
1730 assert_eq!(
1731 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1732 editor.selections.display_ranges(cx)
1733 );
1734
1735 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1736 // Start the cursor at the last line (`y` that was wrapped to a new line)
1737 editor.change_selections(None, window, cx, |s| {
1738 s.select_display_ranges([
1739 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1740 ]);
1741 });
1742
1743 // Moving to the beginning of the line should put us at the start of the second line of
1744 // display text, i.e., the `j`.
1745 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1746 assert_eq!(
1747 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1748 editor.selections.display_ranges(cx)
1749 );
1750
1751 // Moving to the beginning of the line again should be a no-op.
1752 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1753 assert_eq!(
1754 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1755 editor.selections.display_ranges(cx)
1756 );
1757
1758 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1759 // next display line.
1760 editor.move_to_end_of_line(&move_to_end, window, cx);
1761 assert_eq!(
1762 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1763 editor.selections.display_ranges(cx)
1764 );
1765
1766 // Moving to the end of the line again should be a no-op.
1767 editor.move_to_end_of_line(&move_to_end, window, cx);
1768 assert_eq!(
1769 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1770 editor.selections.display_ranges(cx)
1771 );
1772 });
1773}
1774
1775#[gpui::test]
1776fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1777 init_test(cx, |_| {});
1778
1779 let editor = cx.add_window(|window, cx| {
1780 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1781 build_editor(buffer, window, cx)
1782 });
1783 _ = editor.update(cx, |editor, window, cx| {
1784 editor.change_selections(None, window, cx, |s| {
1785 s.select_display_ranges([
1786 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1787 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1788 ])
1789 });
1790
1791 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1792 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1793
1794 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1795 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
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_next_word_end(&MoveToNextWordEnd, window, cx);
1807 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1808
1809 editor.move_to_next_word_end(&MoveToNextWordEnd, 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_right(&MoveRight, window, cx);
1816 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1817 assert_selection_ranges(
1818 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1819 editor,
1820 cx,
1821 );
1822
1823 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1824 assert_selection_ranges(
1825 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1826 editor,
1827 cx,
1828 );
1829
1830 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1831 assert_selection_ranges(
1832 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1833 editor,
1834 cx,
1835 );
1836 });
1837}
1838
1839#[gpui::test]
1840fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1841 init_test(cx, |_| {});
1842
1843 let editor = cx.add_window(|window, cx| {
1844 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1845 build_editor(buffer, window, cx)
1846 });
1847
1848 _ = editor.update(cx, |editor, window, cx| {
1849 editor.set_wrap_width(Some(140.0.into()), cx);
1850 assert_eq!(
1851 editor.display_text(cx),
1852 "use one::{\n two::three::\n four::five\n};"
1853 );
1854
1855 editor.change_selections(None, window, cx, |s| {
1856 s.select_display_ranges([
1857 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1858 ]);
1859 });
1860
1861 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1862 assert_eq!(
1863 editor.selections.display_ranges(cx),
1864 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
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), 14)..DisplayPoint::new(DisplayRow(1), 14)]
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(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
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), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1883 );
1884
1885 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1886 assert_eq!(
1887 editor.selections.display_ranges(cx),
1888 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
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(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1895 );
1896 });
1897}
1898
1899#[gpui::test]
1900async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1901 init_test(cx, |_| {});
1902 let mut cx = EditorTestContext::new(cx).await;
1903
1904 let line_height = cx.editor(|editor, window, _| {
1905 editor
1906 .style()
1907 .unwrap()
1908 .text
1909 .line_height_in_pixels(window.rem_size())
1910 });
1911 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1912
1913 cx.set_state(
1914 &r#"ˇone
1915 two
1916
1917 three
1918 fourˇ
1919 five
1920
1921 six"#
1922 .unindent(),
1923 );
1924
1925 cx.update_editor(|editor, window, cx| {
1926 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1927 });
1928 cx.assert_editor_state(
1929 &r#"one
1930 two
1931 ˇ
1932 three
1933 four
1934 five
1935 ˇ
1936 six"#
1937 .unindent(),
1938 );
1939
1940 cx.update_editor(|editor, window, cx| {
1941 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1942 });
1943 cx.assert_editor_state(
1944 &r#"one
1945 two
1946
1947 three
1948 four
1949 five
1950 ˇ
1951 sixˇ"#
1952 .unindent(),
1953 );
1954
1955 cx.update_editor(|editor, window, cx| {
1956 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1957 });
1958 cx.assert_editor_state(
1959 &r#"one
1960 two
1961
1962 three
1963 four
1964 five
1965
1966 sixˇ"#
1967 .unindent(),
1968 );
1969
1970 cx.update_editor(|editor, window, cx| {
1971 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1972 });
1973 cx.assert_editor_state(
1974 &r#"one
1975 two
1976
1977 three
1978 four
1979 five
1980 ˇ
1981 six"#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, window, cx| {
1986 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1987 });
1988 cx.assert_editor_state(
1989 &r#"one
1990 two
1991 ˇ
1992 three
1993 four
1994 five
1995
1996 six"#
1997 .unindent(),
1998 );
1999
2000 cx.update_editor(|editor, window, cx| {
2001 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2002 });
2003 cx.assert_editor_state(
2004 &r#"ˇone
2005 two
2006
2007 three
2008 four
2009 five
2010
2011 six"#
2012 .unindent(),
2013 );
2014}
2015
2016#[gpui::test]
2017async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
2018 init_test(cx, |_| {});
2019 let mut cx = EditorTestContext::new(cx).await;
2020 let line_height = cx.editor(|editor, window, _| {
2021 editor
2022 .style()
2023 .unwrap()
2024 .text
2025 .line_height_in_pixels(window.rem_size())
2026 });
2027 let window = cx.window;
2028 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2029
2030 cx.set_state(
2031 r#"ˇone
2032 two
2033 three
2034 four
2035 five
2036 six
2037 seven
2038 eight
2039 nine
2040 ten
2041 "#,
2042 );
2043
2044 cx.update_editor(|editor, window, cx| {
2045 assert_eq!(
2046 editor.snapshot(window, cx).scroll_position(),
2047 gpui::Point::new(0., 0.)
2048 );
2049 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2050 assert_eq!(
2051 editor.snapshot(window, cx).scroll_position(),
2052 gpui::Point::new(0., 3.)
2053 );
2054 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2055 assert_eq!(
2056 editor.snapshot(window, cx).scroll_position(),
2057 gpui::Point::new(0., 6.)
2058 );
2059 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2060 assert_eq!(
2061 editor.snapshot(window, cx).scroll_position(),
2062 gpui::Point::new(0., 3.)
2063 );
2064
2065 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2066 assert_eq!(
2067 editor.snapshot(window, cx).scroll_position(),
2068 gpui::Point::new(0., 1.)
2069 );
2070 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2071 assert_eq!(
2072 editor.snapshot(window, cx).scroll_position(),
2073 gpui::Point::new(0., 3.)
2074 );
2075 });
2076}
2077
2078#[gpui::test]
2079async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2080 init_test(cx, |_| {});
2081 let mut cx = EditorTestContext::new(cx).await;
2082
2083 let line_height = cx.update_editor(|editor, window, cx| {
2084 editor.set_vertical_scroll_margin(2, cx);
2085 editor
2086 .style()
2087 .unwrap()
2088 .text
2089 .line_height_in_pixels(window.rem_size())
2090 });
2091 let window = cx.window;
2092 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2093
2094 cx.set_state(
2095 r#"ˇone
2096 two
2097 three
2098 four
2099 five
2100 six
2101 seven
2102 eight
2103 nine
2104 ten
2105 "#,
2106 );
2107 cx.update_editor(|editor, window, cx| {
2108 assert_eq!(
2109 editor.snapshot(window, cx).scroll_position(),
2110 gpui::Point::new(0., 0.0)
2111 );
2112 });
2113
2114 // Add a cursor below the visible area. Since both cursors cannot fit
2115 // on screen, the editor autoscrolls to reveal the newest cursor, and
2116 // allows the vertical scroll margin below that cursor.
2117 cx.update_editor(|editor, window, cx| {
2118 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2119 selections.select_ranges([
2120 Point::new(0, 0)..Point::new(0, 0),
2121 Point::new(6, 0)..Point::new(6, 0),
2122 ]);
2123 })
2124 });
2125 cx.update_editor(|editor, window, cx| {
2126 assert_eq!(
2127 editor.snapshot(window, cx).scroll_position(),
2128 gpui::Point::new(0., 3.0)
2129 );
2130 });
2131
2132 // Move down. The editor cursor scrolls down to track the newest cursor.
2133 cx.update_editor(|editor, window, cx| {
2134 editor.move_down(&Default::default(), window, cx);
2135 });
2136 cx.update_editor(|editor, window, cx| {
2137 assert_eq!(
2138 editor.snapshot(window, cx).scroll_position(),
2139 gpui::Point::new(0., 4.0)
2140 );
2141 });
2142
2143 // Add a cursor above the visible area. Since both cursors fit on screen,
2144 // the editor scrolls to show both.
2145 cx.update_editor(|editor, window, cx| {
2146 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2147 selections.select_ranges([
2148 Point::new(1, 0)..Point::new(1, 0),
2149 Point::new(6, 0)..Point::new(6, 0),
2150 ]);
2151 })
2152 });
2153 cx.update_editor(|editor, window, cx| {
2154 assert_eq!(
2155 editor.snapshot(window, cx).scroll_position(),
2156 gpui::Point::new(0., 1.0)
2157 );
2158 });
2159}
2160
2161#[gpui::test]
2162async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2163 init_test(cx, |_| {});
2164 let mut cx = EditorTestContext::new(cx).await;
2165
2166 let line_height = cx.editor(|editor, window, _cx| {
2167 editor
2168 .style()
2169 .unwrap()
2170 .text
2171 .line_height_in_pixels(window.rem_size())
2172 });
2173 let window = cx.window;
2174 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2175 cx.set_state(
2176 &r#"
2177 ˇone
2178 two
2179 threeˇ
2180 four
2181 five
2182 six
2183 seven
2184 eight
2185 nine
2186 ten
2187 "#
2188 .unindent(),
2189 );
2190
2191 cx.update_editor(|editor, window, cx| {
2192 editor.move_page_down(&MovePageDown::default(), window, cx)
2193 });
2194 cx.assert_editor_state(
2195 &r#"
2196 one
2197 two
2198 three
2199 ˇfour
2200 five
2201 sixˇ
2202 seven
2203 eight
2204 nine
2205 ten
2206 "#
2207 .unindent(),
2208 );
2209
2210 cx.update_editor(|editor, window, cx| {
2211 editor.move_page_down(&MovePageDown::default(), window, cx)
2212 });
2213 cx.assert_editor_state(
2214 &r#"
2215 one
2216 two
2217 three
2218 four
2219 five
2220 six
2221 ˇseven
2222 eight
2223 nineˇ
2224 ten
2225 "#
2226 .unindent(),
2227 );
2228
2229 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2230 cx.assert_editor_state(
2231 &r#"
2232 one
2233 two
2234 three
2235 ˇfour
2236 five
2237 sixˇ
2238 seven
2239 eight
2240 nine
2241 ten
2242 "#
2243 .unindent(),
2244 );
2245
2246 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2247 cx.assert_editor_state(
2248 &r#"
2249 ˇone
2250 two
2251 threeˇ
2252 four
2253 five
2254 six
2255 seven
2256 eight
2257 nine
2258 ten
2259 "#
2260 .unindent(),
2261 );
2262
2263 // Test select collapsing
2264 cx.update_editor(|editor, window, cx| {
2265 editor.move_page_down(&MovePageDown::default(), window, cx);
2266 editor.move_page_down(&MovePageDown::default(), window, cx);
2267 editor.move_page_down(&MovePageDown::default(), window, cx);
2268 });
2269 cx.assert_editor_state(
2270 &r#"
2271 one
2272 two
2273 three
2274 four
2275 five
2276 six
2277 seven
2278 eight
2279 nine
2280 ˇten
2281 ˇ"#
2282 .unindent(),
2283 );
2284}
2285
2286#[gpui::test]
2287async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2288 init_test(cx, |_| {});
2289 let mut cx = EditorTestContext::new(cx).await;
2290 cx.set_state("one «two threeˇ» four");
2291 cx.update_editor(|editor, window, cx| {
2292 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
2293 assert_eq!(editor.text(cx), " four");
2294 });
2295}
2296
2297#[gpui::test]
2298fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2299 init_test(cx, |_| {});
2300
2301 let editor = cx.add_window(|window, cx| {
2302 let buffer = MultiBuffer::build_simple("one two three four", cx);
2303 build_editor(buffer.clone(), window, cx)
2304 });
2305
2306 _ = editor.update(cx, |editor, window, cx| {
2307 editor.change_selections(None, window, cx, |s| {
2308 s.select_display_ranges([
2309 // an empty selection - the preceding word fragment is deleted
2310 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2311 // characters selected - they are deleted
2312 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2313 ])
2314 });
2315 editor.delete_to_previous_word_start(
2316 &DeleteToPreviousWordStart {
2317 ignore_newlines: false,
2318 },
2319 window,
2320 cx,
2321 );
2322 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2323 });
2324
2325 _ = editor.update(cx, |editor, window, cx| {
2326 editor.change_selections(None, window, cx, |s| {
2327 s.select_display_ranges([
2328 // an empty selection - the following word fragment is deleted
2329 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2330 // characters selected - they are deleted
2331 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2332 ])
2333 });
2334 editor.delete_to_next_word_end(
2335 &DeleteToNextWordEnd {
2336 ignore_newlines: false,
2337 },
2338 window,
2339 cx,
2340 );
2341 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2342 });
2343}
2344
2345#[gpui::test]
2346fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2347 init_test(cx, |_| {});
2348
2349 let editor = cx.add_window(|window, cx| {
2350 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2351 build_editor(buffer.clone(), window, cx)
2352 });
2353 let del_to_prev_word_start = DeleteToPreviousWordStart {
2354 ignore_newlines: false,
2355 };
2356 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2357 ignore_newlines: true,
2358 };
2359
2360 _ = editor.update(cx, |editor, window, cx| {
2361 editor.change_selections(None, window, cx, |s| {
2362 s.select_display_ranges([
2363 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2364 ])
2365 });
2366 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2367 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2368 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2369 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2370 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2371 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
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");
2374 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2375 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2376 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2377 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2378 });
2379}
2380
2381#[gpui::test]
2382fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2383 init_test(cx, |_| {});
2384
2385 let editor = cx.add_window(|window, cx| {
2386 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2387 build_editor(buffer.clone(), window, cx)
2388 });
2389 let del_to_next_word_end = DeleteToNextWordEnd {
2390 ignore_newlines: false,
2391 };
2392 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2393 ignore_newlines: true,
2394 };
2395
2396 _ = editor.update(cx, |editor, window, cx| {
2397 editor.change_selections(None, window, cx, |s| {
2398 s.select_display_ranges([
2399 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2400 ])
2401 });
2402 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2403 assert_eq!(
2404 editor.buffer.read(cx).read(cx).text(),
2405 "one\n two\nthree\n four"
2406 );
2407 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2408 assert_eq!(
2409 editor.buffer.read(cx).read(cx).text(),
2410 "\n two\nthree\n four"
2411 );
2412 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2413 assert_eq!(
2414 editor.buffer.read(cx).read(cx).text(),
2415 "two\nthree\n four"
2416 );
2417 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2418 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2419 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2420 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2421 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2422 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2423 });
2424}
2425
2426#[gpui::test]
2427fn test_newline(cx: &mut TestAppContext) {
2428 init_test(cx, |_| {});
2429
2430 let editor = cx.add_window(|window, cx| {
2431 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2432 build_editor(buffer.clone(), window, cx)
2433 });
2434
2435 _ = editor.update(cx, |editor, window, cx| {
2436 editor.change_selections(None, window, cx, |s| {
2437 s.select_display_ranges([
2438 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2439 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2440 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2441 ])
2442 });
2443
2444 editor.newline(&Newline, window, cx);
2445 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2446 });
2447}
2448
2449#[gpui::test]
2450fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2451 init_test(cx, |_| {});
2452
2453 let editor = cx.add_window(|window, cx| {
2454 let buffer = MultiBuffer::build_simple(
2455 "
2456 a
2457 b(
2458 X
2459 )
2460 c(
2461 X
2462 )
2463 "
2464 .unindent()
2465 .as_str(),
2466 cx,
2467 );
2468 let mut editor = build_editor(buffer.clone(), window, cx);
2469 editor.change_selections(None, window, cx, |s| {
2470 s.select_ranges([
2471 Point::new(2, 4)..Point::new(2, 5),
2472 Point::new(5, 4)..Point::new(5, 5),
2473 ])
2474 });
2475 editor
2476 });
2477
2478 _ = editor.update(cx, |editor, window, cx| {
2479 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2480 editor.buffer.update(cx, |buffer, cx| {
2481 buffer.edit(
2482 [
2483 (Point::new(1, 2)..Point::new(3, 0), ""),
2484 (Point::new(4, 2)..Point::new(6, 0), ""),
2485 ],
2486 None,
2487 cx,
2488 );
2489 assert_eq!(
2490 buffer.read(cx).text(),
2491 "
2492 a
2493 b()
2494 c()
2495 "
2496 .unindent()
2497 );
2498 });
2499 assert_eq!(
2500 editor.selections.ranges(cx),
2501 &[
2502 Point::new(1, 2)..Point::new(1, 2),
2503 Point::new(2, 2)..Point::new(2, 2),
2504 ],
2505 );
2506
2507 editor.newline(&Newline, window, cx);
2508 assert_eq!(
2509 editor.text(cx),
2510 "
2511 a
2512 b(
2513 )
2514 c(
2515 )
2516 "
2517 .unindent()
2518 );
2519
2520 // The selections are moved after the inserted newlines
2521 assert_eq!(
2522 editor.selections.ranges(cx),
2523 &[
2524 Point::new(2, 0)..Point::new(2, 0),
2525 Point::new(4, 0)..Point::new(4, 0),
2526 ],
2527 );
2528 });
2529}
2530
2531#[gpui::test]
2532async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2533 init_test(cx, |settings| {
2534 settings.defaults.tab_size = NonZeroU32::new(4)
2535 });
2536
2537 let language = Arc::new(
2538 Language::new(
2539 LanguageConfig::default(),
2540 Some(tree_sitter_rust::LANGUAGE.into()),
2541 )
2542 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2543 .unwrap(),
2544 );
2545
2546 let mut cx = EditorTestContext::new(cx).await;
2547 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2548 cx.set_state(indoc! {"
2549 const a: ˇA = (
2550 (ˇ
2551 «const_functionˇ»(ˇ),
2552 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2553 )ˇ
2554 ˇ);ˇ
2555 "});
2556
2557 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2558 cx.assert_editor_state(indoc! {"
2559 ˇ
2560 const a: A = (
2561 ˇ
2562 (
2563 ˇ
2564 ˇ
2565 const_function(),
2566 ˇ
2567 ˇ
2568 ˇ
2569 ˇ
2570 something_else,
2571 ˇ
2572 )
2573 ˇ
2574 ˇ
2575 );
2576 "});
2577}
2578
2579#[gpui::test]
2580async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2581 init_test(cx, |settings| {
2582 settings.defaults.tab_size = NonZeroU32::new(4)
2583 });
2584
2585 let language = Arc::new(
2586 Language::new(
2587 LanguageConfig::default(),
2588 Some(tree_sitter_rust::LANGUAGE.into()),
2589 )
2590 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2591 .unwrap(),
2592 );
2593
2594 let mut cx = EditorTestContext::new(cx).await;
2595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2596 cx.set_state(indoc! {"
2597 const a: ˇA = (
2598 (ˇ
2599 «const_functionˇ»(ˇ),
2600 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2601 )ˇ
2602 ˇ);ˇ
2603 "});
2604
2605 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2606 cx.assert_editor_state(indoc! {"
2607 const a: A = (
2608 ˇ
2609 (
2610 ˇ
2611 const_function(),
2612 ˇ
2613 ˇ
2614 something_else,
2615 ˇ
2616 ˇ
2617 ˇ
2618 ˇ
2619 )
2620 ˇ
2621 );
2622 ˇ
2623 ˇ
2624 "});
2625}
2626
2627#[gpui::test]
2628async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2629 init_test(cx, |settings| {
2630 settings.defaults.tab_size = NonZeroU32::new(4)
2631 });
2632
2633 let language = Arc::new(Language::new(
2634 LanguageConfig {
2635 line_comments: vec!["//".into()],
2636 ..LanguageConfig::default()
2637 },
2638 None,
2639 ));
2640 {
2641 let mut cx = EditorTestContext::new(cx).await;
2642 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2643 cx.set_state(indoc! {"
2644 // Fooˇ
2645 "});
2646
2647 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2648 cx.assert_editor_state(indoc! {"
2649 // Foo
2650 //ˇ
2651 "});
2652 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2653 cx.set_state(indoc! {"
2654 ˇ// Foo
2655 "});
2656 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2657 cx.assert_editor_state(indoc! {"
2658
2659 ˇ// Foo
2660 "});
2661 }
2662 // Ensure that comment continuations can be disabled.
2663 update_test_language_settings(cx, |settings| {
2664 settings.defaults.extend_comment_on_newline = Some(false);
2665 });
2666 let mut cx = EditorTestContext::new(cx).await;
2667 cx.set_state(indoc! {"
2668 // Fooˇ
2669 "});
2670 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2671 cx.assert_editor_state(indoc! {"
2672 // Foo
2673 ˇ
2674 "});
2675}
2676
2677#[gpui::test]
2678fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2679 init_test(cx, |_| {});
2680
2681 let editor = cx.add_window(|window, cx| {
2682 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2683 let mut editor = build_editor(buffer.clone(), window, cx);
2684 editor.change_selections(None, window, cx, |s| {
2685 s.select_ranges([3..4, 11..12, 19..20])
2686 });
2687 editor
2688 });
2689
2690 _ = editor.update(cx, |editor, window, cx| {
2691 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2692 editor.buffer.update(cx, |buffer, cx| {
2693 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2694 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2695 });
2696 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2697
2698 editor.insert("Z", window, cx);
2699 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2700
2701 // The selections are moved after the inserted characters
2702 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2703 });
2704}
2705
2706#[gpui::test]
2707async fn test_tab(cx: &mut gpui::TestAppContext) {
2708 init_test(cx, |settings| {
2709 settings.defaults.tab_size = NonZeroU32::new(3)
2710 });
2711
2712 let mut cx = EditorTestContext::new(cx).await;
2713 cx.set_state(indoc! {"
2714 ˇabˇc
2715 ˇ🏀ˇ🏀ˇefg
2716 dˇ
2717 "});
2718 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2719 cx.assert_editor_state(indoc! {"
2720 ˇab ˇc
2721 ˇ🏀 ˇ🏀 ˇefg
2722 d ˇ
2723 "});
2724
2725 cx.set_state(indoc! {"
2726 a
2727 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2728 "});
2729 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2730 cx.assert_editor_state(indoc! {"
2731 a
2732 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2733 "});
2734}
2735
2736#[gpui::test]
2737async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2738 init_test(cx, |_| {});
2739
2740 let mut cx = EditorTestContext::new(cx).await;
2741 let language = Arc::new(
2742 Language::new(
2743 LanguageConfig::default(),
2744 Some(tree_sitter_rust::LANGUAGE.into()),
2745 )
2746 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2747 .unwrap(),
2748 );
2749 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2750
2751 // cursors that are already at the suggested indent level insert
2752 // a soft tab. cursors that are to the left of the suggested indent
2753 // auto-indent their line.
2754 cx.set_state(indoc! {"
2755 ˇ
2756 const a: B = (
2757 c(
2758 d(
2759 ˇ
2760 )
2761 ˇ
2762 ˇ )
2763 );
2764 "});
2765 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2766 cx.assert_editor_state(indoc! {"
2767 ˇ
2768 const a: B = (
2769 c(
2770 d(
2771 ˇ
2772 )
2773 ˇ
2774 ˇ)
2775 );
2776 "});
2777
2778 // handle auto-indent when there are multiple cursors on the same line
2779 cx.set_state(indoc! {"
2780 const a: B = (
2781 c(
2782 ˇ ˇ
2783 ˇ )
2784 );
2785 "});
2786 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2787 cx.assert_editor_state(indoc! {"
2788 const a: B = (
2789 c(
2790 ˇ
2791 ˇ)
2792 );
2793 "});
2794}
2795
2796#[gpui::test]
2797async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2798 init_test(cx, |settings| {
2799 settings.defaults.tab_size = NonZeroU32::new(4)
2800 });
2801
2802 let language = Arc::new(
2803 Language::new(
2804 LanguageConfig::default(),
2805 Some(tree_sitter_rust::LANGUAGE.into()),
2806 )
2807 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2808 .unwrap(),
2809 );
2810
2811 let mut cx = EditorTestContext::new(cx).await;
2812 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2813 cx.set_state(indoc! {"
2814 fn a() {
2815 if b {
2816 \t ˇc
2817 }
2818 }
2819 "});
2820
2821 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2822 cx.assert_editor_state(indoc! {"
2823 fn a() {
2824 if b {
2825 ˇc
2826 }
2827 }
2828 "});
2829}
2830
2831#[gpui::test]
2832async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2833 init_test(cx, |settings| {
2834 settings.defaults.tab_size = NonZeroU32::new(4);
2835 });
2836
2837 let mut cx = EditorTestContext::new(cx).await;
2838
2839 cx.set_state(indoc! {"
2840 «oneˇ» «twoˇ»
2841 three
2842 four
2843 "});
2844 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2845 cx.assert_editor_state(indoc! {"
2846 «oneˇ» «twoˇ»
2847 three
2848 four
2849 "});
2850
2851 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2852 cx.assert_editor_state(indoc! {"
2853 «oneˇ» «twoˇ»
2854 three
2855 four
2856 "});
2857
2858 // select across line ending
2859 cx.set_state(indoc! {"
2860 one two
2861 t«hree
2862 ˇ» four
2863 "});
2864 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2865 cx.assert_editor_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ» four
2869 "});
2870
2871 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2872 cx.assert_editor_state(indoc! {"
2873 one two
2874 t«hree
2875 ˇ» four
2876 "});
2877
2878 // Ensure that indenting/outdenting works when the cursor is at column 0.
2879 cx.set_state(indoc! {"
2880 one two
2881 ˇthree
2882 four
2883 "});
2884 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2885 cx.assert_editor_state(indoc! {"
2886 one two
2887 ˇthree
2888 four
2889 "});
2890
2891 cx.set_state(indoc! {"
2892 one two
2893 ˇ three
2894 four
2895 "});
2896 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2897 cx.assert_editor_state(indoc! {"
2898 one two
2899 ˇthree
2900 four
2901 "});
2902}
2903
2904#[gpui::test]
2905async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2906 init_test(cx, |settings| {
2907 settings.defaults.hard_tabs = Some(true);
2908 });
2909
2910 let mut cx = EditorTestContext::new(cx).await;
2911
2912 // select two ranges on one line
2913 cx.set_state(indoc! {"
2914 «oneˇ» «twoˇ»
2915 three
2916 four
2917 "});
2918 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2919 cx.assert_editor_state(indoc! {"
2920 \t«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\t«oneˇ» «twoˇ»
2927 three
2928 four
2929 "});
2930 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2931 cx.assert_editor_state(indoc! {"
2932 \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 «oneˇ» «twoˇ»
2939 three
2940 four
2941 "});
2942
2943 // select across a line ending
2944 cx.set_state(indoc! {"
2945 one two
2946 t«hree
2947 ˇ»four
2948 "});
2949 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2950 cx.assert_editor_state(indoc! {"
2951 one two
2952 \tt«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 \t\tt«hree
2959 ˇ»four
2960 "});
2961 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2962 cx.assert_editor_state(indoc! {"
2963 one two
2964 \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 t«hree
2971 ˇ»four
2972 "});
2973
2974 // Ensure that indenting/outdenting works when the cursor is at column 0.
2975 cx.set_state(indoc! {"
2976 one two
2977 ˇthree
2978 four
2979 "});
2980 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2981 cx.assert_editor_state(indoc! {"
2982 one two
2983 ˇthree
2984 four
2985 "});
2986 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2987 cx.assert_editor_state(indoc! {"
2988 one two
2989 \tˇthree
2990 four
2991 "});
2992 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2993 cx.assert_editor_state(indoc! {"
2994 one two
2995 ˇthree
2996 four
2997 "});
2998}
2999
3000#[gpui::test]
3001fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3002 init_test(cx, |settings| {
3003 settings.languages.extend([
3004 (
3005 "TOML".into(),
3006 LanguageSettingsContent {
3007 tab_size: NonZeroU32::new(2),
3008 ..Default::default()
3009 },
3010 ),
3011 (
3012 "Rust".into(),
3013 LanguageSettingsContent {
3014 tab_size: NonZeroU32::new(4),
3015 ..Default::default()
3016 },
3017 ),
3018 ]);
3019 });
3020
3021 let toml_language = Arc::new(Language::new(
3022 LanguageConfig {
3023 name: "TOML".into(),
3024 ..Default::default()
3025 },
3026 None,
3027 ));
3028 let rust_language = Arc::new(Language::new(
3029 LanguageConfig {
3030 name: "Rust".into(),
3031 ..Default::default()
3032 },
3033 None,
3034 ));
3035
3036 let toml_buffer =
3037 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3038 let rust_buffer =
3039 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3040 let multibuffer = cx.new(|cx| {
3041 let mut multibuffer = MultiBuffer::new(ReadWrite);
3042 multibuffer.push_excerpts(
3043 toml_buffer.clone(),
3044 [ExcerptRange {
3045 context: Point::new(0, 0)..Point::new(2, 0),
3046 primary: None,
3047 }],
3048 cx,
3049 );
3050 multibuffer.push_excerpts(
3051 rust_buffer.clone(),
3052 [ExcerptRange {
3053 context: Point::new(0, 0)..Point::new(1, 0),
3054 primary: None,
3055 }],
3056 cx,
3057 );
3058 multibuffer
3059 });
3060
3061 cx.add_window(|window, cx| {
3062 let mut editor = build_editor(multibuffer, window, cx);
3063
3064 assert_eq!(
3065 editor.text(cx),
3066 indoc! {"
3067 a = 1
3068 b = 2
3069
3070 const c: usize = 3;
3071 "}
3072 );
3073
3074 select_ranges(
3075 &mut editor,
3076 indoc! {"
3077 «aˇ» = 1
3078 b = 2
3079
3080 «const c:ˇ» usize = 3;
3081 "},
3082 window,
3083 cx,
3084 );
3085
3086 editor.tab(&Tab, window, cx);
3087 assert_text_with_selections(
3088 &mut editor,
3089 indoc! {"
3090 «aˇ» = 1
3091 b = 2
3092
3093 «const c:ˇ» usize = 3;
3094 "},
3095 cx,
3096 );
3097 editor.tab_prev(&TabPrev, window, cx);
3098 assert_text_with_selections(
3099 &mut editor,
3100 indoc! {"
3101 «aˇ» = 1
3102 b = 2
3103
3104 «const c:ˇ» usize = 3;
3105 "},
3106 cx,
3107 );
3108
3109 editor
3110 });
3111}
3112
3113#[gpui::test]
3114async fn test_backspace(cx: &mut gpui::TestAppContext) {
3115 init_test(cx, |_| {});
3116
3117 let mut cx = EditorTestContext::new(cx).await;
3118
3119 // Basic backspace
3120 cx.set_state(indoc! {"
3121 onˇe two three
3122 fou«rˇ» five six
3123 seven «ˇeight nine
3124 »ten
3125 "});
3126 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3127 cx.assert_editor_state(indoc! {"
3128 oˇe two three
3129 fouˇ five six
3130 seven ˇten
3131 "});
3132
3133 // Test backspace inside and around indents
3134 cx.set_state(indoc! {"
3135 zero
3136 ˇone
3137 ˇtwo
3138 ˇ ˇ ˇ three
3139 ˇ ˇ four
3140 "});
3141 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3142 cx.assert_editor_state(indoc! {"
3143 zero
3144 ˇone
3145 ˇtwo
3146 ˇ threeˇ four
3147 "});
3148
3149 // Test backspace with line_mode set to true
3150 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3151 cx.set_state(indoc! {"
3152 The ˇquick ˇbrown
3153 fox jumps over
3154 the lazy dog
3155 ˇThe qu«ick bˇ»rown"});
3156 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3157 cx.assert_editor_state(indoc! {"
3158 ˇfox jumps over
3159 the lazy dogˇ"});
3160}
3161
3162#[gpui::test]
3163async fn test_delete(cx: &mut gpui::TestAppContext) {
3164 init_test(cx, |_| {});
3165
3166 let mut cx = EditorTestContext::new(cx).await;
3167 cx.set_state(indoc! {"
3168 onˇe two three
3169 fou«rˇ» five six
3170 seven «ˇeight nine
3171 »ten
3172 "});
3173 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3174 cx.assert_editor_state(indoc! {"
3175 onˇ two three
3176 fouˇ five six
3177 seven ˇten
3178 "});
3179
3180 // Test backspace with line_mode set to true
3181 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3182 cx.set_state(indoc! {"
3183 The ˇquick ˇbrown
3184 fox «ˇjum»ps over
3185 the lazy dog
3186 ˇThe qu«ick bˇ»rown"});
3187 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3188 cx.assert_editor_state("ˇthe lazy dogˇ");
3189}
3190
3191#[gpui::test]
3192fn test_delete_line(cx: &mut TestAppContext) {
3193 init_test(cx, |_| {});
3194
3195 let editor = cx.add_window(|window, cx| {
3196 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3197 build_editor(buffer, window, cx)
3198 });
3199 _ = editor.update(cx, |editor, window, cx| {
3200 editor.change_selections(None, window, cx, |s| {
3201 s.select_display_ranges([
3202 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3203 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3204 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3205 ])
3206 });
3207 editor.delete_line(&DeleteLine, window, cx);
3208 assert_eq!(editor.display_text(cx), "ghi");
3209 assert_eq!(
3210 editor.selections.display_ranges(cx),
3211 vec![
3212 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3213 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3214 ]
3215 );
3216 });
3217
3218 let editor = cx.add_window(|window, cx| {
3219 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3220 build_editor(buffer, window, cx)
3221 });
3222 _ = editor.update(cx, |editor, window, cx| {
3223 editor.change_selections(None, window, cx, |s| {
3224 s.select_display_ranges([
3225 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3226 ])
3227 });
3228 editor.delete_line(&DeleteLine, window, cx);
3229 assert_eq!(editor.display_text(cx), "ghi\n");
3230 assert_eq!(
3231 editor.selections.display_ranges(cx),
3232 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3233 );
3234 });
3235}
3236
3237#[gpui::test]
3238fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3239 init_test(cx, |_| {});
3240
3241 cx.add_window(|window, cx| {
3242 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3243 let mut editor = build_editor(buffer.clone(), window, cx);
3244 let buffer = buffer.read(cx).as_singleton().unwrap();
3245
3246 assert_eq!(
3247 editor.selections.ranges::<Point>(cx),
3248 &[Point::new(0, 0)..Point::new(0, 0)]
3249 );
3250
3251 // When on single line, replace newline at end by space
3252 editor.join_lines(&JoinLines, window, cx);
3253 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3254 assert_eq!(
3255 editor.selections.ranges::<Point>(cx),
3256 &[Point::new(0, 3)..Point::new(0, 3)]
3257 );
3258
3259 // When multiple lines are selected, remove newlines that are spanned by the selection
3260 editor.change_selections(None, window, cx, |s| {
3261 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3262 });
3263 editor.join_lines(&JoinLines, window, cx);
3264 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3265 assert_eq!(
3266 editor.selections.ranges::<Point>(cx),
3267 &[Point::new(0, 11)..Point::new(0, 11)]
3268 );
3269
3270 // Undo should be transactional
3271 editor.undo(&Undo, window, cx);
3272 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3273 assert_eq!(
3274 editor.selections.ranges::<Point>(cx),
3275 &[Point::new(0, 5)..Point::new(2, 2)]
3276 );
3277
3278 // When joining an empty line don't insert a space
3279 editor.change_selections(None, window, cx, |s| {
3280 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3281 });
3282 editor.join_lines(&JoinLines, window, cx);
3283 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3284 assert_eq!(
3285 editor.selections.ranges::<Point>(cx),
3286 [Point::new(2, 3)..Point::new(2, 3)]
3287 );
3288
3289 // We can remove trailing newlines
3290 editor.join_lines(&JoinLines, window, cx);
3291 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3292 assert_eq!(
3293 editor.selections.ranges::<Point>(cx),
3294 [Point::new(2, 3)..Point::new(2, 3)]
3295 );
3296
3297 // We don't blow up on the last line
3298 editor.join_lines(&JoinLines, window, cx);
3299 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3300 assert_eq!(
3301 editor.selections.ranges::<Point>(cx),
3302 [Point::new(2, 3)..Point::new(2, 3)]
3303 );
3304
3305 // reset to test indentation
3306 editor.buffer.update(cx, |buffer, cx| {
3307 buffer.edit(
3308 [
3309 (Point::new(1, 0)..Point::new(1, 2), " "),
3310 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3311 ],
3312 None,
3313 cx,
3314 )
3315 });
3316
3317 // We remove any leading spaces
3318 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3319 editor.change_selections(None, window, cx, |s| {
3320 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3321 });
3322 editor.join_lines(&JoinLines, window, cx);
3323 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3324
3325 // We don't insert a space for a line containing only spaces
3326 editor.join_lines(&JoinLines, window, cx);
3327 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3328
3329 // We ignore any leading tabs
3330 editor.join_lines(&JoinLines, window, cx);
3331 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3332
3333 editor
3334 });
3335}
3336
3337#[gpui::test]
3338fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3339 init_test(cx, |_| {});
3340
3341 cx.add_window(|window, cx| {
3342 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3343 let mut editor = build_editor(buffer.clone(), window, cx);
3344 let buffer = buffer.read(cx).as_singleton().unwrap();
3345
3346 editor.change_selections(None, window, cx, |s| {
3347 s.select_ranges([
3348 Point::new(0, 2)..Point::new(1, 1),
3349 Point::new(1, 2)..Point::new(1, 2),
3350 Point::new(3, 1)..Point::new(3, 2),
3351 ])
3352 });
3353
3354 editor.join_lines(&JoinLines, window, cx);
3355 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3356
3357 assert_eq!(
3358 editor.selections.ranges::<Point>(cx),
3359 [
3360 Point::new(0, 7)..Point::new(0, 7),
3361 Point::new(1, 3)..Point::new(1, 3)
3362 ]
3363 );
3364 editor
3365 });
3366}
3367
3368#[gpui::test]
3369async fn test_join_lines_with_git_diff_base(
3370 executor: BackgroundExecutor,
3371 cx: &mut gpui::TestAppContext,
3372) {
3373 init_test(cx, |_| {});
3374
3375 let mut cx = EditorTestContext::new(cx).await;
3376
3377 let diff_base = r#"
3378 Line 0
3379 Line 1
3380 Line 2
3381 Line 3
3382 "#
3383 .unindent();
3384
3385 cx.set_state(
3386 &r#"
3387 ˇLine 0
3388 Line 1
3389 Line 2
3390 Line 3
3391 "#
3392 .unindent(),
3393 );
3394
3395 cx.set_diff_base(&diff_base);
3396 executor.run_until_parked();
3397
3398 // Join lines
3399 cx.update_editor(|editor, window, cx| {
3400 editor.join_lines(&JoinLines, window, cx);
3401 });
3402 executor.run_until_parked();
3403
3404 cx.assert_editor_state(
3405 &r#"
3406 Line 0ˇ Line 1
3407 Line 2
3408 Line 3
3409 "#
3410 .unindent(),
3411 );
3412 // Join again
3413 cx.update_editor(|editor, window, cx| {
3414 editor.join_lines(&JoinLines, window, cx);
3415 });
3416 executor.run_until_parked();
3417
3418 cx.assert_editor_state(
3419 &r#"
3420 Line 0 Line 1ˇ Line 2
3421 Line 3
3422 "#
3423 .unindent(),
3424 );
3425}
3426
3427#[gpui::test]
3428async fn test_custom_newlines_cause_no_false_positive_diffs(
3429 executor: BackgroundExecutor,
3430 cx: &mut gpui::TestAppContext,
3431) {
3432 init_test(cx, |_| {});
3433 let mut cx = EditorTestContext::new(cx).await;
3434 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3435 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3436 executor.run_until_parked();
3437
3438 cx.update_editor(|editor, window, cx| {
3439 let snapshot = editor.snapshot(window, cx);
3440 assert_eq!(
3441 snapshot
3442 .buffer_snapshot
3443 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3444 .collect::<Vec<_>>(),
3445 Vec::new(),
3446 "Should not have any diffs for files with custom newlines"
3447 );
3448 });
3449}
3450
3451#[gpui::test]
3452async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3453 init_test(cx, |_| {});
3454
3455 let mut cx = EditorTestContext::new(cx).await;
3456
3457 // Test sort_lines_case_insensitive()
3458 cx.set_state(indoc! {"
3459 «z
3460 y
3461 x
3462 Z
3463 Y
3464 Xˇ»
3465 "});
3466 cx.update_editor(|e, window, cx| {
3467 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3468 });
3469 cx.assert_editor_state(indoc! {"
3470 «x
3471 X
3472 y
3473 Y
3474 z
3475 Zˇ»
3476 "});
3477
3478 // Test reverse_lines()
3479 cx.set_state(indoc! {"
3480 «5
3481 4
3482 3
3483 2
3484 1ˇ»
3485 "});
3486 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3487 cx.assert_editor_state(indoc! {"
3488 «1
3489 2
3490 3
3491 4
3492 5ˇ»
3493 "});
3494
3495 // Skip testing shuffle_line()
3496
3497 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3498 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3499
3500 // Don't manipulate when cursor is on single line, but expand the selection
3501 cx.set_state(indoc! {"
3502 ddˇdd
3503 ccc
3504 bb
3505 a
3506 "});
3507 cx.update_editor(|e, window, cx| {
3508 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3509 });
3510 cx.assert_editor_state(indoc! {"
3511 «ddddˇ»
3512 ccc
3513 bb
3514 a
3515 "});
3516
3517 // Basic manipulate case
3518 // Start selection moves to column 0
3519 // End of selection shrinks to fit shorter line
3520 cx.set_state(indoc! {"
3521 dd«d
3522 ccc
3523 bb
3524 aaaaaˇ»
3525 "});
3526 cx.update_editor(|e, window, cx| {
3527 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3528 });
3529 cx.assert_editor_state(indoc! {"
3530 «aaaaa
3531 bb
3532 ccc
3533 dddˇ»
3534 "});
3535
3536 // Manipulate case with newlines
3537 cx.set_state(indoc! {"
3538 dd«d
3539 ccc
3540
3541 bb
3542 aaaaa
3543
3544 ˇ»
3545 "});
3546 cx.update_editor(|e, window, cx| {
3547 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3548 });
3549 cx.assert_editor_state(indoc! {"
3550 «
3551
3552 aaaaa
3553 bb
3554 ccc
3555 dddˇ»
3556
3557 "});
3558
3559 // Adding new line
3560 cx.set_state(indoc! {"
3561 aa«a
3562 bbˇ»b
3563 "});
3564 cx.update_editor(|e, window, cx| {
3565 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3566 });
3567 cx.assert_editor_state(indoc! {"
3568 «aaa
3569 bbb
3570 added_lineˇ»
3571 "});
3572
3573 // Removing line
3574 cx.set_state(indoc! {"
3575 aa«a
3576 bbbˇ»
3577 "});
3578 cx.update_editor(|e, window, cx| {
3579 e.manipulate_lines(window, cx, |lines| {
3580 lines.pop();
3581 })
3582 });
3583 cx.assert_editor_state(indoc! {"
3584 «aaaˇ»
3585 "});
3586
3587 // Removing all lines
3588 cx.set_state(indoc! {"
3589 aa«a
3590 bbbˇ»
3591 "});
3592 cx.update_editor(|e, window, cx| {
3593 e.manipulate_lines(window, cx, |lines| {
3594 lines.drain(..);
3595 })
3596 });
3597 cx.assert_editor_state(indoc! {"
3598 ˇ
3599 "});
3600}
3601
3602#[gpui::test]
3603async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3604 init_test(cx, |_| {});
3605
3606 let mut cx = EditorTestContext::new(cx).await;
3607
3608 // Consider continuous selection as single selection
3609 cx.set_state(indoc! {"
3610 Aaa«aa
3611 cˇ»c«c
3612 bb
3613 aaaˇ»aa
3614 "});
3615 cx.update_editor(|e, window, cx| {
3616 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3617 });
3618 cx.assert_editor_state(indoc! {"
3619 «Aaaaa
3620 ccc
3621 bb
3622 aaaaaˇ»
3623 "});
3624
3625 cx.set_state(indoc! {"
3626 Aaa«aa
3627 cˇ»c«c
3628 bb
3629 aaaˇ»aa
3630 "});
3631 cx.update_editor(|e, window, cx| {
3632 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3633 });
3634 cx.assert_editor_state(indoc! {"
3635 «Aaaaa
3636 ccc
3637 bbˇ»
3638 "});
3639
3640 // Consider non continuous selection as distinct dedup operations
3641 cx.set_state(indoc! {"
3642 «aaaaa
3643 bb
3644 aaaaa
3645 aaaaaˇ»
3646
3647 aaa«aaˇ»
3648 "});
3649 cx.update_editor(|e, window, cx| {
3650 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3651 });
3652 cx.assert_editor_state(indoc! {"
3653 «aaaaa
3654 bbˇ»
3655
3656 «aaaaaˇ»
3657 "});
3658}
3659
3660#[gpui::test]
3661async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3662 init_test(cx, |_| {});
3663
3664 let mut cx = EditorTestContext::new(cx).await;
3665
3666 cx.set_state(indoc! {"
3667 «Aaa
3668 aAa
3669 Aaaˇ»
3670 "});
3671 cx.update_editor(|e, window, cx| {
3672 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3673 });
3674 cx.assert_editor_state(indoc! {"
3675 «Aaa
3676 aAaˇ»
3677 "});
3678
3679 cx.set_state(indoc! {"
3680 «Aaa
3681 aAa
3682 aaAˇ»
3683 "});
3684 cx.update_editor(|e, window, cx| {
3685 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3686 });
3687 cx.assert_editor_state(indoc! {"
3688 «Aaaˇ»
3689 "});
3690}
3691
3692#[gpui::test]
3693async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3694 init_test(cx, |_| {});
3695
3696 let mut cx = EditorTestContext::new(cx).await;
3697
3698 // Manipulate with multiple selections on a single line
3699 cx.set_state(indoc! {"
3700 dd«dd
3701 cˇ»c«c
3702 bb
3703 aaaˇ»aa
3704 "});
3705 cx.update_editor(|e, window, cx| {
3706 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3707 });
3708 cx.assert_editor_state(indoc! {"
3709 «aaaaa
3710 bb
3711 ccc
3712 ddddˇ»
3713 "});
3714
3715 // Manipulate with multiple disjoin selections
3716 cx.set_state(indoc! {"
3717 5«
3718 4
3719 3
3720 2
3721 1ˇ»
3722
3723 dd«dd
3724 ccc
3725 bb
3726 aaaˇ»aa
3727 "});
3728 cx.update_editor(|e, window, cx| {
3729 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3730 });
3731 cx.assert_editor_state(indoc! {"
3732 «1
3733 2
3734 3
3735 4
3736 5ˇ»
3737
3738 «aaaaa
3739 bb
3740 ccc
3741 ddddˇ»
3742 "});
3743
3744 // Adding lines on each selection
3745 cx.set_state(indoc! {"
3746 2«
3747 1ˇ»
3748
3749 bb«bb
3750 aaaˇ»aa
3751 "});
3752 cx.update_editor(|e, window, cx| {
3753 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3754 });
3755 cx.assert_editor_state(indoc! {"
3756 «2
3757 1
3758 added lineˇ»
3759
3760 «bbbb
3761 aaaaa
3762 added lineˇ»
3763 "});
3764
3765 // Removing lines on each selection
3766 cx.set_state(indoc! {"
3767 2«
3768 1ˇ»
3769
3770 bb«bb
3771 aaaˇ»aa
3772 "});
3773 cx.update_editor(|e, window, cx| {
3774 e.manipulate_lines(window, cx, |lines| {
3775 lines.pop();
3776 })
3777 });
3778 cx.assert_editor_state(indoc! {"
3779 «2ˇ»
3780
3781 «bbbbˇ»
3782 "});
3783}
3784
3785#[gpui::test]
3786async fn test_manipulate_text(cx: &mut TestAppContext) {
3787 init_test(cx, |_| {});
3788
3789 let mut cx = EditorTestContext::new(cx).await;
3790
3791 // Test convert_to_upper_case()
3792 cx.set_state(indoc! {"
3793 «hello worldˇ»
3794 "});
3795 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3796 cx.assert_editor_state(indoc! {"
3797 «HELLO WORLDˇ»
3798 "});
3799
3800 // Test convert_to_lower_case()
3801 cx.set_state(indoc! {"
3802 «HELLO WORLDˇ»
3803 "});
3804 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3805 cx.assert_editor_state(indoc! {"
3806 «hello worldˇ»
3807 "});
3808
3809 // Test multiple line, single selection case
3810 cx.set_state(indoc! {"
3811 «The quick brown
3812 fox jumps over
3813 the lazy dogˇ»
3814 "});
3815 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3816 cx.assert_editor_state(indoc! {"
3817 «The Quick Brown
3818 Fox Jumps Over
3819 The Lazy Dogˇ»
3820 "});
3821
3822 // Test multiple line, single selection case
3823 cx.set_state(indoc! {"
3824 «The quick brown
3825 fox jumps over
3826 the lazy dogˇ»
3827 "});
3828 cx.update_editor(|e, window, cx| {
3829 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3830 });
3831 cx.assert_editor_state(indoc! {"
3832 «TheQuickBrown
3833 FoxJumpsOver
3834 TheLazyDogˇ»
3835 "});
3836
3837 // From here on out, test more complex cases of manipulate_text()
3838
3839 // Test no selection case - should affect words cursors are in
3840 // Cursor at beginning, middle, and end of word
3841 cx.set_state(indoc! {"
3842 ˇhello big beauˇtiful worldˇ
3843 "});
3844 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3845 cx.assert_editor_state(indoc! {"
3846 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3847 "});
3848
3849 // Test multiple selections on a single line and across multiple lines
3850 cx.set_state(indoc! {"
3851 «Theˇ» quick «brown
3852 foxˇ» jumps «overˇ»
3853 the «lazyˇ» dog
3854 "});
3855 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3856 cx.assert_editor_state(indoc! {"
3857 «THEˇ» quick «BROWN
3858 FOXˇ» jumps «OVERˇ»
3859 the «LAZYˇ» dog
3860 "});
3861
3862 // Test case where text length grows
3863 cx.set_state(indoc! {"
3864 «tschüߡ»
3865 "});
3866 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3867 cx.assert_editor_state(indoc! {"
3868 «TSCHÜSSˇ»
3869 "});
3870
3871 // Test to make sure we don't crash when text shrinks
3872 cx.set_state(indoc! {"
3873 aaa_bbbˇ
3874 "});
3875 cx.update_editor(|e, window, cx| {
3876 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3877 });
3878 cx.assert_editor_state(indoc! {"
3879 «aaaBbbˇ»
3880 "});
3881
3882 // Test to make sure we all aware of the fact that each word can grow and shrink
3883 // Final selections should be aware of this fact
3884 cx.set_state(indoc! {"
3885 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3886 "});
3887 cx.update_editor(|e, window, cx| {
3888 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3889 });
3890 cx.assert_editor_state(indoc! {"
3891 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3892 "});
3893
3894 cx.set_state(indoc! {"
3895 «hElLo, WoRld!ˇ»
3896 "});
3897 cx.update_editor(|e, window, cx| {
3898 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3899 });
3900 cx.assert_editor_state(indoc! {"
3901 «HeLlO, wOrLD!ˇ»
3902 "});
3903}
3904
3905#[gpui::test]
3906fn test_duplicate_line(cx: &mut TestAppContext) {
3907 init_test(cx, |_| {});
3908
3909 let editor = cx.add_window(|window, cx| {
3910 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3911 build_editor(buffer, window, cx)
3912 });
3913 _ = editor.update(cx, |editor, window, cx| {
3914 editor.change_selections(None, window, cx, |s| {
3915 s.select_display_ranges([
3916 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3917 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3918 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3919 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3920 ])
3921 });
3922 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3923 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3924 assert_eq!(
3925 editor.selections.display_ranges(cx),
3926 vec![
3927 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3928 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3929 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3930 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3931 ]
3932 );
3933 });
3934
3935 let editor = cx.add_window(|window, cx| {
3936 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3937 build_editor(buffer, window, cx)
3938 });
3939 _ = editor.update(cx, |editor, window, cx| {
3940 editor.change_selections(None, window, cx, |s| {
3941 s.select_display_ranges([
3942 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3943 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3944 ])
3945 });
3946 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3947 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3948 assert_eq!(
3949 editor.selections.display_ranges(cx),
3950 vec![
3951 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3952 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3953 ]
3954 );
3955 });
3956
3957 // With `move_upwards` the selections stay in place, except for
3958 // the lines inserted above them
3959 let editor = cx.add_window(|window, cx| {
3960 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3961 build_editor(buffer, window, cx)
3962 });
3963 _ = editor.update(cx, |editor, window, cx| {
3964 editor.change_selections(None, window, cx, |s| {
3965 s.select_display_ranges([
3966 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3967 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3968 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3969 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3970 ])
3971 });
3972 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3973 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3974 assert_eq!(
3975 editor.selections.display_ranges(cx),
3976 vec![
3977 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3978 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3979 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3980 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3981 ]
3982 );
3983 });
3984
3985 let editor = cx.add_window(|window, cx| {
3986 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3987 build_editor(buffer, window, cx)
3988 });
3989 _ = editor.update(cx, |editor, window, cx| {
3990 editor.change_selections(None, window, cx, |s| {
3991 s.select_display_ranges([
3992 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3993 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3994 ])
3995 });
3996 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3997 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3998 assert_eq!(
3999 editor.selections.display_ranges(cx),
4000 vec![
4001 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4002 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4003 ]
4004 );
4005 });
4006
4007 let editor = cx.add_window(|window, cx| {
4008 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4009 build_editor(buffer, window, cx)
4010 });
4011 _ = editor.update(cx, |editor, window, cx| {
4012 editor.change_selections(None, window, cx, |s| {
4013 s.select_display_ranges([
4014 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4015 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4016 ])
4017 });
4018 editor.duplicate_selection(&DuplicateSelection, window, cx);
4019 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4020 assert_eq!(
4021 editor.selections.display_ranges(cx),
4022 vec![
4023 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4024 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4025 ]
4026 );
4027 });
4028}
4029
4030#[gpui::test]
4031fn test_move_line_up_down(cx: &mut TestAppContext) {
4032 init_test(cx, |_| {});
4033
4034 let editor = cx.add_window(|window, cx| {
4035 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4036 build_editor(buffer, window, cx)
4037 });
4038 _ = editor.update(cx, |editor, window, cx| {
4039 editor.fold_creases(
4040 vec![
4041 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4042 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4043 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4044 ],
4045 true,
4046 window,
4047 cx,
4048 );
4049 editor.change_selections(None, window, cx, |s| {
4050 s.select_display_ranges([
4051 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4052 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4053 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4054 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4055 ])
4056 });
4057 assert_eq!(
4058 editor.display_text(cx),
4059 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4060 );
4061
4062 editor.move_line_up(&MoveLineUp, window, cx);
4063 assert_eq!(
4064 editor.display_text(cx),
4065 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4066 );
4067 assert_eq!(
4068 editor.selections.display_ranges(cx),
4069 vec![
4070 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4071 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4072 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4073 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4074 ]
4075 );
4076 });
4077
4078 _ = editor.update(cx, |editor, window, cx| {
4079 editor.move_line_down(&MoveLineDown, window, cx);
4080 assert_eq!(
4081 editor.display_text(cx),
4082 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4083 );
4084 assert_eq!(
4085 editor.selections.display_ranges(cx),
4086 vec![
4087 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4088 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4089 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4090 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4091 ]
4092 );
4093 });
4094
4095 _ = editor.update(cx, |editor, window, cx| {
4096 editor.move_line_down(&MoveLineDown, window, cx);
4097 assert_eq!(
4098 editor.display_text(cx),
4099 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4100 );
4101 assert_eq!(
4102 editor.selections.display_ranges(cx),
4103 vec![
4104 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4105 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4106 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4107 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4108 ]
4109 );
4110 });
4111
4112 _ = editor.update(cx, |editor, window, cx| {
4113 editor.move_line_up(&MoveLineUp, window, cx);
4114 assert_eq!(
4115 editor.display_text(cx),
4116 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4117 );
4118 assert_eq!(
4119 editor.selections.display_ranges(cx),
4120 vec![
4121 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4122 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4123 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4124 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4125 ]
4126 );
4127 });
4128}
4129
4130#[gpui::test]
4131fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4132 init_test(cx, |_| {});
4133
4134 let editor = cx.add_window(|window, cx| {
4135 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4136 build_editor(buffer, window, cx)
4137 });
4138 _ = editor.update(cx, |editor, window, cx| {
4139 let snapshot = editor.buffer.read(cx).snapshot(cx);
4140 editor.insert_blocks(
4141 [BlockProperties {
4142 style: BlockStyle::Fixed,
4143 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4144 height: 1,
4145 render: Arc::new(|_| div().into_any()),
4146 priority: 0,
4147 }],
4148 Some(Autoscroll::fit()),
4149 cx,
4150 );
4151 editor.change_selections(None, window, cx, |s| {
4152 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4153 });
4154 editor.move_line_down(&MoveLineDown, window, cx);
4155 });
4156}
4157
4158#[gpui::test]
4159async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4160 init_test(cx, |_| {});
4161
4162 let mut cx = EditorTestContext::new(cx).await;
4163 cx.set_state(
4164 &"
4165 ˇzero
4166 one
4167 two
4168 three
4169 four
4170 five
4171 "
4172 .unindent(),
4173 );
4174
4175 // Create a four-line block that replaces three lines of text.
4176 cx.update_editor(|editor, window, cx| {
4177 let snapshot = editor.snapshot(window, cx);
4178 let snapshot = &snapshot.buffer_snapshot;
4179 let placement = BlockPlacement::Replace(
4180 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4181 );
4182 editor.insert_blocks(
4183 [BlockProperties {
4184 placement,
4185 height: 4,
4186 style: BlockStyle::Sticky,
4187 render: Arc::new(|_| gpui::div().into_any_element()),
4188 priority: 0,
4189 }],
4190 None,
4191 cx,
4192 );
4193 });
4194
4195 // Move down so that the cursor touches the block.
4196 cx.update_editor(|editor, window, cx| {
4197 editor.move_down(&Default::default(), window, cx);
4198 });
4199 cx.assert_editor_state(
4200 &"
4201 zero
4202 «one
4203 two
4204 threeˇ»
4205 four
4206 five
4207 "
4208 .unindent(),
4209 );
4210
4211 // Move down past the block.
4212 cx.update_editor(|editor, window, cx| {
4213 editor.move_down(&Default::default(), window, cx);
4214 });
4215 cx.assert_editor_state(
4216 &"
4217 zero
4218 one
4219 two
4220 three
4221 ˇfour
4222 five
4223 "
4224 .unindent(),
4225 );
4226}
4227
4228#[gpui::test]
4229fn test_transpose(cx: &mut TestAppContext) {
4230 init_test(cx, |_| {});
4231
4232 _ = cx.add_window(|window, cx| {
4233 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4234 editor.set_style(EditorStyle::default(), window, cx);
4235 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4236 editor.transpose(&Default::default(), window, cx);
4237 assert_eq!(editor.text(cx), "bac");
4238 assert_eq!(editor.selections.ranges(cx), [2..2]);
4239
4240 editor.transpose(&Default::default(), window, cx);
4241 assert_eq!(editor.text(cx), "bca");
4242 assert_eq!(editor.selections.ranges(cx), [3..3]);
4243
4244 editor.transpose(&Default::default(), window, cx);
4245 assert_eq!(editor.text(cx), "bac");
4246 assert_eq!(editor.selections.ranges(cx), [3..3]);
4247
4248 editor
4249 });
4250
4251 _ = cx.add_window(|window, cx| {
4252 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4253 editor.set_style(EditorStyle::default(), window, cx);
4254 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4255 editor.transpose(&Default::default(), window, cx);
4256 assert_eq!(editor.text(cx), "acb\nde");
4257 assert_eq!(editor.selections.ranges(cx), [3..3]);
4258
4259 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4260 editor.transpose(&Default::default(), window, cx);
4261 assert_eq!(editor.text(cx), "acbd\ne");
4262 assert_eq!(editor.selections.ranges(cx), [5..5]);
4263
4264 editor.transpose(&Default::default(), window, cx);
4265 assert_eq!(editor.text(cx), "acbde\n");
4266 assert_eq!(editor.selections.ranges(cx), [6..6]);
4267
4268 editor.transpose(&Default::default(), window, cx);
4269 assert_eq!(editor.text(cx), "acbd\ne");
4270 assert_eq!(editor.selections.ranges(cx), [6..6]);
4271
4272 editor
4273 });
4274
4275 _ = cx.add_window(|window, cx| {
4276 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4277 editor.set_style(EditorStyle::default(), window, cx);
4278 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4279 editor.transpose(&Default::default(), window, cx);
4280 assert_eq!(editor.text(cx), "bacd\ne");
4281 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4282
4283 editor.transpose(&Default::default(), window, cx);
4284 assert_eq!(editor.text(cx), "bcade\n");
4285 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4286
4287 editor.transpose(&Default::default(), window, cx);
4288 assert_eq!(editor.text(cx), "bcda\ne");
4289 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4290
4291 editor.transpose(&Default::default(), window, cx);
4292 assert_eq!(editor.text(cx), "bcade\n");
4293 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4294
4295 editor.transpose(&Default::default(), window, cx);
4296 assert_eq!(editor.text(cx), "bcaed\n");
4297 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4298
4299 editor
4300 });
4301
4302 _ = cx.add_window(|window, cx| {
4303 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4304 editor.set_style(EditorStyle::default(), window, cx);
4305 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4306 editor.transpose(&Default::default(), window, cx);
4307 assert_eq!(editor.text(cx), "🏀🍐✋");
4308 assert_eq!(editor.selections.ranges(cx), [8..8]);
4309
4310 editor.transpose(&Default::default(), window, cx);
4311 assert_eq!(editor.text(cx), "🏀✋🍐");
4312 assert_eq!(editor.selections.ranges(cx), [11..11]);
4313
4314 editor.transpose(&Default::default(), window, cx);
4315 assert_eq!(editor.text(cx), "🏀🍐✋");
4316 assert_eq!(editor.selections.ranges(cx), [11..11]);
4317
4318 editor
4319 });
4320}
4321
4322#[gpui::test]
4323async fn test_rewrap(cx: &mut TestAppContext) {
4324 init_test(cx, |settings| {
4325 settings.languages.extend([
4326 (
4327 "Markdown".into(),
4328 LanguageSettingsContent {
4329 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4330 ..Default::default()
4331 },
4332 ),
4333 (
4334 "Plain Text".into(),
4335 LanguageSettingsContent {
4336 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4337 ..Default::default()
4338 },
4339 ),
4340 ])
4341 });
4342
4343 let mut cx = EditorTestContext::new(cx).await;
4344
4345 let language_with_c_comments = Arc::new(Language::new(
4346 LanguageConfig {
4347 line_comments: vec!["// ".into()],
4348 ..LanguageConfig::default()
4349 },
4350 None,
4351 ));
4352 let language_with_pound_comments = Arc::new(Language::new(
4353 LanguageConfig {
4354 line_comments: vec!["# ".into()],
4355 ..LanguageConfig::default()
4356 },
4357 None,
4358 ));
4359 let markdown_language = Arc::new(Language::new(
4360 LanguageConfig {
4361 name: "Markdown".into(),
4362 ..LanguageConfig::default()
4363 },
4364 None,
4365 ));
4366 let language_with_doc_comments = Arc::new(Language::new(
4367 LanguageConfig {
4368 line_comments: vec!["// ".into(), "/// ".into()],
4369 ..LanguageConfig::default()
4370 },
4371 Some(tree_sitter_rust::LANGUAGE.into()),
4372 ));
4373
4374 let plaintext_language = Arc::new(Language::new(
4375 LanguageConfig {
4376 name: "Plain Text".into(),
4377 ..LanguageConfig::default()
4378 },
4379 None,
4380 ));
4381
4382 assert_rewrap(
4383 indoc! {"
4384 // ˇ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.
4385 "},
4386 indoc! {"
4387 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4388 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4389 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4390 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4391 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4392 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4393 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4394 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4395 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4396 // porttitor id. Aliquam id accumsan eros.
4397 "},
4398 language_with_c_comments.clone(),
4399 &mut cx,
4400 );
4401
4402 // Test that rewrapping works inside of a selection
4403 assert_rewrap(
4404 indoc! {"
4405 «// 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.ˇ»
4406 "},
4407 indoc! {"
4408 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4409 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4410 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4411 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4412 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4413 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4414 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4415 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4416 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4417 // porttitor id. Aliquam id accumsan eros.ˇ»
4418 "},
4419 language_with_c_comments.clone(),
4420 &mut cx,
4421 );
4422
4423 // Test that cursors that expand to the same region are collapsed.
4424 assert_rewrap(
4425 indoc! {"
4426 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4427 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4428 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4429 // ˇ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.
4430 "},
4431 indoc! {"
4432 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4433 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4434 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4435 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4436 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4437 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4438 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4439 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4440 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4441 // porttitor id. Aliquam id accumsan eros.
4442 "},
4443 language_with_c_comments.clone(),
4444 &mut cx,
4445 );
4446
4447 // Test that non-contiguous selections are treated separately.
4448 assert_rewrap(
4449 indoc! {"
4450 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4451 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4452 //
4453 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4454 // ˇ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.
4455 "},
4456 indoc! {"
4457 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4458 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4459 // auctor, eu lacinia sapien scelerisque.
4460 //
4461 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4462 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4463 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4464 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4465 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4466 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4467 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4468 "},
4469 language_with_c_comments.clone(),
4470 &mut cx,
4471 );
4472
4473 // Test that different comment prefixes are supported.
4474 assert_rewrap(
4475 indoc! {"
4476 # ˇ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.
4477 "},
4478 indoc! {"
4479 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4480 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4481 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4482 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4483 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4484 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4485 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4486 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4487 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4488 # accumsan eros.
4489 "},
4490 language_with_pound_comments.clone(),
4491 &mut cx,
4492 );
4493
4494 // Test that rewrapping is ignored outside of comments in most languages.
4495 assert_rewrap(
4496 indoc! {"
4497 /// Adds two numbers.
4498 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4499 fn add(a: u32, b: u32) -> u32 {
4500 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ˇ
4501 }
4502 "},
4503 indoc! {"
4504 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4505 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4506 fn add(a: u32, b: u32) -> u32 {
4507 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ˇ
4508 }
4509 "},
4510 language_with_doc_comments.clone(),
4511 &mut cx,
4512 );
4513
4514 // Test that rewrapping works in Markdown and Plain Text languages.
4515 assert_rewrap(
4516 indoc! {"
4517 # Hello
4518
4519 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4520 "},
4521 indoc! {"
4522 # Hello
4523
4524 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4525 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4526 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4527 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4528 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4529 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4530 Integer sit amet scelerisque nisi.
4531 "},
4532 markdown_language,
4533 &mut cx,
4534 );
4535
4536 assert_rewrap(
4537 indoc! {"
4538 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.
4539 "},
4540 indoc! {"
4541 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4542 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4543 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4544 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4545 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4546 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4547 Integer sit amet scelerisque nisi.
4548 "},
4549 plaintext_language,
4550 &mut cx,
4551 );
4552
4553 // Test rewrapping unaligned comments in a selection.
4554 assert_rewrap(
4555 indoc! {"
4556 fn foo() {
4557 if true {
4558 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4559 // Praesent semper egestas tellus id dignissim.ˇ»
4560 do_something();
4561 } else {
4562 //
4563 }
4564 }
4565 "},
4566 indoc! {"
4567 fn foo() {
4568 if true {
4569 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4570 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4571 // egestas tellus id dignissim.ˇ»
4572 do_something();
4573 } else {
4574 //
4575 }
4576 }
4577 "},
4578 language_with_doc_comments.clone(),
4579 &mut cx,
4580 );
4581
4582 assert_rewrap(
4583 indoc! {"
4584 fn foo() {
4585 if true {
4586 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4587 // Praesent semper egestas tellus id dignissim.»
4588 do_something();
4589 } else {
4590 //
4591 }
4592
4593 }
4594 "},
4595 indoc! {"
4596 fn foo() {
4597 if true {
4598 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4599 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4600 // egestas tellus id dignissim.»
4601 do_something();
4602 } else {
4603 //
4604 }
4605
4606 }
4607 "},
4608 language_with_doc_comments.clone(),
4609 &mut cx,
4610 );
4611
4612 #[track_caller]
4613 fn assert_rewrap(
4614 unwrapped_text: &str,
4615 wrapped_text: &str,
4616 language: Arc<Language>,
4617 cx: &mut EditorTestContext,
4618 ) {
4619 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4620 cx.set_state(unwrapped_text);
4621 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4622 cx.assert_editor_state(wrapped_text);
4623 }
4624}
4625
4626#[gpui::test]
4627async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4628 init_test(cx, |_| {});
4629
4630 let mut cx = EditorTestContext::new(cx).await;
4631
4632 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4633 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4634 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4635
4636 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4637 cx.set_state("two ˇfour ˇsix ˇ");
4638 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4639 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4640
4641 // Paste again but with only two cursors. Since the number of cursors doesn't
4642 // match the number of slices in the clipboard, the entire clipboard text
4643 // is pasted at each cursor.
4644 cx.set_state("ˇtwo one✅ four three six five ˇ");
4645 cx.update_editor(|e, window, cx| {
4646 e.handle_input("( ", window, cx);
4647 e.paste(&Paste, window, cx);
4648 e.handle_input(") ", window, cx);
4649 });
4650 cx.assert_editor_state(
4651 &([
4652 "( one✅ ",
4653 "three ",
4654 "five ) ˇtwo one✅ four three six five ( one✅ ",
4655 "three ",
4656 "five ) ˇ",
4657 ]
4658 .join("\n")),
4659 );
4660
4661 // Cut with three selections, one of which is full-line.
4662 cx.set_state(indoc! {"
4663 1«2ˇ»3
4664 4ˇ567
4665 «8ˇ»9"});
4666 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4667 cx.assert_editor_state(indoc! {"
4668 1ˇ3
4669 ˇ9"});
4670
4671 // Paste with three selections, noticing how the copied selection that was full-line
4672 // gets inserted before the second cursor.
4673 cx.set_state(indoc! {"
4674 1ˇ3
4675 9ˇ
4676 «oˇ»ne"});
4677 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4678 cx.assert_editor_state(indoc! {"
4679 12ˇ3
4680 4567
4681 9ˇ
4682 8ˇne"});
4683
4684 // Copy with a single cursor only, which writes the whole line into the clipboard.
4685 cx.set_state(indoc! {"
4686 The quick brown
4687 fox juˇmps over
4688 the lazy dog"});
4689 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4690 assert_eq!(
4691 cx.read_from_clipboard()
4692 .and_then(|item| item.text().as_deref().map(str::to_string)),
4693 Some("fox jumps over\n".to_string())
4694 );
4695
4696 // Paste with three selections, noticing how the copied full-line selection is inserted
4697 // before the empty selections but replaces the selection that is non-empty.
4698 cx.set_state(indoc! {"
4699 Tˇhe quick brown
4700 «foˇ»x jumps over
4701 tˇhe lazy dog"});
4702 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4703 cx.assert_editor_state(indoc! {"
4704 fox jumps over
4705 Tˇhe quick brown
4706 fox jumps over
4707 ˇx jumps over
4708 fox jumps over
4709 tˇhe lazy dog"});
4710}
4711
4712#[gpui::test]
4713async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4714 init_test(cx, |_| {});
4715
4716 let mut cx = EditorTestContext::new(cx).await;
4717 let language = Arc::new(Language::new(
4718 LanguageConfig::default(),
4719 Some(tree_sitter_rust::LANGUAGE.into()),
4720 ));
4721 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4722
4723 // Cut an indented block, without the leading whitespace.
4724 cx.set_state(indoc! {"
4725 const a: B = (
4726 c(),
4727 «d(
4728 e,
4729 f
4730 )ˇ»
4731 );
4732 "});
4733 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4734 cx.assert_editor_state(indoc! {"
4735 const a: B = (
4736 c(),
4737 ˇ
4738 );
4739 "});
4740
4741 // Paste it at the same position.
4742 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4743 cx.assert_editor_state(indoc! {"
4744 const a: B = (
4745 c(),
4746 d(
4747 e,
4748 f
4749 )ˇ
4750 );
4751 "});
4752
4753 // Paste it at a line with a lower indent level.
4754 cx.set_state(indoc! {"
4755 ˇ
4756 const a: B = (
4757 c(),
4758 );
4759 "});
4760 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4761 cx.assert_editor_state(indoc! {"
4762 d(
4763 e,
4764 f
4765 )ˇ
4766 const a: B = (
4767 c(),
4768 );
4769 "});
4770
4771 // Cut an indented block, with the leading whitespace.
4772 cx.set_state(indoc! {"
4773 const a: B = (
4774 c(),
4775 « d(
4776 e,
4777 f
4778 )
4779 ˇ»);
4780 "});
4781 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4782 cx.assert_editor_state(indoc! {"
4783 const a: B = (
4784 c(),
4785 ˇ);
4786 "});
4787
4788 // Paste it at the same position.
4789 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4790 cx.assert_editor_state(indoc! {"
4791 const a: B = (
4792 c(),
4793 d(
4794 e,
4795 f
4796 )
4797 ˇ);
4798 "});
4799
4800 // Paste it at a line with a higher indent level.
4801 cx.set_state(indoc! {"
4802 const a: B = (
4803 c(),
4804 d(
4805 e,
4806 fˇ
4807 )
4808 );
4809 "});
4810 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4811 cx.assert_editor_state(indoc! {"
4812 const a: B = (
4813 c(),
4814 d(
4815 e,
4816 f d(
4817 e,
4818 f
4819 )
4820 ˇ
4821 )
4822 );
4823 "});
4824}
4825
4826#[gpui::test]
4827fn test_select_all(cx: &mut TestAppContext) {
4828 init_test(cx, |_| {});
4829
4830 let editor = cx.add_window(|window, cx| {
4831 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4832 build_editor(buffer, window, cx)
4833 });
4834 _ = editor.update(cx, |editor, window, cx| {
4835 editor.select_all(&SelectAll, window, cx);
4836 assert_eq!(
4837 editor.selections.display_ranges(cx),
4838 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4839 );
4840 });
4841}
4842
4843#[gpui::test]
4844fn test_select_line(cx: &mut TestAppContext) {
4845 init_test(cx, |_| {});
4846
4847 let editor = cx.add_window(|window, cx| {
4848 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4849 build_editor(buffer, window, cx)
4850 });
4851 _ = editor.update(cx, |editor, window, cx| {
4852 editor.change_selections(None, window, cx, |s| {
4853 s.select_display_ranges([
4854 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4855 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4856 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4857 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4858 ])
4859 });
4860 editor.select_line(&SelectLine, window, cx);
4861 assert_eq!(
4862 editor.selections.display_ranges(cx),
4863 vec![
4864 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4865 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4866 ]
4867 );
4868 });
4869
4870 _ = editor.update(cx, |editor, window, cx| {
4871 editor.select_line(&SelectLine, window, cx);
4872 assert_eq!(
4873 editor.selections.display_ranges(cx),
4874 vec![
4875 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4876 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4877 ]
4878 );
4879 });
4880
4881 _ = editor.update(cx, |editor, window, cx| {
4882 editor.select_line(&SelectLine, window, cx);
4883 assert_eq!(
4884 editor.selections.display_ranges(cx),
4885 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4886 );
4887 });
4888}
4889
4890#[gpui::test]
4891async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4892 init_test(cx, |_| {});
4893 let mut cx = EditorTestContext::new(cx).await;
4894
4895 #[track_caller]
4896 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
4897 cx.set_state(initial_state);
4898 cx.update_editor(|e, window, cx| {
4899 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
4900 });
4901 cx.assert_editor_state(expected_state);
4902 }
4903
4904 // Selection starts and ends at the middle of lines, left-to-right
4905 test(
4906 &mut cx,
4907 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
4908 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4909 );
4910 // Same thing, right-to-left
4911 test(
4912 &mut cx,
4913 "aa\nb«b\ncc\ndd\neˇ»e\nff",
4914 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4915 );
4916
4917 // Whole buffer, left-to-right, last line *doesn't* end with newline
4918 test(
4919 &mut cx,
4920 "«ˇaa\nbb\ncc\ndd\nee\nff»",
4921 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4922 );
4923 // Same thing, right-to-left
4924 test(
4925 &mut cx,
4926 "«aa\nbb\ncc\ndd\nee\nffˇ»",
4927 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4928 );
4929
4930 // Whole buffer, left-to-right, last line ends with newline
4931 test(
4932 &mut cx,
4933 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
4934 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4935 );
4936 // Same thing, right-to-left
4937 test(
4938 &mut cx,
4939 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
4940 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4941 );
4942
4943 // Starts at the end of a line, ends at the start of another
4944 test(
4945 &mut cx,
4946 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
4947 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
4948 );
4949}
4950
4951#[gpui::test]
4952async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
4953 init_test(cx, |_| {});
4954
4955 let editor = cx.add_window(|window, cx| {
4956 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4957 build_editor(buffer, window, cx)
4958 });
4959
4960 // setup
4961 _ = editor.update(cx, |editor, window, cx| {
4962 editor.fold_creases(
4963 vec![
4964 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4965 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4966 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4967 ],
4968 true,
4969 window,
4970 cx,
4971 );
4972 assert_eq!(
4973 editor.display_text(cx),
4974 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4975 );
4976 });
4977
4978 _ = editor.update(cx, |editor, window, cx| {
4979 editor.change_selections(None, window, cx, |s| {
4980 s.select_display_ranges([
4981 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4982 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4983 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4984 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4985 ])
4986 });
4987 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
4988 assert_eq!(
4989 editor.display_text(cx),
4990 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4991 );
4992 });
4993 EditorTestContext::for_editor(editor, cx)
4994 .await
4995 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
4996
4997 _ = editor.update(cx, |editor, window, cx| {
4998 editor.change_selections(None, window, cx, |s| {
4999 s.select_display_ranges([
5000 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5001 ])
5002 });
5003 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5004 assert_eq!(
5005 editor.display_text(cx),
5006 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5007 );
5008 assert_eq!(
5009 editor.selections.display_ranges(cx),
5010 [
5011 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5012 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5013 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5014 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5015 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5016 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5017 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5018 ]
5019 );
5020 });
5021 EditorTestContext::for_editor(editor, cx)
5022 .await
5023 .assert_editor_state(
5024 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5025 );
5026}
5027
5028#[gpui::test]
5029async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5030 init_test(cx, |_| {});
5031
5032 let mut cx = EditorTestContext::new(cx).await;
5033
5034 cx.set_state(indoc!(
5035 r#"abc
5036 defˇghi
5037
5038 jk
5039 nlmo
5040 "#
5041 ));
5042
5043 cx.update_editor(|editor, window, cx| {
5044 editor.add_selection_above(&Default::default(), window, cx);
5045 });
5046
5047 cx.assert_editor_state(indoc!(
5048 r#"abcˇ
5049 defˇghi
5050
5051 jk
5052 nlmo
5053 "#
5054 ));
5055
5056 cx.update_editor(|editor, window, cx| {
5057 editor.add_selection_above(&Default::default(), window, cx);
5058 });
5059
5060 cx.assert_editor_state(indoc!(
5061 r#"abcˇ
5062 defˇghi
5063
5064 jk
5065 nlmo
5066 "#
5067 ));
5068
5069 cx.update_editor(|editor, window, cx| {
5070 editor.add_selection_below(&Default::default(), window, cx);
5071 });
5072
5073 cx.assert_editor_state(indoc!(
5074 r#"abc
5075 defˇghi
5076
5077 jk
5078 nlmo
5079 "#
5080 ));
5081
5082 cx.update_editor(|editor, window, cx| {
5083 editor.undo_selection(&Default::default(), window, cx);
5084 });
5085
5086 cx.assert_editor_state(indoc!(
5087 r#"abcˇ
5088 defˇghi
5089
5090 jk
5091 nlmo
5092 "#
5093 ));
5094
5095 cx.update_editor(|editor, window, cx| {
5096 editor.redo_selection(&Default::default(), window, cx);
5097 });
5098
5099 cx.assert_editor_state(indoc!(
5100 r#"abc
5101 defˇghi
5102
5103 jk
5104 nlmo
5105 "#
5106 ));
5107
5108 cx.update_editor(|editor, window, cx| {
5109 editor.add_selection_below(&Default::default(), window, cx);
5110 });
5111
5112 cx.assert_editor_state(indoc!(
5113 r#"abc
5114 defˇghi
5115
5116 jk
5117 nlmˇo
5118 "#
5119 ));
5120
5121 cx.update_editor(|editor, window, cx| {
5122 editor.add_selection_below(&Default::default(), window, cx);
5123 });
5124
5125 cx.assert_editor_state(indoc!(
5126 r#"abc
5127 defˇghi
5128
5129 jk
5130 nlmˇo
5131 "#
5132 ));
5133
5134 // change selections
5135 cx.set_state(indoc!(
5136 r#"abc
5137 def«ˇg»hi
5138
5139 jk
5140 nlmo
5141 "#
5142 ));
5143
5144 cx.update_editor(|editor, window, cx| {
5145 editor.add_selection_below(&Default::default(), window, cx);
5146 });
5147
5148 cx.assert_editor_state(indoc!(
5149 r#"abc
5150 def«ˇg»hi
5151
5152 jk
5153 nlm«ˇo»
5154 "#
5155 ));
5156
5157 cx.update_editor(|editor, window, cx| {
5158 editor.add_selection_below(&Default::default(), window, cx);
5159 });
5160
5161 cx.assert_editor_state(indoc!(
5162 r#"abc
5163 def«ˇg»hi
5164
5165 jk
5166 nlm«ˇo»
5167 "#
5168 ));
5169
5170 cx.update_editor(|editor, window, cx| {
5171 editor.add_selection_above(&Default::default(), window, cx);
5172 });
5173
5174 cx.assert_editor_state(indoc!(
5175 r#"abc
5176 def«ˇg»hi
5177
5178 jk
5179 nlmo
5180 "#
5181 ));
5182
5183 cx.update_editor(|editor, window, cx| {
5184 editor.add_selection_above(&Default::default(), window, cx);
5185 });
5186
5187 cx.assert_editor_state(indoc!(
5188 r#"abc
5189 def«ˇg»hi
5190
5191 jk
5192 nlmo
5193 "#
5194 ));
5195
5196 // Change selections again
5197 cx.set_state(indoc!(
5198 r#"a«bc
5199 defgˇ»hi
5200
5201 jk
5202 nlmo
5203 "#
5204 ));
5205
5206 cx.update_editor(|editor, window, cx| {
5207 editor.add_selection_below(&Default::default(), window, cx);
5208 });
5209
5210 cx.assert_editor_state(indoc!(
5211 r#"a«bcˇ»
5212 d«efgˇ»hi
5213
5214 j«kˇ»
5215 nlmo
5216 "#
5217 ));
5218
5219 cx.update_editor(|editor, window, cx| {
5220 editor.add_selection_below(&Default::default(), window, cx);
5221 });
5222 cx.assert_editor_state(indoc!(
5223 r#"a«bcˇ»
5224 d«efgˇ»hi
5225
5226 j«kˇ»
5227 n«lmoˇ»
5228 "#
5229 ));
5230 cx.update_editor(|editor, window, cx| {
5231 editor.add_selection_above(&Default::default(), window, cx);
5232 });
5233
5234 cx.assert_editor_state(indoc!(
5235 r#"a«bcˇ»
5236 d«efgˇ»hi
5237
5238 j«kˇ»
5239 nlmo
5240 "#
5241 ));
5242
5243 // Change selections again
5244 cx.set_state(indoc!(
5245 r#"abc
5246 d«ˇefghi
5247
5248 jk
5249 nlm»o
5250 "#
5251 ));
5252
5253 cx.update_editor(|editor, window, cx| {
5254 editor.add_selection_above(&Default::default(), window, cx);
5255 });
5256
5257 cx.assert_editor_state(indoc!(
5258 r#"a«ˇbc»
5259 d«ˇef»ghi
5260
5261 j«ˇk»
5262 n«ˇlm»o
5263 "#
5264 ));
5265
5266 cx.update_editor(|editor, window, cx| {
5267 editor.add_selection_below(&Default::default(), window, cx);
5268 });
5269
5270 cx.assert_editor_state(indoc!(
5271 r#"abc
5272 d«ˇef»ghi
5273
5274 j«ˇk»
5275 n«ˇlm»o
5276 "#
5277 ));
5278}
5279
5280#[gpui::test]
5281async fn test_select_next(cx: &mut gpui::TestAppContext) {
5282 init_test(cx, |_| {});
5283
5284 let mut cx = EditorTestContext::new(cx).await;
5285 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5286
5287 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5288 .unwrap();
5289 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5290
5291 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5292 .unwrap();
5293 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5294
5295 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5296 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5297
5298 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5299 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5300
5301 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5302 .unwrap();
5303 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5304
5305 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5306 .unwrap();
5307 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5308}
5309
5310#[gpui::test]
5311async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5312 init_test(cx, |_| {});
5313
5314 let mut cx = EditorTestContext::new(cx).await;
5315
5316 // Test caret-only selections
5317 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5318 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5319 .unwrap();
5320 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5321
5322 // Test left-to-right selections
5323 cx.set_state("abc\n«abcˇ»\nabc");
5324 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5325 .unwrap();
5326 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5327
5328 // Test right-to-left selections
5329 cx.set_state("abc\n«ˇabc»\nabc");
5330 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5331 .unwrap();
5332 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5333
5334 // Test selecting whitespace with caret selection
5335 cx.set_state("abc\nˇ abc\nabc");
5336 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5337 .unwrap();
5338 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5339
5340 // Test selecting whitespace with left-to-right selection
5341 cx.set_state("abc\n«ˇ »abc\nabc");
5342 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5343 .unwrap();
5344 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5345
5346 // Test no matches with right-to-left selection
5347 cx.set_state("abc\n« ˇ»abc\nabc");
5348 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5349 .unwrap();
5350 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5351}
5352
5353#[gpui::test]
5354async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5355 init_test(cx, |_| {});
5356
5357 let mut cx = EditorTestContext::new(cx).await;
5358 cx.set_state(
5359 r#"let foo = 2;
5360lˇet foo = 2;
5361let fooˇ = 2;
5362let foo = 2;
5363let foo = ˇ2;"#,
5364 );
5365
5366 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5367 .unwrap();
5368 cx.assert_editor_state(
5369 r#"let foo = 2;
5370«letˇ» foo = 2;
5371let «fooˇ» = 2;
5372let foo = 2;
5373let foo = «2ˇ»;"#,
5374 );
5375
5376 // noop for multiple selections with different contents
5377 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5378 .unwrap();
5379 cx.assert_editor_state(
5380 r#"let foo = 2;
5381«letˇ» foo = 2;
5382let «fooˇ» = 2;
5383let foo = 2;
5384let foo = «2ˇ»;"#,
5385 );
5386}
5387
5388#[gpui::test]
5389async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5390 init_test(cx, |_| {});
5391
5392 let mut cx =
5393 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5394
5395 cx.assert_editor_state(indoc! {"
5396 ˇbbb
5397 ccc
5398
5399 bbb
5400 ccc
5401 "});
5402 cx.dispatch_action(SelectPrevious::default());
5403 cx.assert_editor_state(indoc! {"
5404 «bbbˇ»
5405 ccc
5406
5407 bbb
5408 ccc
5409 "});
5410 cx.dispatch_action(SelectPrevious::default());
5411 cx.assert_editor_state(indoc! {"
5412 «bbbˇ»
5413 ccc
5414
5415 «bbbˇ»
5416 ccc
5417 "});
5418}
5419
5420#[gpui::test]
5421async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5422 init_test(cx, |_| {});
5423
5424 let mut cx = EditorTestContext::new(cx).await;
5425 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5426
5427 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5428 .unwrap();
5429 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5430
5431 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5432 .unwrap();
5433 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5434
5435 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5436 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5437
5438 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5439 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5440
5441 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5442 .unwrap();
5443 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5444
5445 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5446 .unwrap();
5447 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5448
5449 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5450 .unwrap();
5451 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5452}
5453
5454#[gpui::test]
5455async fn test_select_previous_empty_buffer(cx: &mut gpui::TestAppContext) {
5456 init_test(cx, |_| {});
5457
5458 let mut cx = EditorTestContext::new(cx).await;
5459 cx.set_state("aˇ");
5460
5461 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5462 .unwrap();
5463 cx.assert_editor_state("«aˇ»");
5464 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5465 .unwrap();
5466 cx.assert_editor_state("«aˇ»");
5467}
5468
5469#[gpui::test]
5470async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5471 init_test(cx, |_| {});
5472
5473 let mut cx = EditorTestContext::new(cx).await;
5474 cx.set_state(
5475 r#"let foo = 2;
5476lˇet foo = 2;
5477let fooˇ = 2;
5478let foo = 2;
5479let foo = ˇ2;"#,
5480 );
5481
5482 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5483 .unwrap();
5484 cx.assert_editor_state(
5485 r#"let foo = 2;
5486«letˇ» foo = 2;
5487let «fooˇ» = 2;
5488let foo = 2;
5489let foo = «2ˇ»;"#,
5490 );
5491
5492 // noop for multiple selections with different contents
5493 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5494 .unwrap();
5495 cx.assert_editor_state(
5496 r#"let foo = 2;
5497«letˇ» foo = 2;
5498let «fooˇ» = 2;
5499let foo = 2;
5500let foo = «2ˇ»;"#,
5501 );
5502}
5503
5504#[gpui::test]
5505async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5506 init_test(cx, |_| {});
5507
5508 let mut cx = EditorTestContext::new(cx).await;
5509 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5510
5511 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5512 .unwrap();
5513 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5514
5515 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5516 .unwrap();
5517 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5518
5519 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5520 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5521
5522 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5523 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5524
5525 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5526 .unwrap();
5527 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5528
5529 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5530 .unwrap();
5531 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5532}
5533
5534#[gpui::test]
5535async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5536 init_test(cx, |_| {});
5537
5538 let language = Arc::new(Language::new(
5539 LanguageConfig::default(),
5540 Some(tree_sitter_rust::LANGUAGE.into()),
5541 ));
5542
5543 let text = r#"
5544 use mod1::mod2::{mod3, mod4};
5545
5546 fn fn_1(param1: bool, param2: &str) {
5547 let var1 = "text";
5548 }
5549 "#
5550 .unindent();
5551
5552 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5553 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5554 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5555
5556 editor
5557 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5558 .await;
5559
5560 editor.update_in(cx, |editor, window, cx| {
5561 editor.change_selections(None, window, cx, |s| {
5562 s.select_display_ranges([
5563 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5564 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5565 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5566 ]);
5567 });
5568 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5569 });
5570 editor.update(cx, |editor, cx| {
5571 assert_text_with_selections(
5572 editor,
5573 indoc! {r#"
5574 use mod1::mod2::{mod3, «mod4ˇ»};
5575
5576 fn fn_1«ˇ(param1: bool, param2: &str)» {
5577 let var1 = "«textˇ»";
5578 }
5579 "#},
5580 cx,
5581 );
5582 });
5583
5584 editor.update_in(cx, |editor, window, cx| {
5585 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5586 });
5587 editor.update(cx, |editor, cx| {
5588 assert_text_with_selections(
5589 editor,
5590 indoc! {r#"
5591 use mod1::mod2::«{mod3, mod4}ˇ»;
5592
5593 «ˇfn fn_1(param1: bool, param2: &str) {
5594 let var1 = "text";
5595 }»
5596 "#},
5597 cx,
5598 );
5599 });
5600
5601 editor.update_in(cx, |editor, window, cx| {
5602 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5603 });
5604 assert_eq!(
5605 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5606 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5607 );
5608
5609 // Trying to expand the selected syntax node one more time has no effect.
5610 editor.update_in(cx, |editor, window, cx| {
5611 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5612 });
5613 assert_eq!(
5614 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5615 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5616 );
5617
5618 editor.update_in(cx, |editor, window, cx| {
5619 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, 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_smaller_syntax_node(&SelectSmallerSyntaxNode, 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_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5654 });
5655 editor.update(cx, |editor, cx| {
5656 assert_text_with_selections(
5657 editor,
5658 indoc! {r#"
5659 use mod1::mod2::{mod3, mo«ˇ»d4};
5660
5661 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5662 let var1 = "te«ˇ»xt";
5663 }
5664 "#},
5665 cx,
5666 );
5667 });
5668
5669 // Trying to shrink the selected syntax node one more time has no effect.
5670 editor.update_in(cx, |editor, window, cx| {
5671 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5672 });
5673 editor.update_in(cx, |editor, _, cx| {
5674 assert_text_with_selections(
5675 editor,
5676 indoc! {r#"
5677 use mod1::mod2::{mod3, mo«ˇ»d4};
5678
5679 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5680 let var1 = "te«ˇ»xt";
5681 }
5682 "#},
5683 cx,
5684 );
5685 });
5686
5687 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5688 // a fold.
5689 editor.update_in(cx, |editor, window, cx| {
5690 editor.fold_creases(
5691 vec![
5692 Crease::simple(
5693 Point::new(0, 21)..Point::new(0, 24),
5694 FoldPlaceholder::test(),
5695 ),
5696 Crease::simple(
5697 Point::new(3, 20)..Point::new(3, 22),
5698 FoldPlaceholder::test(),
5699 ),
5700 ],
5701 true,
5702 window,
5703 cx,
5704 );
5705 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5706 });
5707 editor.update(cx, |editor, cx| {
5708 assert_text_with_selections(
5709 editor,
5710 indoc! {r#"
5711 use mod1::mod2::«{mod3, mod4}ˇ»;
5712
5713 fn fn_1«ˇ(param1: bool, param2: &str)» {
5714 «let var1 = "text";ˇ»
5715 }
5716 "#},
5717 cx,
5718 );
5719 });
5720}
5721
5722#[gpui::test]
5723async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
5724 init_test(cx, |_| {});
5725
5726 let base_text = r#"
5727 impl A {
5728 // this is an uncommitted comment
5729
5730 fn b() {
5731 c();
5732 }
5733
5734 // this is another uncommitted comment
5735
5736 fn d() {
5737 // e
5738 // f
5739 }
5740 }
5741
5742 fn g() {
5743 // h
5744 }
5745 "#
5746 .unindent();
5747
5748 let text = r#"
5749 ˇimpl A {
5750
5751 fn b() {
5752 c();
5753 }
5754
5755 fn d() {
5756 // e
5757 // f
5758 }
5759 }
5760
5761 fn g() {
5762 // h
5763 }
5764 "#
5765 .unindent();
5766
5767 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5768 cx.set_state(&text);
5769 cx.set_diff_base(&base_text);
5770 cx.update_editor(|editor, window, cx| {
5771 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5772 });
5773
5774 cx.assert_state_with_diff(
5775 "
5776 ˇimpl A {
5777 - // this is an uncommitted comment
5778
5779 fn b() {
5780 c();
5781 }
5782
5783 - // this is another uncommitted comment
5784 -
5785 fn d() {
5786 // e
5787 // f
5788 }
5789 }
5790
5791 fn g() {
5792 // h
5793 }
5794 "
5795 .unindent(),
5796 );
5797
5798 let expected_display_text = "
5799 impl A {
5800 // this is an uncommitted comment
5801
5802 fn b() {
5803 ⋯
5804 }
5805
5806 // this is another uncommitted comment
5807
5808 fn d() {
5809 ⋯
5810 }
5811 }
5812
5813 fn g() {
5814 ⋯
5815 }
5816 "
5817 .unindent();
5818
5819 cx.update_editor(|editor, window, cx| {
5820 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5821 assert_eq!(editor.display_text(cx), expected_display_text);
5822 });
5823}
5824
5825#[gpui::test]
5826async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5827 init_test(cx, |_| {});
5828
5829 let language = Arc::new(
5830 Language::new(
5831 LanguageConfig {
5832 brackets: BracketPairConfig {
5833 pairs: vec![
5834 BracketPair {
5835 start: "{".to_string(),
5836 end: "}".to_string(),
5837 close: false,
5838 surround: false,
5839 newline: true,
5840 },
5841 BracketPair {
5842 start: "(".to_string(),
5843 end: ")".to_string(),
5844 close: false,
5845 surround: false,
5846 newline: true,
5847 },
5848 ],
5849 ..Default::default()
5850 },
5851 ..Default::default()
5852 },
5853 Some(tree_sitter_rust::LANGUAGE.into()),
5854 )
5855 .with_indents_query(
5856 r#"
5857 (_ "(" ")" @end) @indent
5858 (_ "{" "}" @end) @indent
5859 "#,
5860 )
5861 .unwrap(),
5862 );
5863
5864 let text = "fn a() {}";
5865
5866 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5867 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5868 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5869 editor
5870 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5871 .await;
5872
5873 editor.update_in(cx, |editor, window, cx| {
5874 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5875 editor.newline(&Newline, window, cx);
5876 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5877 assert_eq!(
5878 editor.selections.ranges(cx),
5879 &[
5880 Point::new(1, 4)..Point::new(1, 4),
5881 Point::new(3, 4)..Point::new(3, 4),
5882 Point::new(5, 0)..Point::new(5, 0)
5883 ]
5884 );
5885 });
5886}
5887
5888#[gpui::test]
5889async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5890 init_test(cx, |_| {});
5891
5892 {
5893 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5894 cx.set_state(indoc! {"
5895 impl A {
5896
5897 fn b() {}
5898
5899 «fn c() {
5900
5901 }ˇ»
5902 }
5903 "});
5904
5905 cx.update_editor(|editor, window, cx| {
5906 editor.autoindent(&Default::default(), window, cx);
5907 });
5908
5909 cx.assert_editor_state(indoc! {"
5910 impl A {
5911
5912 fn b() {}
5913
5914 «fn c() {
5915
5916 }ˇ»
5917 }
5918 "});
5919 }
5920
5921 {
5922 let mut cx = EditorTestContext::new_multibuffer(
5923 cx,
5924 [indoc! { "
5925 impl A {
5926 «
5927 // a
5928 fn b(){}
5929 »
5930 «
5931 }
5932 fn c(){}
5933 »
5934 "}],
5935 );
5936
5937 let buffer = cx.update_editor(|editor, _, cx| {
5938 let buffer = editor.buffer().update(cx, |buffer, _| {
5939 buffer.all_buffers().iter().next().unwrap().clone()
5940 });
5941 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5942 buffer
5943 });
5944
5945 cx.run_until_parked();
5946 cx.update_editor(|editor, window, cx| {
5947 editor.select_all(&Default::default(), window, cx);
5948 editor.autoindent(&Default::default(), window, cx)
5949 });
5950 cx.run_until_parked();
5951
5952 cx.update(|_, cx| {
5953 pretty_assertions::assert_eq!(
5954 buffer.read(cx).text(),
5955 indoc! { "
5956 impl A {
5957
5958 // a
5959 fn b(){}
5960
5961
5962 }
5963 fn c(){}
5964
5965 " }
5966 )
5967 });
5968 }
5969}
5970
5971#[gpui::test]
5972async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5973 init_test(cx, |_| {});
5974
5975 let mut cx = EditorTestContext::new(cx).await;
5976
5977 let language = Arc::new(Language::new(
5978 LanguageConfig {
5979 brackets: BracketPairConfig {
5980 pairs: vec![
5981 BracketPair {
5982 start: "{".to_string(),
5983 end: "}".to_string(),
5984 close: true,
5985 surround: true,
5986 newline: true,
5987 },
5988 BracketPair {
5989 start: "(".to_string(),
5990 end: ")".to_string(),
5991 close: true,
5992 surround: true,
5993 newline: true,
5994 },
5995 BracketPair {
5996 start: "/*".to_string(),
5997 end: " */".to_string(),
5998 close: true,
5999 surround: true,
6000 newline: true,
6001 },
6002 BracketPair {
6003 start: "[".to_string(),
6004 end: "]".to_string(),
6005 close: false,
6006 surround: false,
6007 newline: true,
6008 },
6009 BracketPair {
6010 start: "\"".to_string(),
6011 end: "\"".to_string(),
6012 close: true,
6013 surround: true,
6014 newline: false,
6015 },
6016 BracketPair {
6017 start: "<".to_string(),
6018 end: ">".to_string(),
6019 close: false,
6020 surround: true,
6021 newline: true,
6022 },
6023 ],
6024 ..Default::default()
6025 },
6026 autoclose_before: "})]".to_string(),
6027 ..Default::default()
6028 },
6029 Some(tree_sitter_rust::LANGUAGE.into()),
6030 ));
6031
6032 cx.language_registry().add(language.clone());
6033 cx.update_buffer(|buffer, cx| {
6034 buffer.set_language(Some(language), cx);
6035 });
6036
6037 cx.set_state(
6038 &r#"
6039 🏀ˇ
6040 εˇ
6041 ❤️ˇ
6042 "#
6043 .unindent(),
6044 );
6045
6046 // autoclose multiple nested brackets at multiple cursors
6047 cx.update_editor(|editor, window, cx| {
6048 editor.handle_input("{", window, cx);
6049 editor.handle_input("{", window, cx);
6050 editor.handle_input("{", window, cx);
6051 });
6052 cx.assert_editor_state(
6053 &"
6054 🏀{{{ˇ}}}
6055 ε{{{ˇ}}}
6056 ❤️{{{ˇ}}}
6057 "
6058 .unindent(),
6059 );
6060
6061 // insert a different closing bracket
6062 cx.update_editor(|editor, window, cx| {
6063 editor.handle_input(")", window, cx);
6064 });
6065 cx.assert_editor_state(
6066 &"
6067 🏀{{{)ˇ}}}
6068 ε{{{)ˇ}}}
6069 ❤️{{{)ˇ}}}
6070 "
6071 .unindent(),
6072 );
6073
6074 // skip over the auto-closed brackets when typing a closing bracket
6075 cx.update_editor(|editor, window, cx| {
6076 editor.move_right(&MoveRight, window, cx);
6077 editor.handle_input("}", window, cx);
6078 editor.handle_input("}", window, cx);
6079 editor.handle_input("}", window, cx);
6080 });
6081 cx.assert_editor_state(
6082 &"
6083 🏀{{{)}}}}ˇ
6084 ε{{{)}}}}ˇ
6085 ❤️{{{)}}}}ˇ
6086 "
6087 .unindent(),
6088 );
6089
6090 // autoclose multi-character pairs
6091 cx.set_state(
6092 &"
6093 ˇ
6094 ˇ
6095 "
6096 .unindent(),
6097 );
6098 cx.update_editor(|editor, window, cx| {
6099 editor.handle_input("/", window, cx);
6100 editor.handle_input("*", window, cx);
6101 });
6102 cx.assert_editor_state(
6103 &"
6104 /*ˇ */
6105 /*ˇ */
6106 "
6107 .unindent(),
6108 );
6109
6110 // one cursor autocloses a multi-character pair, one cursor
6111 // does not autoclose.
6112 cx.set_state(
6113 &"
6114 /ˇ
6115 ˇ
6116 "
6117 .unindent(),
6118 );
6119 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6120 cx.assert_editor_state(
6121 &"
6122 /*ˇ */
6123 *ˇ
6124 "
6125 .unindent(),
6126 );
6127
6128 // Don't autoclose if the next character isn't whitespace and isn't
6129 // listed in the language's "autoclose_before" section.
6130 cx.set_state("ˇa b");
6131 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6132 cx.assert_editor_state("{ˇa b");
6133
6134 // Don't autoclose if `close` is false for the bracket pair
6135 cx.set_state("ˇ");
6136 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6137 cx.assert_editor_state("[ˇ");
6138
6139 // Surround with brackets if text is selected
6140 cx.set_state("«aˇ» b");
6141 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6142 cx.assert_editor_state("{«aˇ»} b");
6143
6144 // Autclose pair where the start and end characters are the same
6145 cx.set_state("aˇ");
6146 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6147 cx.assert_editor_state("a\"ˇ\"");
6148 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6149 cx.assert_editor_state("a\"\"ˇ");
6150
6151 // Don't autoclose pair if autoclose is disabled
6152 cx.set_state("ˇ");
6153 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6154 cx.assert_editor_state("<ˇ");
6155
6156 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6157 cx.set_state("«aˇ» b");
6158 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6159 cx.assert_editor_state("<«aˇ»> b");
6160}
6161
6162#[gpui::test]
6163async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
6164 init_test(cx, |settings| {
6165 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6166 });
6167
6168 let mut cx = EditorTestContext::new(cx).await;
6169
6170 let language = Arc::new(Language::new(
6171 LanguageConfig {
6172 brackets: BracketPairConfig {
6173 pairs: vec![
6174 BracketPair {
6175 start: "{".to_string(),
6176 end: "}".to_string(),
6177 close: true,
6178 surround: true,
6179 newline: true,
6180 },
6181 BracketPair {
6182 start: "(".to_string(),
6183 end: ")".to_string(),
6184 close: true,
6185 surround: true,
6186 newline: true,
6187 },
6188 BracketPair {
6189 start: "[".to_string(),
6190 end: "]".to_string(),
6191 close: false,
6192 surround: false,
6193 newline: true,
6194 },
6195 ],
6196 ..Default::default()
6197 },
6198 autoclose_before: "})]".to_string(),
6199 ..Default::default()
6200 },
6201 Some(tree_sitter_rust::LANGUAGE.into()),
6202 ));
6203
6204 cx.language_registry().add(language.clone());
6205 cx.update_buffer(|buffer, cx| {
6206 buffer.set_language(Some(language), cx);
6207 });
6208
6209 cx.set_state(
6210 &"
6211 ˇ
6212 ˇ
6213 ˇ
6214 "
6215 .unindent(),
6216 );
6217
6218 // ensure only matching closing brackets are skipped over
6219 cx.update_editor(|editor, window, cx| {
6220 editor.handle_input("}", window, cx);
6221 editor.move_left(&MoveLeft, window, cx);
6222 editor.handle_input(")", window, cx);
6223 editor.move_left(&MoveLeft, window, cx);
6224 });
6225 cx.assert_editor_state(
6226 &"
6227 ˇ)}
6228 ˇ)}
6229 ˇ)}
6230 "
6231 .unindent(),
6232 );
6233
6234 // skip-over closing brackets at multiple cursors
6235 cx.update_editor(|editor, window, cx| {
6236 editor.handle_input(")", window, cx);
6237 editor.handle_input("}", window, cx);
6238 });
6239 cx.assert_editor_state(
6240 &"
6241 )}ˇ
6242 )}ˇ
6243 )}ˇ
6244 "
6245 .unindent(),
6246 );
6247
6248 // ignore non-close brackets
6249 cx.update_editor(|editor, window, cx| {
6250 editor.handle_input("]", window, cx);
6251 editor.move_left(&MoveLeft, window, cx);
6252 editor.handle_input("]", window, cx);
6253 });
6254 cx.assert_editor_state(
6255 &"
6256 )}]ˇ]
6257 )}]ˇ]
6258 )}]ˇ]
6259 "
6260 .unindent(),
6261 );
6262}
6263
6264#[gpui::test]
6265async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
6266 init_test(cx, |_| {});
6267
6268 let mut cx = EditorTestContext::new(cx).await;
6269
6270 let html_language = Arc::new(
6271 Language::new(
6272 LanguageConfig {
6273 name: "HTML".into(),
6274 brackets: BracketPairConfig {
6275 pairs: vec![
6276 BracketPair {
6277 start: "<".into(),
6278 end: ">".into(),
6279 close: true,
6280 ..Default::default()
6281 },
6282 BracketPair {
6283 start: "{".into(),
6284 end: "}".into(),
6285 close: true,
6286 ..Default::default()
6287 },
6288 BracketPair {
6289 start: "(".into(),
6290 end: ")".into(),
6291 close: true,
6292 ..Default::default()
6293 },
6294 ],
6295 ..Default::default()
6296 },
6297 autoclose_before: "})]>".into(),
6298 ..Default::default()
6299 },
6300 Some(tree_sitter_html::LANGUAGE.into()),
6301 )
6302 .with_injection_query(
6303 r#"
6304 (script_element
6305 (raw_text) @injection.content
6306 (#set! injection.language "javascript"))
6307 "#,
6308 )
6309 .unwrap(),
6310 );
6311
6312 let javascript_language = Arc::new(Language::new(
6313 LanguageConfig {
6314 name: "JavaScript".into(),
6315 brackets: BracketPairConfig {
6316 pairs: vec![
6317 BracketPair {
6318 start: "/*".into(),
6319 end: " */".into(),
6320 close: true,
6321 ..Default::default()
6322 },
6323 BracketPair {
6324 start: "{".into(),
6325 end: "}".into(),
6326 close: true,
6327 ..Default::default()
6328 },
6329 BracketPair {
6330 start: "(".into(),
6331 end: ")".into(),
6332 close: true,
6333 ..Default::default()
6334 },
6335 ],
6336 ..Default::default()
6337 },
6338 autoclose_before: "})]>".into(),
6339 ..Default::default()
6340 },
6341 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6342 ));
6343
6344 cx.language_registry().add(html_language.clone());
6345 cx.language_registry().add(javascript_language.clone());
6346
6347 cx.update_buffer(|buffer, cx| {
6348 buffer.set_language(Some(html_language), cx);
6349 });
6350
6351 cx.set_state(
6352 &r#"
6353 <body>ˇ
6354 <script>
6355 var x = 1;ˇ
6356 </script>
6357 </body>ˇ
6358 "#
6359 .unindent(),
6360 );
6361
6362 // Precondition: different languages are active at different locations.
6363 cx.update_editor(|editor, window, cx| {
6364 let snapshot = editor.snapshot(window, cx);
6365 let cursors = editor.selections.ranges::<usize>(cx);
6366 let languages = cursors
6367 .iter()
6368 .map(|c| snapshot.language_at(c.start).unwrap().name())
6369 .collect::<Vec<_>>();
6370 assert_eq!(
6371 languages,
6372 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6373 );
6374 });
6375
6376 // Angle brackets autoclose in HTML, but not JavaScript.
6377 cx.update_editor(|editor, window, cx| {
6378 editor.handle_input("<", window, cx);
6379 editor.handle_input("a", window, cx);
6380 });
6381 cx.assert_editor_state(
6382 &r#"
6383 <body><aˇ>
6384 <script>
6385 var x = 1;<aˇ
6386 </script>
6387 </body><aˇ>
6388 "#
6389 .unindent(),
6390 );
6391
6392 // Curly braces and parens autoclose in both HTML and JavaScript.
6393 cx.update_editor(|editor, window, cx| {
6394 editor.handle_input(" b=", window, cx);
6395 editor.handle_input("{", window, cx);
6396 editor.handle_input("c", window, cx);
6397 editor.handle_input("(", window, cx);
6398 });
6399 cx.assert_editor_state(
6400 &r#"
6401 <body><a b={c(ˇ)}>
6402 <script>
6403 var x = 1;<a b={c(ˇ)}
6404 </script>
6405 </body><a b={c(ˇ)}>
6406 "#
6407 .unindent(),
6408 );
6409
6410 // Brackets that were already autoclosed are skipped.
6411 cx.update_editor(|editor, window, cx| {
6412 editor.handle_input(")", window, cx);
6413 editor.handle_input("d", window, cx);
6414 editor.handle_input("}", window, cx);
6415 });
6416 cx.assert_editor_state(
6417 &r#"
6418 <body><a b={c()d}ˇ>
6419 <script>
6420 var x = 1;<a b={c()d}ˇ
6421 </script>
6422 </body><a b={c()d}ˇ>
6423 "#
6424 .unindent(),
6425 );
6426 cx.update_editor(|editor, window, cx| {
6427 editor.handle_input(">", window, cx);
6428 });
6429 cx.assert_editor_state(
6430 &r#"
6431 <body><a b={c()d}>ˇ
6432 <script>
6433 var x = 1;<a b={c()d}>ˇ
6434 </script>
6435 </body><a b={c()d}>ˇ
6436 "#
6437 .unindent(),
6438 );
6439
6440 // Reset
6441 cx.set_state(
6442 &r#"
6443 <body>ˇ
6444 <script>
6445 var x = 1;ˇ
6446 </script>
6447 </body>ˇ
6448 "#
6449 .unindent(),
6450 );
6451
6452 cx.update_editor(|editor, window, cx| {
6453 editor.handle_input("<", window, cx);
6454 });
6455 cx.assert_editor_state(
6456 &r#"
6457 <body><ˇ>
6458 <script>
6459 var x = 1;<ˇ
6460 </script>
6461 </body><ˇ>
6462 "#
6463 .unindent(),
6464 );
6465
6466 // When backspacing, the closing angle brackets are removed.
6467 cx.update_editor(|editor, window, cx| {
6468 editor.backspace(&Backspace, window, cx);
6469 });
6470 cx.assert_editor_state(
6471 &r#"
6472 <body>ˇ
6473 <script>
6474 var x = 1;ˇ
6475 </script>
6476 </body>ˇ
6477 "#
6478 .unindent(),
6479 );
6480
6481 // Block comments autoclose in JavaScript, but not HTML.
6482 cx.update_editor(|editor, window, cx| {
6483 editor.handle_input("/", window, cx);
6484 editor.handle_input("*", window, cx);
6485 });
6486 cx.assert_editor_state(
6487 &r#"
6488 <body>/*ˇ
6489 <script>
6490 var x = 1;/*ˇ */
6491 </script>
6492 </body>/*ˇ
6493 "#
6494 .unindent(),
6495 );
6496}
6497
6498#[gpui::test]
6499async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6500 init_test(cx, |_| {});
6501
6502 let mut cx = EditorTestContext::new(cx).await;
6503
6504 let rust_language = Arc::new(
6505 Language::new(
6506 LanguageConfig {
6507 name: "Rust".into(),
6508 brackets: serde_json::from_value(json!([
6509 { "start": "{", "end": "}", "close": true, "newline": true },
6510 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6511 ]))
6512 .unwrap(),
6513 autoclose_before: "})]>".into(),
6514 ..Default::default()
6515 },
6516 Some(tree_sitter_rust::LANGUAGE.into()),
6517 )
6518 .with_override_query("(string_literal) @string")
6519 .unwrap(),
6520 );
6521
6522 cx.language_registry().add(rust_language.clone());
6523 cx.update_buffer(|buffer, cx| {
6524 buffer.set_language(Some(rust_language), cx);
6525 });
6526
6527 cx.set_state(
6528 &r#"
6529 let x = ˇ
6530 "#
6531 .unindent(),
6532 );
6533
6534 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6535 cx.update_editor(|editor, window, cx| {
6536 editor.handle_input("\"", window, cx);
6537 });
6538 cx.assert_editor_state(
6539 &r#"
6540 let x = "ˇ"
6541 "#
6542 .unindent(),
6543 );
6544
6545 // Inserting another quotation mark. The cursor moves across the existing
6546 // automatically-inserted quotation mark.
6547 cx.update_editor(|editor, window, cx| {
6548 editor.handle_input("\"", window, cx);
6549 });
6550 cx.assert_editor_state(
6551 &r#"
6552 let x = ""ˇ
6553 "#
6554 .unindent(),
6555 );
6556
6557 // Reset
6558 cx.set_state(
6559 &r#"
6560 let x = ˇ
6561 "#
6562 .unindent(),
6563 );
6564
6565 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6566 cx.update_editor(|editor, window, cx| {
6567 editor.handle_input("\"", window, cx);
6568 editor.handle_input(" ", window, cx);
6569 editor.move_left(&Default::default(), window, cx);
6570 editor.handle_input("\\", window, cx);
6571 editor.handle_input("\"", window, cx);
6572 });
6573 cx.assert_editor_state(
6574 &r#"
6575 let x = "\"ˇ "
6576 "#
6577 .unindent(),
6578 );
6579
6580 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6581 // mark. Nothing is inserted.
6582 cx.update_editor(|editor, window, cx| {
6583 editor.move_right(&Default::default(), window, cx);
6584 editor.handle_input("\"", window, cx);
6585 });
6586 cx.assert_editor_state(
6587 &r#"
6588 let x = "\" "ˇ
6589 "#
6590 .unindent(),
6591 );
6592}
6593
6594#[gpui::test]
6595async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6596 init_test(cx, |_| {});
6597
6598 let language = Arc::new(Language::new(
6599 LanguageConfig {
6600 brackets: BracketPairConfig {
6601 pairs: vec![
6602 BracketPair {
6603 start: "{".to_string(),
6604 end: "}".to_string(),
6605 close: true,
6606 surround: true,
6607 newline: true,
6608 },
6609 BracketPair {
6610 start: "/* ".to_string(),
6611 end: "*/".to_string(),
6612 close: true,
6613 surround: true,
6614 ..Default::default()
6615 },
6616 ],
6617 ..Default::default()
6618 },
6619 ..Default::default()
6620 },
6621 Some(tree_sitter_rust::LANGUAGE.into()),
6622 ));
6623
6624 let text = r#"
6625 a
6626 b
6627 c
6628 "#
6629 .unindent();
6630
6631 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6632 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6633 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6634 editor
6635 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6636 .await;
6637
6638 editor.update_in(cx, |editor, window, cx| {
6639 editor.change_selections(None, window, cx, |s| {
6640 s.select_display_ranges([
6641 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6642 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6643 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6644 ])
6645 });
6646
6647 editor.handle_input("{", window, cx);
6648 editor.handle_input("{", window, cx);
6649 editor.handle_input("{", window, cx);
6650 assert_eq!(
6651 editor.text(cx),
6652 "
6653 {{{a}}}
6654 {{{b}}}
6655 {{{c}}}
6656 "
6657 .unindent()
6658 );
6659 assert_eq!(
6660 editor.selections.display_ranges(cx),
6661 [
6662 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6663 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6664 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6665 ]
6666 );
6667
6668 editor.undo(&Undo, window, cx);
6669 editor.undo(&Undo, window, cx);
6670 editor.undo(&Undo, window, cx);
6671 assert_eq!(
6672 editor.text(cx),
6673 "
6674 a
6675 b
6676 c
6677 "
6678 .unindent()
6679 );
6680 assert_eq!(
6681 editor.selections.display_ranges(cx),
6682 [
6683 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6684 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6685 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6686 ]
6687 );
6688
6689 // Ensure inserting the first character of a multi-byte bracket pair
6690 // doesn't surround the selections with the bracket.
6691 editor.handle_input("/", window, cx);
6692 assert_eq!(
6693 editor.text(cx),
6694 "
6695 /
6696 /
6697 /
6698 "
6699 .unindent()
6700 );
6701 assert_eq!(
6702 editor.selections.display_ranges(cx),
6703 [
6704 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6705 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6706 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6707 ]
6708 );
6709
6710 editor.undo(&Undo, window, cx);
6711 assert_eq!(
6712 editor.text(cx),
6713 "
6714 a
6715 b
6716 c
6717 "
6718 .unindent()
6719 );
6720 assert_eq!(
6721 editor.selections.display_ranges(cx),
6722 [
6723 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6724 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6725 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6726 ]
6727 );
6728
6729 // Ensure inserting the last character of a multi-byte bracket pair
6730 // doesn't surround the selections with the bracket.
6731 editor.handle_input("*", window, cx);
6732 assert_eq!(
6733 editor.text(cx),
6734 "
6735 *
6736 *
6737 *
6738 "
6739 .unindent()
6740 );
6741 assert_eq!(
6742 editor.selections.display_ranges(cx),
6743 [
6744 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6745 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6746 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6747 ]
6748 );
6749 });
6750}
6751
6752#[gpui::test]
6753async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6754 init_test(cx, |_| {});
6755
6756 let language = Arc::new(Language::new(
6757 LanguageConfig {
6758 brackets: BracketPairConfig {
6759 pairs: vec![BracketPair {
6760 start: "{".to_string(),
6761 end: "}".to_string(),
6762 close: true,
6763 surround: true,
6764 newline: true,
6765 }],
6766 ..Default::default()
6767 },
6768 autoclose_before: "}".to_string(),
6769 ..Default::default()
6770 },
6771 Some(tree_sitter_rust::LANGUAGE.into()),
6772 ));
6773
6774 let text = r#"
6775 a
6776 b
6777 c
6778 "#
6779 .unindent();
6780
6781 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6782 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6783 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6784 editor
6785 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6786 .await;
6787
6788 editor.update_in(cx, |editor, window, cx| {
6789 editor.change_selections(None, window, cx, |s| {
6790 s.select_ranges([
6791 Point::new(0, 1)..Point::new(0, 1),
6792 Point::new(1, 1)..Point::new(1, 1),
6793 Point::new(2, 1)..Point::new(2, 1),
6794 ])
6795 });
6796
6797 editor.handle_input("{", window, cx);
6798 editor.handle_input("{", window, cx);
6799 editor.handle_input("_", window, cx);
6800 assert_eq!(
6801 editor.text(cx),
6802 "
6803 a{{_}}
6804 b{{_}}
6805 c{{_}}
6806 "
6807 .unindent()
6808 );
6809 assert_eq!(
6810 editor.selections.ranges::<Point>(cx),
6811 [
6812 Point::new(0, 4)..Point::new(0, 4),
6813 Point::new(1, 4)..Point::new(1, 4),
6814 Point::new(2, 4)..Point::new(2, 4)
6815 ]
6816 );
6817
6818 editor.backspace(&Default::default(), window, cx);
6819 editor.backspace(&Default::default(), window, cx);
6820 assert_eq!(
6821 editor.text(cx),
6822 "
6823 a{}
6824 b{}
6825 c{}
6826 "
6827 .unindent()
6828 );
6829 assert_eq!(
6830 editor.selections.ranges::<Point>(cx),
6831 [
6832 Point::new(0, 2)..Point::new(0, 2),
6833 Point::new(1, 2)..Point::new(1, 2),
6834 Point::new(2, 2)..Point::new(2, 2)
6835 ]
6836 );
6837
6838 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6839 assert_eq!(
6840 editor.text(cx),
6841 "
6842 a
6843 b
6844 c
6845 "
6846 .unindent()
6847 );
6848 assert_eq!(
6849 editor.selections.ranges::<Point>(cx),
6850 [
6851 Point::new(0, 1)..Point::new(0, 1),
6852 Point::new(1, 1)..Point::new(1, 1),
6853 Point::new(2, 1)..Point::new(2, 1)
6854 ]
6855 );
6856 });
6857}
6858
6859#[gpui::test]
6860async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6861 init_test(cx, |settings| {
6862 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6863 });
6864
6865 let mut cx = EditorTestContext::new(cx).await;
6866
6867 let language = Arc::new(Language::new(
6868 LanguageConfig {
6869 brackets: BracketPairConfig {
6870 pairs: vec![
6871 BracketPair {
6872 start: "{".to_string(),
6873 end: "}".to_string(),
6874 close: true,
6875 surround: true,
6876 newline: true,
6877 },
6878 BracketPair {
6879 start: "(".to_string(),
6880 end: ")".to_string(),
6881 close: true,
6882 surround: true,
6883 newline: true,
6884 },
6885 BracketPair {
6886 start: "[".to_string(),
6887 end: "]".to_string(),
6888 close: false,
6889 surround: true,
6890 newline: true,
6891 },
6892 ],
6893 ..Default::default()
6894 },
6895 autoclose_before: "})]".to_string(),
6896 ..Default::default()
6897 },
6898 Some(tree_sitter_rust::LANGUAGE.into()),
6899 ));
6900
6901 cx.language_registry().add(language.clone());
6902 cx.update_buffer(|buffer, cx| {
6903 buffer.set_language(Some(language), cx);
6904 });
6905
6906 cx.set_state(
6907 &"
6908 {(ˇ)}
6909 [[ˇ]]
6910 {(ˇ)}
6911 "
6912 .unindent(),
6913 );
6914
6915 cx.update_editor(|editor, window, cx| {
6916 editor.backspace(&Default::default(), window, cx);
6917 editor.backspace(&Default::default(), window, cx);
6918 });
6919
6920 cx.assert_editor_state(
6921 &"
6922 ˇ
6923 ˇ]]
6924 ˇ
6925 "
6926 .unindent(),
6927 );
6928
6929 cx.update_editor(|editor, window, cx| {
6930 editor.handle_input("{", window, cx);
6931 editor.handle_input("{", window, cx);
6932 editor.move_right(&MoveRight, window, cx);
6933 editor.move_right(&MoveRight, window, cx);
6934 editor.move_left(&MoveLeft, window, cx);
6935 editor.move_left(&MoveLeft, window, cx);
6936 editor.backspace(&Default::default(), window, cx);
6937 });
6938
6939 cx.assert_editor_state(
6940 &"
6941 {ˇ}
6942 {ˇ}]]
6943 {ˇ}
6944 "
6945 .unindent(),
6946 );
6947
6948 cx.update_editor(|editor, window, cx| {
6949 editor.backspace(&Default::default(), window, cx);
6950 });
6951
6952 cx.assert_editor_state(
6953 &"
6954 ˇ
6955 ˇ]]
6956 ˇ
6957 "
6958 .unindent(),
6959 );
6960}
6961
6962#[gpui::test]
6963async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6964 init_test(cx, |_| {});
6965
6966 let language = Arc::new(Language::new(
6967 LanguageConfig::default(),
6968 Some(tree_sitter_rust::LANGUAGE.into()),
6969 ));
6970
6971 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
6972 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6973 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6974 editor
6975 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6976 .await;
6977
6978 editor.update_in(cx, |editor, window, cx| {
6979 editor.set_auto_replace_emoji_shortcode(true);
6980
6981 editor.handle_input("Hello ", window, cx);
6982 editor.handle_input(":wave", window, cx);
6983 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6984
6985 editor.handle_input(":", window, cx);
6986 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6987
6988 editor.handle_input(" :smile", window, cx);
6989 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6990
6991 editor.handle_input(":", window, cx);
6992 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6993
6994 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6995 editor.handle_input(":wave", window, cx);
6996 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6997
6998 editor.handle_input(":", window, cx);
6999 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7000
7001 editor.handle_input(":1", window, cx);
7002 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7003
7004 editor.handle_input(":", window, cx);
7005 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7006
7007 // Ensure shortcode does not get replaced when it is part of a word
7008 editor.handle_input(" Test:wave", window, cx);
7009 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7010
7011 editor.handle_input(":", window, cx);
7012 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7013
7014 editor.set_auto_replace_emoji_shortcode(false);
7015
7016 // Ensure shortcode does not get replaced when auto replace is off
7017 editor.handle_input(" :wave", window, cx);
7018 assert_eq!(
7019 editor.text(cx),
7020 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7021 );
7022
7023 editor.handle_input(":", window, cx);
7024 assert_eq!(
7025 editor.text(cx),
7026 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7027 );
7028 });
7029}
7030
7031#[gpui::test]
7032async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
7033 init_test(cx, |_| {});
7034
7035 let (text, insertion_ranges) = marked_text_ranges(
7036 indoc! {"
7037 ˇ
7038 "},
7039 false,
7040 );
7041
7042 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7043 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7044
7045 _ = editor.update_in(cx, |editor, window, cx| {
7046 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7047
7048 editor
7049 .insert_snippet(&insertion_ranges, snippet, window, cx)
7050 .unwrap();
7051
7052 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7053 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7054 assert_eq!(editor.text(cx), expected_text);
7055 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7056 }
7057
7058 assert(
7059 editor,
7060 cx,
7061 indoc! {"
7062 type «» =•
7063 "},
7064 );
7065
7066 assert!(editor.context_menu_visible(), "There should be a matches");
7067 });
7068}
7069
7070#[gpui::test]
7071async fn test_snippets(cx: &mut gpui::TestAppContext) {
7072 init_test(cx, |_| {});
7073
7074 let (text, insertion_ranges) = marked_text_ranges(
7075 indoc! {"
7076 a.ˇ b
7077 a.ˇ b
7078 a.ˇ b
7079 "},
7080 false,
7081 );
7082
7083 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7084 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7085
7086 editor.update_in(cx, |editor, window, cx| {
7087 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7088
7089 editor
7090 .insert_snippet(&insertion_ranges, snippet, window, cx)
7091 .unwrap();
7092
7093 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7094 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7095 assert_eq!(editor.text(cx), expected_text);
7096 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7097 }
7098
7099 assert(
7100 editor,
7101 cx,
7102 indoc! {"
7103 a.f(«one», two, «three») b
7104 a.f(«one», two, «three») b
7105 a.f(«one», two, «three») b
7106 "},
7107 );
7108
7109 // Can't move earlier than the first tab stop
7110 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7111 assert(
7112 editor,
7113 cx,
7114 indoc! {"
7115 a.f(«one», two, «three») b
7116 a.f(«one», two, «three») b
7117 a.f(«one», two, «three») b
7118 "},
7119 );
7120
7121 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7122 assert(
7123 editor,
7124 cx,
7125 indoc! {"
7126 a.f(one, «two», three) b
7127 a.f(one, «two», three) b
7128 a.f(one, «two», three) b
7129 "},
7130 );
7131
7132 editor.move_to_prev_snippet_tabstop(window, cx);
7133 assert(
7134 editor,
7135 cx,
7136 indoc! {"
7137 a.f(«one», two, «three») b
7138 a.f(«one», two, «three») b
7139 a.f(«one», two, «three») b
7140 "},
7141 );
7142
7143 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7144 assert(
7145 editor,
7146 cx,
7147 indoc! {"
7148 a.f(one, «two», three) b
7149 a.f(one, «two», three) b
7150 a.f(one, «two», three) b
7151 "},
7152 );
7153 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7154 assert(
7155 editor,
7156 cx,
7157 indoc! {"
7158 a.f(one, two, three)ˇ b
7159 a.f(one, two, three)ˇ b
7160 a.f(one, two, three)ˇ b
7161 "},
7162 );
7163
7164 // As soon as the last tab stop is reached, snippet state is gone
7165 editor.move_to_prev_snippet_tabstop(window, cx);
7166 assert(
7167 editor,
7168 cx,
7169 indoc! {"
7170 a.f(one, two, three)ˇ b
7171 a.f(one, two, three)ˇ b
7172 a.f(one, two, three)ˇ b
7173 "},
7174 );
7175 });
7176}
7177
7178#[gpui::test]
7179async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
7180 init_test(cx, |_| {});
7181
7182 let fs = FakeFs::new(cx.executor());
7183 fs.insert_file(path!("/file.rs"), Default::default()).await;
7184
7185 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7186
7187 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7188 language_registry.add(rust_lang());
7189 let mut fake_servers = language_registry.register_fake_lsp(
7190 "Rust",
7191 FakeLspAdapter {
7192 capabilities: lsp::ServerCapabilities {
7193 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7194 ..Default::default()
7195 },
7196 ..Default::default()
7197 },
7198 );
7199
7200 let buffer = project
7201 .update(cx, |project, cx| {
7202 project.open_local_buffer(path!("/file.rs"), cx)
7203 })
7204 .await
7205 .unwrap();
7206
7207 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7208 let (editor, cx) = cx.add_window_view(|window, cx| {
7209 build_editor_with_project(project.clone(), buffer, window, cx)
7210 });
7211 editor.update_in(cx, |editor, window, cx| {
7212 editor.set_text("one\ntwo\nthree\n", window, cx)
7213 });
7214 assert!(cx.read(|cx| editor.is_dirty(cx)));
7215
7216 cx.executor().start_waiting();
7217 let fake_server = fake_servers.next().await.unwrap();
7218
7219 let save = editor
7220 .update_in(cx, |editor, window, cx| {
7221 editor.save(true, project.clone(), window, cx)
7222 })
7223 .unwrap();
7224 fake_server
7225 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7226 assert_eq!(
7227 params.text_document.uri,
7228 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7229 );
7230 assert_eq!(params.options.tab_size, 4);
7231 Ok(Some(vec![lsp::TextEdit::new(
7232 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7233 ", ".to_string(),
7234 )]))
7235 })
7236 .next()
7237 .await;
7238 cx.executor().start_waiting();
7239 save.await;
7240
7241 assert_eq!(
7242 editor.update(cx, |editor, cx| editor.text(cx)),
7243 "one, two\nthree\n"
7244 );
7245 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7246
7247 editor.update_in(cx, |editor, window, cx| {
7248 editor.set_text("one\ntwo\nthree\n", window, cx)
7249 });
7250 assert!(cx.read(|cx| editor.is_dirty(cx)));
7251
7252 // Ensure we can still save even if formatting hangs.
7253 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7254 assert_eq!(
7255 params.text_document.uri,
7256 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7257 );
7258 futures::future::pending::<()>().await;
7259 unreachable!()
7260 });
7261 let save = editor
7262 .update_in(cx, |editor, window, cx| {
7263 editor.save(true, project.clone(), window, cx)
7264 })
7265 .unwrap();
7266 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7267 cx.executor().start_waiting();
7268 save.await;
7269 assert_eq!(
7270 editor.update(cx, |editor, cx| editor.text(cx)),
7271 "one\ntwo\nthree\n"
7272 );
7273 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7274
7275 // For non-dirty buffer, no formatting request should be sent
7276 let save = editor
7277 .update_in(cx, |editor, window, cx| {
7278 editor.save(true, project.clone(), window, cx)
7279 })
7280 .unwrap();
7281 let _pending_format_request = fake_server
7282 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7283 panic!("Should not be invoked on non-dirty buffer");
7284 })
7285 .next();
7286 cx.executor().start_waiting();
7287 save.await;
7288
7289 // Set rust language override and assert overridden tabsize is sent to language server
7290 update_test_language_settings(cx, |settings| {
7291 settings.languages.insert(
7292 "Rust".into(),
7293 LanguageSettingsContent {
7294 tab_size: NonZeroU32::new(8),
7295 ..Default::default()
7296 },
7297 );
7298 });
7299
7300 editor.update_in(cx, |editor, window, cx| {
7301 editor.set_text("somehting_new\n", window, cx)
7302 });
7303 assert!(cx.read(|cx| editor.is_dirty(cx)));
7304 let save = editor
7305 .update_in(cx, |editor, window, cx| {
7306 editor.save(true, project.clone(), window, cx)
7307 })
7308 .unwrap();
7309 fake_server
7310 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7311 assert_eq!(
7312 params.text_document.uri,
7313 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7314 );
7315 assert_eq!(params.options.tab_size, 8);
7316 Ok(Some(vec![]))
7317 })
7318 .next()
7319 .await;
7320 cx.executor().start_waiting();
7321 save.await;
7322}
7323
7324#[gpui::test]
7325async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
7326 init_test(cx, |_| {});
7327
7328 let cols = 4;
7329 let rows = 10;
7330 let sample_text_1 = sample_text(rows, cols, 'a');
7331 assert_eq!(
7332 sample_text_1,
7333 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7334 );
7335 let sample_text_2 = sample_text(rows, cols, 'l');
7336 assert_eq!(
7337 sample_text_2,
7338 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7339 );
7340 let sample_text_3 = sample_text(rows, cols, 'v');
7341 assert_eq!(
7342 sample_text_3,
7343 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7344 );
7345
7346 let fs = FakeFs::new(cx.executor());
7347 fs.insert_tree(
7348 path!("/a"),
7349 json!({
7350 "main.rs": sample_text_1,
7351 "other.rs": sample_text_2,
7352 "lib.rs": sample_text_3,
7353 }),
7354 )
7355 .await;
7356
7357 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7358 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7359 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7360
7361 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7362 language_registry.add(rust_lang());
7363 let mut fake_servers = language_registry.register_fake_lsp(
7364 "Rust",
7365 FakeLspAdapter {
7366 capabilities: lsp::ServerCapabilities {
7367 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7368 ..Default::default()
7369 },
7370 ..Default::default()
7371 },
7372 );
7373
7374 let worktree = project.update(cx, |project, cx| {
7375 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7376 assert_eq!(worktrees.len(), 1);
7377 worktrees.pop().unwrap()
7378 });
7379 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7380
7381 let buffer_1 = project
7382 .update(cx, |project, cx| {
7383 project.open_buffer((worktree_id, "main.rs"), cx)
7384 })
7385 .await
7386 .unwrap();
7387 let buffer_2 = project
7388 .update(cx, |project, cx| {
7389 project.open_buffer((worktree_id, "other.rs"), cx)
7390 })
7391 .await
7392 .unwrap();
7393 let buffer_3 = project
7394 .update(cx, |project, cx| {
7395 project.open_buffer((worktree_id, "lib.rs"), cx)
7396 })
7397 .await
7398 .unwrap();
7399
7400 let multi_buffer = cx.new(|cx| {
7401 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7402 multi_buffer.push_excerpts(
7403 buffer_1.clone(),
7404 [
7405 ExcerptRange {
7406 context: Point::new(0, 0)..Point::new(3, 0),
7407 primary: None,
7408 },
7409 ExcerptRange {
7410 context: Point::new(5, 0)..Point::new(7, 0),
7411 primary: None,
7412 },
7413 ExcerptRange {
7414 context: Point::new(9, 0)..Point::new(10, 4),
7415 primary: None,
7416 },
7417 ],
7418 cx,
7419 );
7420 multi_buffer.push_excerpts(
7421 buffer_2.clone(),
7422 [
7423 ExcerptRange {
7424 context: Point::new(0, 0)..Point::new(3, 0),
7425 primary: None,
7426 },
7427 ExcerptRange {
7428 context: Point::new(5, 0)..Point::new(7, 0),
7429 primary: None,
7430 },
7431 ExcerptRange {
7432 context: Point::new(9, 0)..Point::new(10, 4),
7433 primary: None,
7434 },
7435 ],
7436 cx,
7437 );
7438 multi_buffer.push_excerpts(
7439 buffer_3.clone(),
7440 [
7441 ExcerptRange {
7442 context: Point::new(0, 0)..Point::new(3, 0),
7443 primary: None,
7444 },
7445 ExcerptRange {
7446 context: Point::new(5, 0)..Point::new(7, 0),
7447 primary: None,
7448 },
7449 ExcerptRange {
7450 context: Point::new(9, 0)..Point::new(10, 4),
7451 primary: None,
7452 },
7453 ],
7454 cx,
7455 );
7456 multi_buffer
7457 });
7458 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7459 Editor::new(
7460 EditorMode::Full,
7461 multi_buffer,
7462 Some(project.clone()),
7463 true,
7464 window,
7465 cx,
7466 )
7467 });
7468
7469 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7470 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7471 s.select_ranges(Some(1..2))
7472 });
7473 editor.insert("|one|two|three|", window, cx);
7474 });
7475 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7476 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7477 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7478 s.select_ranges(Some(60..70))
7479 });
7480 editor.insert("|four|five|six|", window, cx);
7481 });
7482 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7483
7484 // First two buffers should be edited, but not the third one.
7485 assert_eq!(
7486 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7487 "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}",
7488 );
7489 buffer_1.update(cx, |buffer, _| {
7490 assert!(buffer.is_dirty());
7491 assert_eq!(
7492 buffer.text(),
7493 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7494 )
7495 });
7496 buffer_2.update(cx, |buffer, _| {
7497 assert!(buffer.is_dirty());
7498 assert_eq!(
7499 buffer.text(),
7500 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7501 )
7502 });
7503 buffer_3.update(cx, |buffer, _| {
7504 assert!(!buffer.is_dirty());
7505 assert_eq!(buffer.text(), sample_text_3,)
7506 });
7507 cx.executor().run_until_parked();
7508
7509 cx.executor().start_waiting();
7510 let save = multi_buffer_editor
7511 .update_in(cx, |editor, window, cx| {
7512 editor.save(true, project.clone(), window, cx)
7513 })
7514 .unwrap();
7515
7516 let fake_server = fake_servers.next().await.unwrap();
7517 fake_server
7518 .server
7519 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7520 Ok(Some(vec![lsp::TextEdit::new(
7521 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7522 format!("[{} formatted]", params.text_document.uri),
7523 )]))
7524 })
7525 .detach();
7526 save.await;
7527
7528 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7529 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7530 assert_eq!(
7531 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7532 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}"),
7533 );
7534 buffer_1.update(cx, |buffer, _| {
7535 assert!(!buffer.is_dirty());
7536 assert_eq!(
7537 buffer.text(),
7538 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7539 )
7540 });
7541 buffer_2.update(cx, |buffer, _| {
7542 assert!(!buffer.is_dirty());
7543 assert_eq!(
7544 buffer.text(),
7545 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7546 )
7547 });
7548 buffer_3.update(cx, |buffer, _| {
7549 assert!(!buffer.is_dirty());
7550 assert_eq!(buffer.text(), sample_text_3,)
7551 });
7552}
7553
7554#[gpui::test]
7555async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7556 init_test(cx, |_| {});
7557
7558 let fs = FakeFs::new(cx.executor());
7559 fs.insert_file(path!("/file.rs"), Default::default()).await;
7560
7561 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7562
7563 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7564 language_registry.add(rust_lang());
7565 let mut fake_servers = language_registry.register_fake_lsp(
7566 "Rust",
7567 FakeLspAdapter {
7568 capabilities: lsp::ServerCapabilities {
7569 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7570 ..Default::default()
7571 },
7572 ..Default::default()
7573 },
7574 );
7575
7576 let buffer = project
7577 .update(cx, |project, cx| {
7578 project.open_local_buffer(path!("/file.rs"), cx)
7579 })
7580 .await
7581 .unwrap();
7582
7583 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7584 let (editor, cx) = cx.add_window_view(|window, cx| {
7585 build_editor_with_project(project.clone(), buffer, window, cx)
7586 });
7587 editor.update_in(cx, |editor, window, cx| {
7588 editor.set_text("one\ntwo\nthree\n", window, cx)
7589 });
7590 assert!(cx.read(|cx| editor.is_dirty(cx)));
7591
7592 cx.executor().start_waiting();
7593 let fake_server = fake_servers.next().await.unwrap();
7594
7595 let save = editor
7596 .update_in(cx, |editor, window, cx| {
7597 editor.save(true, project.clone(), window, cx)
7598 })
7599 .unwrap();
7600 fake_server
7601 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7602 assert_eq!(
7603 params.text_document.uri,
7604 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7605 );
7606 assert_eq!(params.options.tab_size, 4);
7607 Ok(Some(vec![lsp::TextEdit::new(
7608 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7609 ", ".to_string(),
7610 )]))
7611 })
7612 .next()
7613 .await;
7614 cx.executor().start_waiting();
7615 save.await;
7616 assert_eq!(
7617 editor.update(cx, |editor, cx| editor.text(cx)),
7618 "one, two\nthree\n"
7619 );
7620 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7621
7622 editor.update_in(cx, |editor, window, cx| {
7623 editor.set_text("one\ntwo\nthree\n", window, cx)
7624 });
7625 assert!(cx.read(|cx| editor.is_dirty(cx)));
7626
7627 // Ensure we can still save even if formatting hangs.
7628 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7629 move |params, _| async move {
7630 assert_eq!(
7631 params.text_document.uri,
7632 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7633 );
7634 futures::future::pending::<()>().await;
7635 unreachable!()
7636 },
7637 );
7638 let save = editor
7639 .update_in(cx, |editor, window, cx| {
7640 editor.save(true, project.clone(), window, cx)
7641 })
7642 .unwrap();
7643 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7644 cx.executor().start_waiting();
7645 save.await;
7646 assert_eq!(
7647 editor.update(cx, |editor, cx| editor.text(cx)),
7648 "one\ntwo\nthree\n"
7649 );
7650 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7651
7652 // For non-dirty buffer, no formatting request should be sent
7653 let save = editor
7654 .update_in(cx, |editor, window, cx| {
7655 editor.save(true, project.clone(), window, cx)
7656 })
7657 .unwrap();
7658 let _pending_format_request = fake_server
7659 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7660 panic!("Should not be invoked on non-dirty buffer");
7661 })
7662 .next();
7663 cx.executor().start_waiting();
7664 save.await;
7665
7666 // Set Rust language override and assert overridden tabsize is sent to language server
7667 update_test_language_settings(cx, |settings| {
7668 settings.languages.insert(
7669 "Rust".into(),
7670 LanguageSettingsContent {
7671 tab_size: NonZeroU32::new(8),
7672 ..Default::default()
7673 },
7674 );
7675 });
7676
7677 editor.update_in(cx, |editor, window, cx| {
7678 editor.set_text("somehting_new\n", window, cx)
7679 });
7680 assert!(cx.read(|cx| editor.is_dirty(cx)));
7681 let save = editor
7682 .update_in(cx, |editor, window, cx| {
7683 editor.save(true, project.clone(), window, cx)
7684 })
7685 .unwrap();
7686 fake_server
7687 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7688 assert_eq!(
7689 params.text_document.uri,
7690 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7691 );
7692 assert_eq!(params.options.tab_size, 8);
7693 Ok(Some(vec![]))
7694 })
7695 .next()
7696 .await;
7697 cx.executor().start_waiting();
7698 save.await;
7699}
7700
7701#[gpui::test]
7702async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7703 init_test(cx, |settings| {
7704 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7705 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7706 ))
7707 });
7708
7709 let fs = FakeFs::new(cx.executor());
7710 fs.insert_file(path!("/file.rs"), Default::default()).await;
7711
7712 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7713
7714 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7715 language_registry.add(Arc::new(Language::new(
7716 LanguageConfig {
7717 name: "Rust".into(),
7718 matcher: LanguageMatcher {
7719 path_suffixes: vec!["rs".to_string()],
7720 ..Default::default()
7721 },
7722 ..LanguageConfig::default()
7723 },
7724 Some(tree_sitter_rust::LANGUAGE.into()),
7725 )));
7726 update_test_language_settings(cx, |settings| {
7727 // Enable Prettier formatting for the same buffer, and ensure
7728 // LSP is called instead of Prettier.
7729 settings.defaults.prettier = Some(PrettierSettings {
7730 allowed: true,
7731 ..PrettierSettings::default()
7732 });
7733 });
7734 let mut fake_servers = language_registry.register_fake_lsp(
7735 "Rust",
7736 FakeLspAdapter {
7737 capabilities: lsp::ServerCapabilities {
7738 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7739 ..Default::default()
7740 },
7741 ..Default::default()
7742 },
7743 );
7744
7745 let buffer = project
7746 .update(cx, |project, cx| {
7747 project.open_local_buffer(path!("/file.rs"), cx)
7748 })
7749 .await
7750 .unwrap();
7751
7752 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7753 let (editor, cx) = cx.add_window_view(|window, cx| {
7754 build_editor_with_project(project.clone(), buffer, window, cx)
7755 });
7756 editor.update_in(cx, |editor, window, cx| {
7757 editor.set_text("one\ntwo\nthree\n", window, cx)
7758 });
7759
7760 cx.executor().start_waiting();
7761 let fake_server = fake_servers.next().await.unwrap();
7762
7763 let format = editor
7764 .update_in(cx, |editor, window, cx| {
7765 editor.perform_format(
7766 project.clone(),
7767 FormatTrigger::Manual,
7768 FormatTarget::Buffers,
7769 window,
7770 cx,
7771 )
7772 })
7773 .unwrap();
7774 fake_server
7775 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7776 assert_eq!(
7777 params.text_document.uri,
7778 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7779 );
7780 assert_eq!(params.options.tab_size, 4);
7781 Ok(Some(vec![lsp::TextEdit::new(
7782 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7783 ", ".to_string(),
7784 )]))
7785 })
7786 .next()
7787 .await;
7788 cx.executor().start_waiting();
7789 format.await;
7790 assert_eq!(
7791 editor.update(cx, |editor, cx| editor.text(cx)),
7792 "one, two\nthree\n"
7793 );
7794
7795 editor.update_in(cx, |editor, window, cx| {
7796 editor.set_text("one\ntwo\nthree\n", window, cx)
7797 });
7798 // Ensure we don't lock if formatting hangs.
7799 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7800 assert_eq!(
7801 params.text_document.uri,
7802 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7803 );
7804 futures::future::pending::<()>().await;
7805 unreachable!()
7806 });
7807 let format = editor
7808 .update_in(cx, |editor, window, cx| {
7809 editor.perform_format(
7810 project,
7811 FormatTrigger::Manual,
7812 FormatTarget::Buffers,
7813 window,
7814 cx,
7815 )
7816 })
7817 .unwrap();
7818 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7819 cx.executor().start_waiting();
7820 format.await;
7821 assert_eq!(
7822 editor.update(cx, |editor, cx| editor.text(cx)),
7823 "one\ntwo\nthree\n"
7824 );
7825}
7826
7827#[gpui::test]
7828async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7829 init_test(cx, |_| {});
7830
7831 let mut cx = EditorLspTestContext::new_rust(
7832 lsp::ServerCapabilities {
7833 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7834 ..Default::default()
7835 },
7836 cx,
7837 )
7838 .await;
7839
7840 cx.set_state(indoc! {"
7841 one.twoˇ
7842 "});
7843
7844 // The format request takes a long time. When it completes, it inserts
7845 // a newline and an indent before the `.`
7846 cx.lsp
7847 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7848 let executor = cx.background_executor().clone();
7849 async move {
7850 executor.timer(Duration::from_millis(100)).await;
7851 Ok(Some(vec![lsp::TextEdit {
7852 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7853 new_text: "\n ".into(),
7854 }]))
7855 }
7856 });
7857
7858 // Submit a format request.
7859 let format_1 = cx
7860 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7861 .unwrap();
7862 cx.executor().run_until_parked();
7863
7864 // Submit a second format request.
7865 let format_2 = cx
7866 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7867 .unwrap();
7868 cx.executor().run_until_parked();
7869
7870 // Wait for both format requests to complete
7871 cx.executor().advance_clock(Duration::from_millis(200));
7872 cx.executor().start_waiting();
7873 format_1.await.unwrap();
7874 cx.executor().start_waiting();
7875 format_2.await.unwrap();
7876
7877 // The formatting edits only happens once.
7878 cx.assert_editor_state(indoc! {"
7879 one
7880 .twoˇ
7881 "});
7882}
7883
7884#[gpui::test]
7885async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7886 init_test(cx, |settings| {
7887 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7888 });
7889
7890 let mut cx = EditorLspTestContext::new_rust(
7891 lsp::ServerCapabilities {
7892 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7893 ..Default::default()
7894 },
7895 cx,
7896 )
7897 .await;
7898
7899 // Set up a buffer white some trailing whitespace and no trailing newline.
7900 cx.set_state(
7901 &[
7902 "one ", //
7903 "twoˇ", //
7904 "three ", //
7905 "four", //
7906 ]
7907 .join("\n"),
7908 );
7909
7910 // Submit a format request.
7911 let format = cx
7912 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7913 .unwrap();
7914
7915 // Record which buffer changes have been sent to the language server
7916 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7917 cx.lsp
7918 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7919 let buffer_changes = buffer_changes.clone();
7920 move |params, _| {
7921 buffer_changes.lock().extend(
7922 params
7923 .content_changes
7924 .into_iter()
7925 .map(|e| (e.range.unwrap(), e.text)),
7926 );
7927 }
7928 });
7929
7930 // Handle formatting requests to the language server.
7931 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7932 let buffer_changes = buffer_changes.clone();
7933 move |_, _| {
7934 // When formatting is requested, trailing whitespace has already been stripped,
7935 // and the trailing newline has already been added.
7936 assert_eq!(
7937 &buffer_changes.lock()[1..],
7938 &[
7939 (
7940 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7941 "".into()
7942 ),
7943 (
7944 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7945 "".into()
7946 ),
7947 (
7948 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7949 "\n".into()
7950 ),
7951 ]
7952 );
7953
7954 // Insert blank lines between each line of the buffer.
7955 async move {
7956 Ok(Some(vec![
7957 lsp::TextEdit {
7958 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7959 new_text: "\n".into(),
7960 },
7961 lsp::TextEdit {
7962 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7963 new_text: "\n".into(),
7964 },
7965 ]))
7966 }
7967 }
7968 });
7969
7970 // After formatting the buffer, the trailing whitespace is stripped,
7971 // a newline is appended, and the edits provided by the language server
7972 // have been applied.
7973 format.await.unwrap();
7974 cx.assert_editor_state(
7975 &[
7976 "one", //
7977 "", //
7978 "twoˇ", //
7979 "", //
7980 "three", //
7981 "four", //
7982 "", //
7983 ]
7984 .join("\n"),
7985 );
7986
7987 // Undoing the formatting undoes the trailing whitespace removal, the
7988 // trailing newline, and the LSP edits.
7989 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7990 cx.assert_editor_state(
7991 &[
7992 "one ", //
7993 "twoˇ", //
7994 "three ", //
7995 "four", //
7996 ]
7997 .join("\n"),
7998 );
7999}
8000
8001#[gpui::test]
8002async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8003 cx: &mut gpui::TestAppContext,
8004) {
8005 init_test(cx, |_| {});
8006
8007 cx.update(|cx| {
8008 cx.update_global::<SettingsStore, _>(|settings, cx| {
8009 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8010 settings.auto_signature_help = Some(true);
8011 });
8012 });
8013 });
8014
8015 let mut cx = EditorLspTestContext::new_rust(
8016 lsp::ServerCapabilities {
8017 signature_help_provider: Some(lsp::SignatureHelpOptions {
8018 ..Default::default()
8019 }),
8020 ..Default::default()
8021 },
8022 cx,
8023 )
8024 .await;
8025
8026 let language = Language::new(
8027 LanguageConfig {
8028 name: "Rust".into(),
8029 brackets: BracketPairConfig {
8030 pairs: vec![
8031 BracketPair {
8032 start: "{".to_string(),
8033 end: "}".to_string(),
8034 close: true,
8035 surround: true,
8036 newline: true,
8037 },
8038 BracketPair {
8039 start: "(".to_string(),
8040 end: ")".to_string(),
8041 close: true,
8042 surround: true,
8043 newline: true,
8044 },
8045 BracketPair {
8046 start: "/*".to_string(),
8047 end: " */".to_string(),
8048 close: true,
8049 surround: true,
8050 newline: true,
8051 },
8052 BracketPair {
8053 start: "[".to_string(),
8054 end: "]".to_string(),
8055 close: false,
8056 surround: false,
8057 newline: true,
8058 },
8059 BracketPair {
8060 start: "\"".to_string(),
8061 end: "\"".to_string(),
8062 close: true,
8063 surround: true,
8064 newline: false,
8065 },
8066 BracketPair {
8067 start: "<".to_string(),
8068 end: ">".to_string(),
8069 close: false,
8070 surround: true,
8071 newline: true,
8072 },
8073 ],
8074 ..Default::default()
8075 },
8076 autoclose_before: "})]".to_string(),
8077 ..Default::default()
8078 },
8079 Some(tree_sitter_rust::LANGUAGE.into()),
8080 );
8081 let language = Arc::new(language);
8082
8083 cx.language_registry().add(language.clone());
8084 cx.update_buffer(|buffer, cx| {
8085 buffer.set_language(Some(language), cx);
8086 });
8087
8088 cx.set_state(
8089 &r#"
8090 fn main() {
8091 sampleˇ
8092 }
8093 "#
8094 .unindent(),
8095 );
8096
8097 cx.update_editor(|editor, window, cx| {
8098 editor.handle_input("(", window, cx);
8099 });
8100 cx.assert_editor_state(
8101 &"
8102 fn main() {
8103 sample(ˇ)
8104 }
8105 "
8106 .unindent(),
8107 );
8108
8109 let mocked_response = lsp::SignatureHelp {
8110 signatures: vec![lsp::SignatureInformation {
8111 label: "fn sample(param1: u8, param2: u8)".to_string(),
8112 documentation: None,
8113 parameters: Some(vec![
8114 lsp::ParameterInformation {
8115 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8116 documentation: None,
8117 },
8118 lsp::ParameterInformation {
8119 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8120 documentation: None,
8121 },
8122 ]),
8123 active_parameter: None,
8124 }],
8125 active_signature: Some(0),
8126 active_parameter: Some(0),
8127 };
8128 handle_signature_help_request(&mut cx, mocked_response).await;
8129
8130 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8131 .await;
8132
8133 cx.editor(|editor, _, _| {
8134 let signature_help_state = editor.signature_help_state.popover().cloned();
8135 assert_eq!(
8136 signature_help_state.unwrap().label,
8137 "param1: u8, param2: u8"
8138 );
8139 });
8140}
8141
8142#[gpui::test]
8143async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
8144 init_test(cx, |_| {});
8145
8146 cx.update(|cx| {
8147 cx.update_global::<SettingsStore, _>(|settings, cx| {
8148 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8149 settings.auto_signature_help = Some(false);
8150 settings.show_signature_help_after_edits = Some(false);
8151 });
8152 });
8153 });
8154
8155 let mut cx = EditorLspTestContext::new_rust(
8156 lsp::ServerCapabilities {
8157 signature_help_provider: Some(lsp::SignatureHelpOptions {
8158 ..Default::default()
8159 }),
8160 ..Default::default()
8161 },
8162 cx,
8163 )
8164 .await;
8165
8166 let language = Language::new(
8167 LanguageConfig {
8168 name: "Rust".into(),
8169 brackets: BracketPairConfig {
8170 pairs: vec![
8171 BracketPair {
8172 start: "{".to_string(),
8173 end: "}".to_string(),
8174 close: true,
8175 surround: true,
8176 newline: true,
8177 },
8178 BracketPair {
8179 start: "(".to_string(),
8180 end: ")".to_string(),
8181 close: true,
8182 surround: true,
8183 newline: true,
8184 },
8185 BracketPair {
8186 start: "/*".to_string(),
8187 end: " */".to_string(),
8188 close: true,
8189 surround: true,
8190 newline: true,
8191 },
8192 BracketPair {
8193 start: "[".to_string(),
8194 end: "]".to_string(),
8195 close: false,
8196 surround: false,
8197 newline: true,
8198 },
8199 BracketPair {
8200 start: "\"".to_string(),
8201 end: "\"".to_string(),
8202 close: true,
8203 surround: true,
8204 newline: false,
8205 },
8206 BracketPair {
8207 start: "<".to_string(),
8208 end: ">".to_string(),
8209 close: false,
8210 surround: true,
8211 newline: true,
8212 },
8213 ],
8214 ..Default::default()
8215 },
8216 autoclose_before: "})]".to_string(),
8217 ..Default::default()
8218 },
8219 Some(tree_sitter_rust::LANGUAGE.into()),
8220 );
8221 let language = Arc::new(language);
8222
8223 cx.language_registry().add(language.clone());
8224 cx.update_buffer(|buffer, cx| {
8225 buffer.set_language(Some(language), cx);
8226 });
8227
8228 // Ensure that signature_help is not called when no signature help is enabled.
8229 cx.set_state(
8230 &r#"
8231 fn main() {
8232 sampleˇ
8233 }
8234 "#
8235 .unindent(),
8236 );
8237 cx.update_editor(|editor, window, cx| {
8238 editor.handle_input("(", window, cx);
8239 });
8240 cx.assert_editor_state(
8241 &"
8242 fn main() {
8243 sample(ˇ)
8244 }
8245 "
8246 .unindent(),
8247 );
8248 cx.editor(|editor, _, _| {
8249 assert!(editor.signature_help_state.task().is_none());
8250 });
8251
8252 let mocked_response = lsp::SignatureHelp {
8253 signatures: vec![lsp::SignatureInformation {
8254 label: "fn sample(param1: u8, param2: u8)".to_string(),
8255 documentation: None,
8256 parameters: Some(vec![
8257 lsp::ParameterInformation {
8258 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8259 documentation: None,
8260 },
8261 lsp::ParameterInformation {
8262 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8263 documentation: None,
8264 },
8265 ]),
8266 active_parameter: None,
8267 }],
8268 active_signature: Some(0),
8269 active_parameter: Some(0),
8270 };
8271
8272 // Ensure that signature_help is called when enabled afte edits
8273 cx.update(|_, cx| {
8274 cx.update_global::<SettingsStore, _>(|settings, cx| {
8275 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8276 settings.auto_signature_help = Some(false);
8277 settings.show_signature_help_after_edits = Some(true);
8278 });
8279 });
8280 });
8281 cx.set_state(
8282 &r#"
8283 fn main() {
8284 sampleˇ
8285 }
8286 "#
8287 .unindent(),
8288 );
8289 cx.update_editor(|editor, window, cx| {
8290 editor.handle_input("(", window, cx);
8291 });
8292 cx.assert_editor_state(
8293 &"
8294 fn main() {
8295 sample(ˇ)
8296 }
8297 "
8298 .unindent(),
8299 );
8300 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8301 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8302 .await;
8303 cx.update_editor(|editor, _, _| {
8304 let signature_help_state = editor.signature_help_state.popover().cloned();
8305 assert!(signature_help_state.is_some());
8306 assert_eq!(
8307 signature_help_state.unwrap().label,
8308 "param1: u8, param2: u8"
8309 );
8310 editor.signature_help_state = SignatureHelpState::default();
8311 });
8312
8313 // Ensure that signature_help is called when auto signature help override is enabled
8314 cx.update(|_, cx| {
8315 cx.update_global::<SettingsStore, _>(|settings, cx| {
8316 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8317 settings.auto_signature_help = Some(true);
8318 settings.show_signature_help_after_edits = Some(false);
8319 });
8320 });
8321 });
8322 cx.set_state(
8323 &r#"
8324 fn main() {
8325 sampleˇ
8326 }
8327 "#
8328 .unindent(),
8329 );
8330 cx.update_editor(|editor, window, cx| {
8331 editor.handle_input("(", window, cx);
8332 });
8333 cx.assert_editor_state(
8334 &"
8335 fn main() {
8336 sample(ˇ)
8337 }
8338 "
8339 .unindent(),
8340 );
8341 handle_signature_help_request(&mut cx, mocked_response).await;
8342 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8343 .await;
8344 cx.editor(|editor, _, _| {
8345 let signature_help_state = editor.signature_help_state.popover().cloned();
8346 assert!(signature_help_state.is_some());
8347 assert_eq!(
8348 signature_help_state.unwrap().label,
8349 "param1: u8, param2: u8"
8350 );
8351 });
8352}
8353
8354#[gpui::test]
8355async fn test_signature_help(cx: &mut gpui::TestAppContext) {
8356 init_test(cx, |_| {});
8357 cx.update(|cx| {
8358 cx.update_global::<SettingsStore, _>(|settings, cx| {
8359 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8360 settings.auto_signature_help = Some(true);
8361 });
8362 });
8363 });
8364
8365 let mut cx = EditorLspTestContext::new_rust(
8366 lsp::ServerCapabilities {
8367 signature_help_provider: Some(lsp::SignatureHelpOptions {
8368 ..Default::default()
8369 }),
8370 ..Default::default()
8371 },
8372 cx,
8373 )
8374 .await;
8375
8376 // A test that directly calls `show_signature_help`
8377 cx.update_editor(|editor, window, cx| {
8378 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8379 });
8380
8381 let mocked_response = lsp::SignatureHelp {
8382 signatures: vec![lsp::SignatureInformation {
8383 label: "fn sample(param1: u8, param2: u8)".to_string(),
8384 documentation: None,
8385 parameters: Some(vec![
8386 lsp::ParameterInformation {
8387 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8388 documentation: None,
8389 },
8390 lsp::ParameterInformation {
8391 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8392 documentation: None,
8393 },
8394 ]),
8395 active_parameter: None,
8396 }],
8397 active_signature: Some(0),
8398 active_parameter: Some(0),
8399 };
8400 handle_signature_help_request(&mut cx, mocked_response).await;
8401
8402 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8403 .await;
8404
8405 cx.editor(|editor, _, _| {
8406 let signature_help_state = editor.signature_help_state.popover().cloned();
8407 assert!(signature_help_state.is_some());
8408 assert_eq!(
8409 signature_help_state.unwrap().label,
8410 "param1: u8, param2: u8"
8411 );
8412 });
8413
8414 // When exiting outside from inside the brackets, `signature_help` is closed.
8415 cx.set_state(indoc! {"
8416 fn main() {
8417 sample(ˇ);
8418 }
8419
8420 fn sample(param1: u8, param2: u8) {}
8421 "});
8422
8423 cx.update_editor(|editor, window, cx| {
8424 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8425 });
8426
8427 let mocked_response = lsp::SignatureHelp {
8428 signatures: Vec::new(),
8429 active_signature: None,
8430 active_parameter: None,
8431 };
8432 handle_signature_help_request(&mut cx, mocked_response).await;
8433
8434 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8435 .await;
8436
8437 cx.editor(|editor, _, _| {
8438 assert!(!editor.signature_help_state.is_shown());
8439 });
8440
8441 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8442 cx.set_state(indoc! {"
8443 fn main() {
8444 sample(ˇ);
8445 }
8446
8447 fn sample(param1: u8, param2: u8) {}
8448 "});
8449
8450 let mocked_response = lsp::SignatureHelp {
8451 signatures: vec![lsp::SignatureInformation {
8452 label: "fn sample(param1: u8, param2: u8)".to_string(),
8453 documentation: None,
8454 parameters: Some(vec![
8455 lsp::ParameterInformation {
8456 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8457 documentation: None,
8458 },
8459 lsp::ParameterInformation {
8460 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8461 documentation: None,
8462 },
8463 ]),
8464 active_parameter: None,
8465 }],
8466 active_signature: Some(0),
8467 active_parameter: Some(0),
8468 };
8469 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8470 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8471 .await;
8472 cx.editor(|editor, _, _| {
8473 assert!(editor.signature_help_state.is_shown());
8474 });
8475
8476 // Restore the popover with more parameter input
8477 cx.set_state(indoc! {"
8478 fn main() {
8479 sample(param1, param2ˇ);
8480 }
8481
8482 fn sample(param1: u8, param2: u8) {}
8483 "});
8484
8485 let mocked_response = lsp::SignatureHelp {
8486 signatures: vec![lsp::SignatureInformation {
8487 label: "fn sample(param1: u8, param2: u8)".to_string(),
8488 documentation: None,
8489 parameters: Some(vec![
8490 lsp::ParameterInformation {
8491 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8492 documentation: None,
8493 },
8494 lsp::ParameterInformation {
8495 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8496 documentation: None,
8497 },
8498 ]),
8499 active_parameter: None,
8500 }],
8501 active_signature: Some(0),
8502 active_parameter: Some(1),
8503 };
8504 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8505 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8506 .await;
8507
8508 // When selecting a range, the popover is gone.
8509 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8510 cx.update_editor(|editor, window, cx| {
8511 editor.change_selections(None, window, cx, |s| {
8512 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8513 })
8514 });
8515 cx.assert_editor_state(indoc! {"
8516 fn main() {
8517 sample(param1, «ˇparam2»);
8518 }
8519
8520 fn sample(param1: u8, param2: u8) {}
8521 "});
8522 cx.editor(|editor, _, _| {
8523 assert!(!editor.signature_help_state.is_shown());
8524 });
8525
8526 // When unselecting again, the popover is back if within the brackets.
8527 cx.update_editor(|editor, window, cx| {
8528 editor.change_selections(None, window, cx, |s| {
8529 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8530 })
8531 });
8532 cx.assert_editor_state(indoc! {"
8533 fn main() {
8534 sample(param1, ˇparam2);
8535 }
8536
8537 fn sample(param1: u8, param2: u8) {}
8538 "});
8539 handle_signature_help_request(&mut cx, mocked_response).await;
8540 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8541 .await;
8542 cx.editor(|editor, _, _| {
8543 assert!(editor.signature_help_state.is_shown());
8544 });
8545
8546 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8547 cx.update_editor(|editor, window, cx| {
8548 editor.change_selections(None, window, cx, |s| {
8549 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8550 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8551 })
8552 });
8553 cx.assert_editor_state(indoc! {"
8554 fn main() {
8555 sample(param1, ˇparam2);
8556 }
8557
8558 fn sample(param1: u8, param2: u8) {}
8559 "});
8560
8561 let mocked_response = lsp::SignatureHelp {
8562 signatures: vec![lsp::SignatureInformation {
8563 label: "fn sample(param1: u8, param2: u8)".to_string(),
8564 documentation: None,
8565 parameters: Some(vec![
8566 lsp::ParameterInformation {
8567 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8568 documentation: None,
8569 },
8570 lsp::ParameterInformation {
8571 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8572 documentation: None,
8573 },
8574 ]),
8575 active_parameter: None,
8576 }],
8577 active_signature: Some(0),
8578 active_parameter: Some(1),
8579 };
8580 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8581 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8582 .await;
8583 cx.update_editor(|editor, _, cx| {
8584 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8585 });
8586 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8587 .await;
8588 cx.update_editor(|editor, window, cx| {
8589 editor.change_selections(None, window, cx, |s| {
8590 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8591 })
8592 });
8593 cx.assert_editor_state(indoc! {"
8594 fn main() {
8595 sample(param1, «ˇparam2»);
8596 }
8597
8598 fn sample(param1: u8, param2: u8) {}
8599 "});
8600 cx.update_editor(|editor, window, cx| {
8601 editor.change_selections(None, window, cx, |s| {
8602 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8603 })
8604 });
8605 cx.assert_editor_state(indoc! {"
8606 fn main() {
8607 sample(param1, ˇparam2);
8608 }
8609
8610 fn sample(param1: u8, param2: u8) {}
8611 "});
8612 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8613 .await;
8614}
8615
8616#[gpui::test]
8617async fn test_completion(cx: &mut gpui::TestAppContext) {
8618 init_test(cx, |_| {});
8619
8620 let mut cx = EditorLspTestContext::new_rust(
8621 lsp::ServerCapabilities {
8622 completion_provider: Some(lsp::CompletionOptions {
8623 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8624 resolve_provider: Some(true),
8625 ..Default::default()
8626 }),
8627 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8628 ..Default::default()
8629 },
8630 cx,
8631 )
8632 .await;
8633 let counter = Arc::new(AtomicUsize::new(0));
8634
8635 cx.set_state(indoc! {"
8636 oneˇ
8637 two
8638 three
8639 "});
8640 cx.simulate_keystroke(".");
8641 handle_completion_request(
8642 &mut cx,
8643 indoc! {"
8644 one.|<>
8645 two
8646 three
8647 "},
8648 vec!["first_completion", "second_completion"],
8649 counter.clone(),
8650 )
8651 .await;
8652 cx.condition(|editor, _| editor.context_menu_visible())
8653 .await;
8654 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8655
8656 let _handler = handle_signature_help_request(
8657 &mut cx,
8658 lsp::SignatureHelp {
8659 signatures: vec![lsp::SignatureInformation {
8660 label: "test signature".to_string(),
8661 documentation: None,
8662 parameters: Some(vec![lsp::ParameterInformation {
8663 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8664 documentation: None,
8665 }]),
8666 active_parameter: None,
8667 }],
8668 active_signature: None,
8669 active_parameter: None,
8670 },
8671 );
8672 cx.update_editor(|editor, window, cx| {
8673 assert!(
8674 !editor.signature_help_state.is_shown(),
8675 "No signature help was called for"
8676 );
8677 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8678 });
8679 cx.run_until_parked();
8680 cx.update_editor(|editor, _, _| {
8681 assert!(
8682 !editor.signature_help_state.is_shown(),
8683 "No signature help should be shown when completions menu is open"
8684 );
8685 });
8686
8687 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8688 editor.context_menu_next(&Default::default(), window, cx);
8689 editor
8690 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8691 .unwrap()
8692 });
8693 cx.assert_editor_state(indoc! {"
8694 one.second_completionˇ
8695 two
8696 three
8697 "});
8698
8699 handle_resolve_completion_request(
8700 &mut cx,
8701 Some(vec![
8702 (
8703 //This overlaps with the primary completion edit which is
8704 //misbehavior from the LSP spec, test that we filter it out
8705 indoc! {"
8706 one.second_ˇcompletion
8707 two
8708 threeˇ
8709 "},
8710 "overlapping additional edit",
8711 ),
8712 (
8713 indoc! {"
8714 one.second_completion
8715 two
8716 threeˇ
8717 "},
8718 "\nadditional edit",
8719 ),
8720 ]),
8721 )
8722 .await;
8723 apply_additional_edits.await.unwrap();
8724 cx.assert_editor_state(indoc! {"
8725 one.second_completionˇ
8726 two
8727 three
8728 additional edit
8729 "});
8730
8731 cx.set_state(indoc! {"
8732 one.second_completion
8733 twoˇ
8734 threeˇ
8735 additional edit
8736 "});
8737 cx.simulate_keystroke(" ");
8738 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8739 cx.simulate_keystroke("s");
8740 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8741
8742 cx.assert_editor_state(indoc! {"
8743 one.second_completion
8744 two sˇ
8745 three sˇ
8746 additional edit
8747 "});
8748 handle_completion_request(
8749 &mut cx,
8750 indoc! {"
8751 one.second_completion
8752 two s
8753 three <s|>
8754 additional edit
8755 "},
8756 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8757 counter.clone(),
8758 )
8759 .await;
8760 cx.condition(|editor, _| editor.context_menu_visible())
8761 .await;
8762 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8763
8764 cx.simulate_keystroke("i");
8765
8766 handle_completion_request(
8767 &mut cx,
8768 indoc! {"
8769 one.second_completion
8770 two si
8771 three <si|>
8772 additional edit
8773 "},
8774 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8775 counter.clone(),
8776 )
8777 .await;
8778 cx.condition(|editor, _| editor.context_menu_visible())
8779 .await;
8780 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8781
8782 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8783 editor
8784 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8785 .unwrap()
8786 });
8787 cx.assert_editor_state(indoc! {"
8788 one.second_completion
8789 two sixth_completionˇ
8790 three sixth_completionˇ
8791 additional edit
8792 "});
8793
8794 apply_additional_edits.await.unwrap();
8795
8796 update_test_language_settings(&mut cx, |settings| {
8797 settings.defaults.show_completions_on_input = Some(false);
8798 });
8799 cx.set_state("editorˇ");
8800 cx.simulate_keystroke(".");
8801 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8802 cx.simulate_keystroke("c");
8803 cx.simulate_keystroke("l");
8804 cx.simulate_keystroke("o");
8805 cx.assert_editor_state("editor.cloˇ");
8806 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8807 cx.update_editor(|editor, window, cx| {
8808 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8809 });
8810 handle_completion_request(
8811 &mut cx,
8812 "editor.<clo|>",
8813 vec!["close", "clobber"],
8814 counter.clone(),
8815 )
8816 .await;
8817 cx.condition(|editor, _| editor.context_menu_visible())
8818 .await;
8819 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8820
8821 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8822 editor
8823 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8824 .unwrap()
8825 });
8826 cx.assert_editor_state("editor.closeˇ");
8827 handle_resolve_completion_request(&mut cx, None).await;
8828 apply_additional_edits.await.unwrap();
8829}
8830
8831#[gpui::test]
8832async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
8833 init_test(cx, |_| {});
8834
8835 let fs = FakeFs::new(cx.executor());
8836 fs.insert_tree(
8837 path!("/a"),
8838 json!({
8839 "main.ts": "a",
8840 }),
8841 )
8842 .await;
8843
8844 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8845 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8846 let typescript_language = Arc::new(Language::new(
8847 LanguageConfig {
8848 name: "TypeScript".into(),
8849 matcher: LanguageMatcher {
8850 path_suffixes: vec!["ts".to_string()],
8851 ..LanguageMatcher::default()
8852 },
8853 line_comments: vec!["// ".into()],
8854 ..LanguageConfig::default()
8855 },
8856 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8857 ));
8858 language_registry.add(typescript_language.clone());
8859 let mut fake_servers = language_registry.register_fake_lsp(
8860 "TypeScript",
8861 FakeLspAdapter {
8862 capabilities: lsp::ServerCapabilities {
8863 completion_provider: Some(lsp::CompletionOptions {
8864 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8865 ..lsp::CompletionOptions::default()
8866 }),
8867 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8868 ..lsp::ServerCapabilities::default()
8869 },
8870 // Emulate vtsls label generation
8871 label_for_completion: Some(Box::new(|item, _| {
8872 let text = if let Some(description) = item
8873 .label_details
8874 .as_ref()
8875 .and_then(|label_details| label_details.description.as_ref())
8876 {
8877 format!("{} {}", item.label, description)
8878 } else if let Some(detail) = &item.detail {
8879 format!("{} {}", item.label, detail)
8880 } else {
8881 item.label.clone()
8882 };
8883 let len = text.len();
8884 Some(language::CodeLabel {
8885 text,
8886 runs: Vec::new(),
8887 filter_range: 0..len,
8888 })
8889 })),
8890 ..FakeLspAdapter::default()
8891 },
8892 );
8893 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8894 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8895 let worktree_id = workspace
8896 .update(cx, |workspace, _window, cx| {
8897 workspace.project().update(cx, |project, cx| {
8898 project.worktrees(cx).next().unwrap().read(cx).id()
8899 })
8900 })
8901 .unwrap();
8902 let _buffer = project
8903 .update(cx, |project, cx| {
8904 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
8905 })
8906 .await
8907 .unwrap();
8908 let editor = workspace
8909 .update(cx, |workspace, window, cx| {
8910 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8911 })
8912 .unwrap()
8913 .await
8914 .unwrap()
8915 .downcast::<Editor>()
8916 .unwrap();
8917 let fake_server = fake_servers.next().await.unwrap();
8918
8919 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8920 let multiline_label_2 = "a\nb\nc\n";
8921 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8922 let multiline_description = "d\ne\nf\n";
8923 let multiline_detail_2 = "g\nh\ni\n";
8924
8925 let mut completion_handle =
8926 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8927 Ok(Some(lsp::CompletionResponse::Array(vec![
8928 lsp::CompletionItem {
8929 label: multiline_label.to_string(),
8930 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8931 range: lsp::Range {
8932 start: lsp::Position {
8933 line: params.text_document_position.position.line,
8934 character: params.text_document_position.position.character,
8935 },
8936 end: lsp::Position {
8937 line: params.text_document_position.position.line,
8938 character: params.text_document_position.position.character,
8939 },
8940 },
8941 new_text: "new_text_1".to_string(),
8942 })),
8943 ..lsp::CompletionItem::default()
8944 },
8945 lsp::CompletionItem {
8946 label: "single line label 1".to_string(),
8947 detail: Some(multiline_detail.to_string()),
8948 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8949 range: lsp::Range {
8950 start: lsp::Position {
8951 line: params.text_document_position.position.line,
8952 character: params.text_document_position.position.character,
8953 },
8954 end: lsp::Position {
8955 line: params.text_document_position.position.line,
8956 character: params.text_document_position.position.character,
8957 },
8958 },
8959 new_text: "new_text_2".to_string(),
8960 })),
8961 ..lsp::CompletionItem::default()
8962 },
8963 lsp::CompletionItem {
8964 label: "single line label 2".to_string(),
8965 label_details: Some(lsp::CompletionItemLabelDetails {
8966 description: Some(multiline_description.to_string()),
8967 detail: None,
8968 }),
8969 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8970 range: lsp::Range {
8971 start: lsp::Position {
8972 line: params.text_document_position.position.line,
8973 character: params.text_document_position.position.character,
8974 },
8975 end: lsp::Position {
8976 line: params.text_document_position.position.line,
8977 character: params.text_document_position.position.character,
8978 },
8979 },
8980 new_text: "new_text_2".to_string(),
8981 })),
8982 ..lsp::CompletionItem::default()
8983 },
8984 lsp::CompletionItem {
8985 label: multiline_label_2.to_string(),
8986 detail: Some(multiline_detail_2.to_string()),
8987 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8988 range: lsp::Range {
8989 start: lsp::Position {
8990 line: params.text_document_position.position.line,
8991 character: params.text_document_position.position.character,
8992 },
8993 end: lsp::Position {
8994 line: params.text_document_position.position.line,
8995 character: params.text_document_position.position.character,
8996 },
8997 },
8998 new_text: "new_text_3".to_string(),
8999 })),
9000 ..lsp::CompletionItem::default()
9001 },
9002 lsp::CompletionItem {
9003 label: "Label with many spaces and \t but without newlines".to_string(),
9004 detail: Some(
9005 "Details with many spaces and \t but without newlines".to_string(),
9006 ),
9007 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9008 range: lsp::Range {
9009 start: lsp::Position {
9010 line: params.text_document_position.position.line,
9011 character: params.text_document_position.position.character,
9012 },
9013 end: lsp::Position {
9014 line: params.text_document_position.position.line,
9015 character: params.text_document_position.position.character,
9016 },
9017 },
9018 new_text: "new_text_4".to_string(),
9019 })),
9020 ..lsp::CompletionItem::default()
9021 },
9022 ])))
9023 });
9024
9025 editor.update_in(cx, |editor, window, cx| {
9026 cx.focus_self(window);
9027 editor.move_to_end(&MoveToEnd, window, cx);
9028 editor.handle_input(".", window, cx);
9029 });
9030 cx.run_until_parked();
9031 completion_handle.next().await.unwrap();
9032
9033 editor.update(cx, |editor, _| {
9034 assert!(editor.context_menu_visible());
9035 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9036 {
9037 let completion_labels = menu
9038 .completions
9039 .borrow()
9040 .iter()
9041 .map(|c| c.label.text.clone())
9042 .collect::<Vec<_>>();
9043 assert_eq!(
9044 completion_labels,
9045 &[
9046 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9047 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9048 "single line label 2 d e f ",
9049 "a b c g h i ",
9050 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9051 ],
9052 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9053 );
9054
9055 for completion in menu
9056 .completions
9057 .borrow()
9058 .iter() {
9059 assert_eq!(
9060 completion.label.filter_range,
9061 0..completion.label.text.len(),
9062 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9063 );
9064 }
9065
9066 } else {
9067 panic!("expected completion menu to be open");
9068 }
9069 });
9070}
9071
9072#[gpui::test]
9073async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
9074 init_test(cx, |_| {});
9075 let mut cx = EditorLspTestContext::new_rust(
9076 lsp::ServerCapabilities {
9077 completion_provider: Some(lsp::CompletionOptions {
9078 trigger_characters: Some(vec![".".to_string()]),
9079 ..Default::default()
9080 }),
9081 ..Default::default()
9082 },
9083 cx,
9084 )
9085 .await;
9086 cx.lsp
9087 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9088 Ok(Some(lsp::CompletionResponse::Array(vec![
9089 lsp::CompletionItem {
9090 label: "first".into(),
9091 ..Default::default()
9092 },
9093 lsp::CompletionItem {
9094 label: "last".into(),
9095 ..Default::default()
9096 },
9097 ])))
9098 });
9099 cx.set_state("variableˇ");
9100 cx.simulate_keystroke(".");
9101 cx.executor().run_until_parked();
9102
9103 cx.update_editor(|editor, _, _| {
9104 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9105 {
9106 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9107 } else {
9108 panic!("expected completion menu to be open");
9109 }
9110 });
9111
9112 cx.update_editor(|editor, window, cx| {
9113 editor.move_page_down(&MovePageDown::default(), window, cx);
9114 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9115 {
9116 assert!(
9117 menu.selected_item == 1,
9118 "expected PageDown to select the last item from the context menu"
9119 );
9120 } else {
9121 panic!("expected completion menu to stay open after PageDown");
9122 }
9123 });
9124
9125 cx.update_editor(|editor, window, cx| {
9126 editor.move_page_up(&MovePageUp::default(), window, cx);
9127 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9128 {
9129 assert!(
9130 menu.selected_item == 0,
9131 "expected PageUp to select the first item from the context menu"
9132 );
9133 } else {
9134 panic!("expected completion menu to stay open after PageUp");
9135 }
9136 });
9137}
9138
9139#[gpui::test]
9140async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
9141 init_test(cx, |_| {});
9142 let mut cx = EditorLspTestContext::new_rust(
9143 lsp::ServerCapabilities {
9144 completion_provider: Some(lsp::CompletionOptions {
9145 trigger_characters: Some(vec![".".to_string()]),
9146 ..Default::default()
9147 }),
9148 ..Default::default()
9149 },
9150 cx,
9151 )
9152 .await;
9153 cx.lsp
9154 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9155 Ok(Some(lsp::CompletionResponse::Array(vec![
9156 lsp::CompletionItem {
9157 label: "Range".into(),
9158 sort_text: Some("a".into()),
9159 ..Default::default()
9160 },
9161 lsp::CompletionItem {
9162 label: "r".into(),
9163 sort_text: Some("b".into()),
9164 ..Default::default()
9165 },
9166 lsp::CompletionItem {
9167 label: "ret".into(),
9168 sort_text: Some("c".into()),
9169 ..Default::default()
9170 },
9171 lsp::CompletionItem {
9172 label: "return".into(),
9173 sort_text: Some("d".into()),
9174 ..Default::default()
9175 },
9176 lsp::CompletionItem {
9177 label: "slice".into(),
9178 sort_text: Some("d".into()),
9179 ..Default::default()
9180 },
9181 ])))
9182 });
9183 cx.set_state("rˇ");
9184 cx.executor().run_until_parked();
9185 cx.update_editor(|editor, window, cx| {
9186 editor.show_completions(
9187 &ShowCompletions {
9188 trigger: Some("r".into()),
9189 },
9190 window,
9191 cx,
9192 );
9193 });
9194 cx.executor().run_until_parked();
9195
9196 cx.update_editor(|editor, _, _| {
9197 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9198 {
9199 assert_eq!(
9200 completion_menu_entries(&menu),
9201 &["r", "ret", "Range", "return"]
9202 );
9203 } else {
9204 panic!("expected completion menu to be open");
9205 }
9206 });
9207}
9208
9209#[gpui::test]
9210async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
9211 init_test(cx, |_| {});
9212
9213 let mut cx = EditorLspTestContext::new_rust(
9214 lsp::ServerCapabilities {
9215 completion_provider: Some(lsp::CompletionOptions {
9216 trigger_characters: Some(vec![".".to_string()]),
9217 resolve_provider: Some(true),
9218 ..Default::default()
9219 }),
9220 ..Default::default()
9221 },
9222 cx,
9223 )
9224 .await;
9225
9226 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9227 cx.simulate_keystroke(".");
9228 let completion_item = lsp::CompletionItem {
9229 label: "Some".into(),
9230 kind: Some(lsp::CompletionItemKind::SNIPPET),
9231 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9232 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9233 kind: lsp::MarkupKind::Markdown,
9234 value: "```rust\nSome(2)\n```".to_string(),
9235 })),
9236 deprecated: Some(false),
9237 sort_text: Some("Some".to_string()),
9238 filter_text: Some("Some".to_string()),
9239 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9240 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9241 range: lsp::Range {
9242 start: lsp::Position {
9243 line: 0,
9244 character: 22,
9245 },
9246 end: lsp::Position {
9247 line: 0,
9248 character: 22,
9249 },
9250 },
9251 new_text: "Some(2)".to_string(),
9252 })),
9253 additional_text_edits: Some(vec![lsp::TextEdit {
9254 range: lsp::Range {
9255 start: lsp::Position {
9256 line: 0,
9257 character: 20,
9258 },
9259 end: lsp::Position {
9260 line: 0,
9261 character: 22,
9262 },
9263 },
9264 new_text: "".to_string(),
9265 }]),
9266 ..Default::default()
9267 };
9268
9269 let closure_completion_item = completion_item.clone();
9270 let counter = Arc::new(AtomicUsize::new(0));
9271 let counter_clone = counter.clone();
9272 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9273 let task_completion_item = closure_completion_item.clone();
9274 counter_clone.fetch_add(1, atomic::Ordering::Release);
9275 async move {
9276 Ok(Some(lsp::CompletionResponse::Array(vec![
9277 task_completion_item,
9278 ])))
9279 }
9280 });
9281
9282 cx.condition(|editor, _| editor.context_menu_visible())
9283 .await;
9284 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9285 assert!(request.next().await.is_some());
9286 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9287
9288 cx.simulate_keystroke("S");
9289 cx.simulate_keystroke("o");
9290 cx.simulate_keystroke("m");
9291 cx.condition(|editor, _| editor.context_menu_visible())
9292 .await;
9293 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9294 assert!(request.next().await.is_some());
9295 assert!(request.next().await.is_some());
9296 assert!(request.next().await.is_some());
9297 request.close();
9298 assert!(request.next().await.is_none());
9299 assert_eq!(
9300 counter.load(atomic::Ordering::Acquire),
9301 4,
9302 "With the completions menu open, only one LSP request should happen per input"
9303 );
9304}
9305
9306#[gpui::test]
9307async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
9308 init_test(cx, |_| {});
9309 let mut cx = EditorTestContext::new(cx).await;
9310 let language = Arc::new(Language::new(
9311 LanguageConfig {
9312 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9313 ..Default::default()
9314 },
9315 Some(tree_sitter_rust::LANGUAGE.into()),
9316 ));
9317 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9318
9319 // If multiple selections intersect a line, the line is only toggled once.
9320 cx.set_state(indoc! {"
9321 fn a() {
9322 «//b();
9323 ˇ»// «c();
9324 //ˇ» d();
9325 }
9326 "});
9327
9328 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9329
9330 cx.assert_editor_state(indoc! {"
9331 fn a() {
9332 «b();
9333 c();
9334 ˇ» d();
9335 }
9336 "});
9337
9338 // The comment prefix is inserted at the same column for every line in a
9339 // selection.
9340 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9341
9342 cx.assert_editor_state(indoc! {"
9343 fn a() {
9344 // «b();
9345 // c();
9346 ˇ»// d();
9347 }
9348 "});
9349
9350 // If a selection ends at the beginning of a line, that line is not toggled.
9351 cx.set_selections_state(indoc! {"
9352 fn a() {
9353 // b();
9354 «// c();
9355 ˇ» // d();
9356 }
9357 "});
9358
9359 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9360
9361 cx.assert_editor_state(indoc! {"
9362 fn a() {
9363 // b();
9364 «c();
9365 ˇ» // d();
9366 }
9367 "});
9368
9369 // If a selection span a single line and is empty, the line is toggled.
9370 cx.set_state(indoc! {"
9371 fn a() {
9372 a();
9373 b();
9374 ˇ
9375 }
9376 "});
9377
9378 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9379
9380 cx.assert_editor_state(indoc! {"
9381 fn a() {
9382 a();
9383 b();
9384 //•ˇ
9385 }
9386 "});
9387
9388 // If a selection span multiple lines, empty lines are not toggled.
9389 cx.set_state(indoc! {"
9390 fn a() {
9391 «a();
9392
9393 c();ˇ»
9394 }
9395 "});
9396
9397 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9398
9399 cx.assert_editor_state(indoc! {"
9400 fn a() {
9401 // «a();
9402
9403 // c();ˇ»
9404 }
9405 "});
9406
9407 // If a selection includes multiple comment prefixes, all lines are uncommented.
9408 cx.set_state(indoc! {"
9409 fn a() {
9410 «// a();
9411 /// b();
9412 //! c();ˇ»
9413 }
9414 "});
9415
9416 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9417
9418 cx.assert_editor_state(indoc! {"
9419 fn a() {
9420 «a();
9421 b();
9422 c();ˇ»
9423 }
9424 "});
9425}
9426
9427#[gpui::test]
9428async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
9429 init_test(cx, |_| {});
9430 let mut cx = EditorTestContext::new(cx).await;
9431 let language = Arc::new(Language::new(
9432 LanguageConfig {
9433 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9434 ..Default::default()
9435 },
9436 Some(tree_sitter_rust::LANGUAGE.into()),
9437 ));
9438 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9439
9440 let toggle_comments = &ToggleComments {
9441 advance_downwards: false,
9442 ignore_indent: true,
9443 };
9444
9445 // If multiple selections intersect a line, the line is only toggled once.
9446 cx.set_state(indoc! {"
9447 fn a() {
9448 // «b();
9449 // c();
9450 // ˇ» d();
9451 }
9452 "});
9453
9454 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9455
9456 cx.assert_editor_state(indoc! {"
9457 fn a() {
9458 «b();
9459 c();
9460 ˇ» d();
9461 }
9462 "});
9463
9464 // The comment prefix is inserted at the beginning of each line
9465 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9466
9467 cx.assert_editor_state(indoc! {"
9468 fn a() {
9469 // «b();
9470 // c();
9471 // ˇ» d();
9472 }
9473 "});
9474
9475 // If a selection ends at the beginning of a line, that line is not toggled.
9476 cx.set_selections_state(indoc! {"
9477 fn a() {
9478 // b();
9479 // «c();
9480 ˇ»// d();
9481 }
9482 "});
9483
9484 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9485
9486 cx.assert_editor_state(indoc! {"
9487 fn a() {
9488 // b();
9489 «c();
9490 ˇ»// d();
9491 }
9492 "});
9493
9494 // If a selection span a single line and is empty, the line is toggled.
9495 cx.set_state(indoc! {"
9496 fn a() {
9497 a();
9498 b();
9499 ˇ
9500 }
9501 "});
9502
9503 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9504
9505 cx.assert_editor_state(indoc! {"
9506 fn a() {
9507 a();
9508 b();
9509 //ˇ
9510 }
9511 "});
9512
9513 // If a selection span multiple lines, empty lines are not toggled.
9514 cx.set_state(indoc! {"
9515 fn a() {
9516 «a();
9517
9518 c();ˇ»
9519 }
9520 "});
9521
9522 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9523
9524 cx.assert_editor_state(indoc! {"
9525 fn a() {
9526 // «a();
9527
9528 // c();ˇ»
9529 }
9530 "});
9531
9532 // If a selection includes multiple comment prefixes, all lines are uncommented.
9533 cx.set_state(indoc! {"
9534 fn a() {
9535 // «a();
9536 /// b();
9537 //! c();ˇ»
9538 }
9539 "});
9540
9541 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9542
9543 cx.assert_editor_state(indoc! {"
9544 fn a() {
9545 «a();
9546 b();
9547 c();ˇ»
9548 }
9549 "});
9550}
9551
9552#[gpui::test]
9553async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
9554 init_test(cx, |_| {});
9555
9556 let language = Arc::new(Language::new(
9557 LanguageConfig {
9558 line_comments: vec!["// ".into()],
9559 ..Default::default()
9560 },
9561 Some(tree_sitter_rust::LANGUAGE.into()),
9562 ));
9563
9564 let mut cx = EditorTestContext::new(cx).await;
9565
9566 cx.language_registry().add(language.clone());
9567 cx.update_buffer(|buffer, cx| {
9568 buffer.set_language(Some(language), cx);
9569 });
9570
9571 let toggle_comments = &ToggleComments {
9572 advance_downwards: true,
9573 ignore_indent: false,
9574 };
9575
9576 // Single cursor on one line -> advance
9577 // Cursor moves horizontally 3 characters as well on non-blank line
9578 cx.set_state(indoc!(
9579 "fn a() {
9580 ˇdog();
9581 cat();
9582 }"
9583 ));
9584 cx.update_editor(|editor, window, cx| {
9585 editor.toggle_comments(toggle_comments, window, cx);
9586 });
9587 cx.assert_editor_state(indoc!(
9588 "fn a() {
9589 // dog();
9590 catˇ();
9591 }"
9592 ));
9593
9594 // Single selection on one line -> don't advance
9595 cx.set_state(indoc!(
9596 "fn a() {
9597 «dog()ˇ»;
9598 cat();
9599 }"
9600 ));
9601 cx.update_editor(|editor, window, cx| {
9602 editor.toggle_comments(toggle_comments, window, cx);
9603 });
9604 cx.assert_editor_state(indoc!(
9605 "fn a() {
9606 // «dog()ˇ»;
9607 cat();
9608 }"
9609 ));
9610
9611 // Multiple cursors on one line -> advance
9612 cx.set_state(indoc!(
9613 "fn a() {
9614 ˇdˇog();
9615 cat();
9616 }"
9617 ));
9618 cx.update_editor(|editor, window, cx| {
9619 editor.toggle_comments(toggle_comments, window, cx);
9620 });
9621 cx.assert_editor_state(indoc!(
9622 "fn a() {
9623 // dog();
9624 catˇ(ˇ);
9625 }"
9626 ));
9627
9628 // Multiple cursors on one line, with selection -> don't advance
9629 cx.set_state(indoc!(
9630 "fn a() {
9631 ˇdˇog«()ˇ»;
9632 cat();
9633 }"
9634 ));
9635 cx.update_editor(|editor, window, cx| {
9636 editor.toggle_comments(toggle_comments, window, cx);
9637 });
9638 cx.assert_editor_state(indoc!(
9639 "fn a() {
9640 // ˇdˇog«()ˇ»;
9641 cat();
9642 }"
9643 ));
9644
9645 // Single cursor on one line -> advance
9646 // Cursor moves to column 0 on blank line
9647 cx.set_state(indoc!(
9648 "fn a() {
9649 ˇdog();
9650
9651 cat();
9652 }"
9653 ));
9654 cx.update_editor(|editor, window, cx| {
9655 editor.toggle_comments(toggle_comments, window, cx);
9656 });
9657 cx.assert_editor_state(indoc!(
9658 "fn a() {
9659 // dog();
9660 ˇ
9661 cat();
9662 }"
9663 ));
9664
9665 // Single cursor on one line -> advance
9666 // Cursor starts and ends at column 0
9667 cx.set_state(indoc!(
9668 "fn a() {
9669 ˇ dog();
9670 cat();
9671 }"
9672 ));
9673 cx.update_editor(|editor, window, cx| {
9674 editor.toggle_comments(toggle_comments, window, cx);
9675 });
9676 cx.assert_editor_state(indoc!(
9677 "fn a() {
9678 // dog();
9679 ˇ cat();
9680 }"
9681 ));
9682}
9683
9684#[gpui::test]
9685async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9686 init_test(cx, |_| {});
9687
9688 let mut cx = EditorTestContext::new(cx).await;
9689
9690 let html_language = Arc::new(
9691 Language::new(
9692 LanguageConfig {
9693 name: "HTML".into(),
9694 block_comment: Some(("<!-- ".into(), " -->".into())),
9695 ..Default::default()
9696 },
9697 Some(tree_sitter_html::LANGUAGE.into()),
9698 )
9699 .with_injection_query(
9700 r#"
9701 (script_element
9702 (raw_text) @injection.content
9703 (#set! injection.language "javascript"))
9704 "#,
9705 )
9706 .unwrap(),
9707 );
9708
9709 let javascript_language = Arc::new(Language::new(
9710 LanguageConfig {
9711 name: "JavaScript".into(),
9712 line_comments: vec!["// ".into()],
9713 ..Default::default()
9714 },
9715 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9716 ));
9717
9718 cx.language_registry().add(html_language.clone());
9719 cx.language_registry().add(javascript_language.clone());
9720 cx.update_buffer(|buffer, cx| {
9721 buffer.set_language(Some(html_language), cx);
9722 });
9723
9724 // Toggle comments for empty selections
9725 cx.set_state(
9726 &r#"
9727 <p>A</p>ˇ
9728 <p>B</p>ˇ
9729 <p>C</p>ˇ
9730 "#
9731 .unindent(),
9732 );
9733 cx.update_editor(|editor, window, cx| {
9734 editor.toggle_comments(&ToggleComments::default(), window, cx)
9735 });
9736 cx.assert_editor_state(
9737 &r#"
9738 <!-- <p>A</p>ˇ -->
9739 <!-- <p>B</p>ˇ -->
9740 <!-- <p>C</p>ˇ -->
9741 "#
9742 .unindent(),
9743 );
9744 cx.update_editor(|editor, window, cx| {
9745 editor.toggle_comments(&ToggleComments::default(), window, cx)
9746 });
9747 cx.assert_editor_state(
9748 &r#"
9749 <p>A</p>ˇ
9750 <p>B</p>ˇ
9751 <p>C</p>ˇ
9752 "#
9753 .unindent(),
9754 );
9755
9756 // Toggle comments for mixture of empty and non-empty selections, where
9757 // multiple selections occupy a given line.
9758 cx.set_state(
9759 &r#"
9760 <p>A«</p>
9761 <p>ˇ»B</p>ˇ
9762 <p>C«</p>
9763 <p>ˇ»D</p>ˇ
9764 "#
9765 .unindent(),
9766 );
9767
9768 cx.update_editor(|editor, window, cx| {
9769 editor.toggle_comments(&ToggleComments::default(), window, cx)
9770 });
9771 cx.assert_editor_state(
9772 &r#"
9773 <!-- <p>A«</p>
9774 <p>ˇ»B</p>ˇ -->
9775 <!-- <p>C«</p>
9776 <p>ˇ»D</p>ˇ -->
9777 "#
9778 .unindent(),
9779 );
9780 cx.update_editor(|editor, window, cx| {
9781 editor.toggle_comments(&ToggleComments::default(), window, cx)
9782 });
9783 cx.assert_editor_state(
9784 &r#"
9785 <p>A«</p>
9786 <p>ˇ»B</p>ˇ
9787 <p>C«</p>
9788 <p>ˇ»D</p>ˇ
9789 "#
9790 .unindent(),
9791 );
9792
9793 // Toggle comments when different languages are active for different
9794 // selections.
9795 cx.set_state(
9796 &r#"
9797 ˇ<script>
9798 ˇvar x = new Y();
9799 ˇ</script>
9800 "#
9801 .unindent(),
9802 );
9803 cx.executor().run_until_parked();
9804 cx.update_editor(|editor, window, cx| {
9805 editor.toggle_comments(&ToggleComments::default(), window, cx)
9806 });
9807 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9808 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9809 cx.assert_editor_state(
9810 &r#"
9811 <!-- ˇ<script> -->
9812 // ˇvar x = new Y();
9813 <!-- ˇ</script> -->
9814 "#
9815 .unindent(),
9816 );
9817}
9818
9819#[gpui::test]
9820fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9821 init_test(cx, |_| {});
9822
9823 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9824 let multibuffer = cx.new(|cx| {
9825 let mut multibuffer = MultiBuffer::new(ReadWrite);
9826 multibuffer.push_excerpts(
9827 buffer.clone(),
9828 [
9829 ExcerptRange {
9830 context: Point::new(0, 0)..Point::new(0, 4),
9831 primary: None,
9832 },
9833 ExcerptRange {
9834 context: Point::new(1, 0)..Point::new(1, 4),
9835 primary: None,
9836 },
9837 ],
9838 cx,
9839 );
9840 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9841 multibuffer
9842 });
9843
9844 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9845 editor.update_in(cx, |editor, window, cx| {
9846 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9847 editor.change_selections(None, window, cx, |s| {
9848 s.select_ranges([
9849 Point::new(0, 0)..Point::new(0, 0),
9850 Point::new(1, 0)..Point::new(1, 0),
9851 ])
9852 });
9853
9854 editor.handle_input("X", window, cx);
9855 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9856 assert_eq!(
9857 editor.selections.ranges(cx),
9858 [
9859 Point::new(0, 1)..Point::new(0, 1),
9860 Point::new(1, 1)..Point::new(1, 1),
9861 ]
9862 );
9863
9864 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9865 editor.change_selections(None, window, cx, |s| {
9866 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9867 });
9868 editor.backspace(&Default::default(), window, cx);
9869 assert_eq!(editor.text(cx), "Xa\nbbb");
9870 assert_eq!(
9871 editor.selections.ranges(cx),
9872 [Point::new(1, 0)..Point::new(1, 0)]
9873 );
9874
9875 editor.change_selections(None, window, cx, |s| {
9876 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9877 });
9878 editor.backspace(&Default::default(), window, cx);
9879 assert_eq!(editor.text(cx), "X\nbb");
9880 assert_eq!(
9881 editor.selections.ranges(cx),
9882 [Point::new(0, 1)..Point::new(0, 1)]
9883 );
9884 });
9885}
9886
9887#[gpui::test]
9888fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9889 init_test(cx, |_| {});
9890
9891 let markers = vec![('[', ']').into(), ('(', ')').into()];
9892 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9893 indoc! {"
9894 [aaaa
9895 (bbbb]
9896 cccc)",
9897 },
9898 markers.clone(),
9899 );
9900 let excerpt_ranges = markers.into_iter().map(|marker| {
9901 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9902 ExcerptRange {
9903 context,
9904 primary: None,
9905 }
9906 });
9907 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9908 let multibuffer = cx.new(|cx| {
9909 let mut multibuffer = MultiBuffer::new(ReadWrite);
9910 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9911 multibuffer
9912 });
9913
9914 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9915 editor.update_in(cx, |editor, window, cx| {
9916 let (expected_text, selection_ranges) = marked_text_ranges(
9917 indoc! {"
9918 aaaa
9919 bˇbbb
9920 bˇbbˇb
9921 cccc"
9922 },
9923 true,
9924 );
9925 assert_eq!(editor.text(cx), expected_text);
9926 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9927
9928 editor.handle_input("X", window, cx);
9929
9930 let (expected_text, expected_selections) = marked_text_ranges(
9931 indoc! {"
9932 aaaa
9933 bXˇbbXb
9934 bXˇbbXˇb
9935 cccc"
9936 },
9937 false,
9938 );
9939 assert_eq!(editor.text(cx), expected_text);
9940 assert_eq!(editor.selections.ranges(cx), expected_selections);
9941
9942 editor.newline(&Newline, window, cx);
9943 let (expected_text, expected_selections) = marked_text_ranges(
9944 indoc! {"
9945 aaaa
9946 bX
9947 ˇbbX
9948 b
9949 bX
9950 ˇbbX
9951 ˇb
9952 cccc"
9953 },
9954 false,
9955 );
9956 assert_eq!(editor.text(cx), expected_text);
9957 assert_eq!(editor.selections.ranges(cx), expected_selections);
9958 });
9959}
9960
9961#[gpui::test]
9962fn test_refresh_selections(cx: &mut TestAppContext) {
9963 init_test(cx, |_| {});
9964
9965 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9966 let mut excerpt1_id = None;
9967 let multibuffer = cx.new(|cx| {
9968 let mut multibuffer = MultiBuffer::new(ReadWrite);
9969 excerpt1_id = multibuffer
9970 .push_excerpts(
9971 buffer.clone(),
9972 [
9973 ExcerptRange {
9974 context: Point::new(0, 0)..Point::new(1, 4),
9975 primary: None,
9976 },
9977 ExcerptRange {
9978 context: Point::new(1, 0)..Point::new(2, 4),
9979 primary: None,
9980 },
9981 ],
9982 cx,
9983 )
9984 .into_iter()
9985 .next();
9986 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9987 multibuffer
9988 });
9989
9990 let editor = cx.add_window(|window, cx| {
9991 let mut editor = build_editor(multibuffer.clone(), window, cx);
9992 let snapshot = editor.snapshot(window, cx);
9993 editor.change_selections(None, window, cx, |s| {
9994 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9995 });
9996 editor.begin_selection(
9997 Point::new(2, 1).to_display_point(&snapshot),
9998 true,
9999 1,
10000 window,
10001 cx,
10002 );
10003 assert_eq!(
10004 editor.selections.ranges(cx),
10005 [
10006 Point::new(1, 3)..Point::new(1, 3),
10007 Point::new(2, 1)..Point::new(2, 1),
10008 ]
10009 );
10010 editor
10011 });
10012
10013 // Refreshing selections is a no-op when excerpts haven't changed.
10014 _ = editor.update(cx, |editor, window, cx| {
10015 editor.change_selections(None, window, cx, |s| s.refresh());
10016 assert_eq!(
10017 editor.selections.ranges(cx),
10018 [
10019 Point::new(1, 3)..Point::new(1, 3),
10020 Point::new(2, 1)..Point::new(2, 1),
10021 ]
10022 );
10023 });
10024
10025 multibuffer.update(cx, |multibuffer, cx| {
10026 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10027 });
10028 _ = editor.update(cx, |editor, window, cx| {
10029 // Removing an excerpt causes the first selection to become degenerate.
10030 assert_eq!(
10031 editor.selections.ranges(cx),
10032 [
10033 Point::new(0, 0)..Point::new(0, 0),
10034 Point::new(0, 1)..Point::new(0, 1)
10035 ]
10036 );
10037
10038 // Refreshing selections will relocate the first selection to the original buffer
10039 // location.
10040 editor.change_selections(None, window, cx, |s| s.refresh());
10041 assert_eq!(
10042 editor.selections.ranges(cx),
10043 [
10044 Point::new(0, 1)..Point::new(0, 1),
10045 Point::new(0, 3)..Point::new(0, 3)
10046 ]
10047 );
10048 assert!(editor.selections.pending_anchor().is_some());
10049 });
10050}
10051
10052#[gpui::test]
10053fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10054 init_test(cx, |_| {});
10055
10056 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10057 let mut excerpt1_id = None;
10058 let multibuffer = cx.new(|cx| {
10059 let mut multibuffer = MultiBuffer::new(ReadWrite);
10060 excerpt1_id = multibuffer
10061 .push_excerpts(
10062 buffer.clone(),
10063 [
10064 ExcerptRange {
10065 context: Point::new(0, 0)..Point::new(1, 4),
10066 primary: None,
10067 },
10068 ExcerptRange {
10069 context: Point::new(1, 0)..Point::new(2, 4),
10070 primary: None,
10071 },
10072 ],
10073 cx,
10074 )
10075 .into_iter()
10076 .next();
10077 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10078 multibuffer
10079 });
10080
10081 let editor = cx.add_window(|window, cx| {
10082 let mut editor = build_editor(multibuffer.clone(), window, cx);
10083 let snapshot = editor.snapshot(window, cx);
10084 editor.begin_selection(
10085 Point::new(1, 3).to_display_point(&snapshot),
10086 false,
10087 1,
10088 window,
10089 cx,
10090 );
10091 assert_eq!(
10092 editor.selections.ranges(cx),
10093 [Point::new(1, 3)..Point::new(1, 3)]
10094 );
10095 editor
10096 });
10097
10098 multibuffer.update(cx, |multibuffer, cx| {
10099 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10100 });
10101 _ = editor.update(cx, |editor, window, cx| {
10102 assert_eq!(
10103 editor.selections.ranges(cx),
10104 [Point::new(0, 0)..Point::new(0, 0)]
10105 );
10106
10107 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10108 editor.change_selections(None, window, cx, |s| s.refresh());
10109 assert_eq!(
10110 editor.selections.ranges(cx),
10111 [Point::new(0, 3)..Point::new(0, 3)]
10112 );
10113 assert!(editor.selections.pending_anchor().is_some());
10114 });
10115}
10116
10117#[gpui::test]
10118async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
10119 init_test(cx, |_| {});
10120
10121 let language = Arc::new(
10122 Language::new(
10123 LanguageConfig {
10124 brackets: BracketPairConfig {
10125 pairs: vec![
10126 BracketPair {
10127 start: "{".to_string(),
10128 end: "}".to_string(),
10129 close: true,
10130 surround: true,
10131 newline: true,
10132 },
10133 BracketPair {
10134 start: "/* ".to_string(),
10135 end: " */".to_string(),
10136 close: true,
10137 surround: true,
10138 newline: true,
10139 },
10140 ],
10141 ..Default::default()
10142 },
10143 ..Default::default()
10144 },
10145 Some(tree_sitter_rust::LANGUAGE.into()),
10146 )
10147 .with_indents_query("")
10148 .unwrap(),
10149 );
10150
10151 let text = concat!(
10152 "{ }\n", //
10153 " x\n", //
10154 " /* */\n", //
10155 "x\n", //
10156 "{{} }\n", //
10157 );
10158
10159 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10160 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10161 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10162 editor
10163 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10164 .await;
10165
10166 editor.update_in(cx, |editor, window, cx| {
10167 editor.change_selections(None, window, cx, |s| {
10168 s.select_display_ranges([
10169 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10170 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10171 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10172 ])
10173 });
10174 editor.newline(&Newline, window, cx);
10175
10176 assert_eq!(
10177 editor.buffer().read(cx).read(cx).text(),
10178 concat!(
10179 "{ \n", // Suppress rustfmt
10180 "\n", //
10181 "}\n", //
10182 " x\n", //
10183 " /* \n", //
10184 " \n", //
10185 " */\n", //
10186 "x\n", //
10187 "{{} \n", //
10188 "}\n", //
10189 )
10190 );
10191 });
10192}
10193
10194#[gpui::test]
10195fn test_highlighted_ranges(cx: &mut TestAppContext) {
10196 init_test(cx, |_| {});
10197
10198 let editor = cx.add_window(|window, cx| {
10199 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10200 build_editor(buffer.clone(), window, cx)
10201 });
10202
10203 _ = editor.update(cx, |editor, window, cx| {
10204 struct Type1;
10205 struct Type2;
10206
10207 let buffer = editor.buffer.read(cx).snapshot(cx);
10208
10209 let anchor_range =
10210 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10211
10212 editor.highlight_background::<Type1>(
10213 &[
10214 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10215 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10216 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10217 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10218 ],
10219 |_| Hsla::red(),
10220 cx,
10221 );
10222 editor.highlight_background::<Type2>(
10223 &[
10224 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10225 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10226 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10227 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10228 ],
10229 |_| Hsla::green(),
10230 cx,
10231 );
10232
10233 let snapshot = editor.snapshot(window, cx);
10234 let mut highlighted_ranges = editor.background_highlights_in_range(
10235 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10236 &snapshot,
10237 cx.theme().colors(),
10238 );
10239 // Enforce a consistent ordering based on color without relying on the ordering of the
10240 // highlight's `TypeId` which is non-executor.
10241 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10242 assert_eq!(
10243 highlighted_ranges,
10244 &[
10245 (
10246 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10247 Hsla::red(),
10248 ),
10249 (
10250 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10251 Hsla::red(),
10252 ),
10253 (
10254 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10255 Hsla::green(),
10256 ),
10257 (
10258 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10259 Hsla::green(),
10260 ),
10261 ]
10262 );
10263 assert_eq!(
10264 editor.background_highlights_in_range(
10265 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10266 &snapshot,
10267 cx.theme().colors(),
10268 ),
10269 &[(
10270 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10271 Hsla::red(),
10272 )]
10273 );
10274 });
10275}
10276
10277#[gpui::test]
10278async fn test_following(cx: &mut gpui::TestAppContext) {
10279 init_test(cx, |_| {});
10280
10281 let fs = FakeFs::new(cx.executor());
10282 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10283
10284 let buffer = project.update(cx, |project, cx| {
10285 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10286 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10287 });
10288 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10289 let follower = cx.update(|cx| {
10290 cx.open_window(
10291 WindowOptions {
10292 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10293 gpui::Point::new(px(0.), px(0.)),
10294 gpui::Point::new(px(10.), px(80.)),
10295 ))),
10296 ..Default::default()
10297 },
10298 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10299 )
10300 .unwrap()
10301 });
10302
10303 let is_still_following = Rc::new(RefCell::new(true));
10304 let follower_edit_event_count = Rc::new(RefCell::new(0));
10305 let pending_update = Rc::new(RefCell::new(None));
10306 let leader_entity = leader.root(cx).unwrap();
10307 let follower_entity = follower.root(cx).unwrap();
10308 _ = follower.update(cx, {
10309 let update = pending_update.clone();
10310 let is_still_following = is_still_following.clone();
10311 let follower_edit_event_count = follower_edit_event_count.clone();
10312 |_, window, cx| {
10313 cx.subscribe_in(
10314 &leader_entity,
10315 window,
10316 move |_, leader, event, window, cx| {
10317 leader.read(cx).add_event_to_update_proto(
10318 event,
10319 &mut update.borrow_mut(),
10320 window,
10321 cx,
10322 );
10323 },
10324 )
10325 .detach();
10326
10327 cx.subscribe_in(
10328 &follower_entity,
10329 window,
10330 move |_, _, event: &EditorEvent, _window, _cx| {
10331 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10332 *is_still_following.borrow_mut() = false;
10333 }
10334
10335 if let EditorEvent::BufferEdited = event {
10336 *follower_edit_event_count.borrow_mut() += 1;
10337 }
10338 },
10339 )
10340 .detach();
10341 }
10342 });
10343
10344 // Update the selections only
10345 _ = leader.update(cx, |leader, window, cx| {
10346 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10347 });
10348 follower
10349 .update(cx, |follower, window, cx| {
10350 follower.apply_update_proto(
10351 &project,
10352 pending_update.borrow_mut().take().unwrap(),
10353 window,
10354 cx,
10355 )
10356 })
10357 .unwrap()
10358 .await
10359 .unwrap();
10360 _ = follower.update(cx, |follower, _, cx| {
10361 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10362 });
10363 assert!(*is_still_following.borrow());
10364 assert_eq!(*follower_edit_event_count.borrow(), 0);
10365
10366 // Update the scroll position only
10367 _ = leader.update(cx, |leader, window, cx| {
10368 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10369 });
10370 follower
10371 .update(cx, |follower, window, cx| {
10372 follower.apply_update_proto(
10373 &project,
10374 pending_update.borrow_mut().take().unwrap(),
10375 window,
10376 cx,
10377 )
10378 })
10379 .unwrap()
10380 .await
10381 .unwrap();
10382 assert_eq!(
10383 follower
10384 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10385 .unwrap(),
10386 gpui::Point::new(1.5, 3.5)
10387 );
10388 assert!(*is_still_following.borrow());
10389 assert_eq!(*follower_edit_event_count.borrow(), 0);
10390
10391 // Update the selections and scroll position. The follower's scroll position is updated
10392 // via autoscroll, not via the leader's exact scroll position.
10393 _ = leader.update(cx, |leader, window, cx| {
10394 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10395 leader.request_autoscroll(Autoscroll::newest(), cx);
10396 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10397 });
10398 follower
10399 .update(cx, |follower, window, cx| {
10400 follower.apply_update_proto(
10401 &project,
10402 pending_update.borrow_mut().take().unwrap(),
10403 window,
10404 cx,
10405 )
10406 })
10407 .unwrap()
10408 .await
10409 .unwrap();
10410 _ = follower.update(cx, |follower, _, cx| {
10411 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10412 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10413 });
10414 assert!(*is_still_following.borrow());
10415
10416 // Creating a pending selection that precedes another selection
10417 _ = leader.update(cx, |leader, window, cx| {
10418 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10419 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10420 });
10421 follower
10422 .update(cx, |follower, window, cx| {
10423 follower.apply_update_proto(
10424 &project,
10425 pending_update.borrow_mut().take().unwrap(),
10426 window,
10427 cx,
10428 )
10429 })
10430 .unwrap()
10431 .await
10432 .unwrap();
10433 _ = follower.update(cx, |follower, _, cx| {
10434 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10435 });
10436 assert!(*is_still_following.borrow());
10437
10438 // Extend the pending selection so that it surrounds another selection
10439 _ = leader.update(cx, |leader, window, cx| {
10440 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10441 });
10442 follower
10443 .update(cx, |follower, window, cx| {
10444 follower.apply_update_proto(
10445 &project,
10446 pending_update.borrow_mut().take().unwrap(),
10447 window,
10448 cx,
10449 )
10450 })
10451 .unwrap()
10452 .await
10453 .unwrap();
10454 _ = follower.update(cx, |follower, _, cx| {
10455 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10456 });
10457
10458 // Scrolling locally breaks the follow
10459 _ = follower.update(cx, |follower, window, cx| {
10460 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10461 follower.set_scroll_anchor(
10462 ScrollAnchor {
10463 anchor: top_anchor,
10464 offset: gpui::Point::new(0.0, 0.5),
10465 },
10466 window,
10467 cx,
10468 );
10469 });
10470 assert!(!(*is_still_following.borrow()));
10471}
10472
10473#[gpui::test]
10474async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
10475 init_test(cx, |_| {});
10476
10477 let fs = FakeFs::new(cx.executor());
10478 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10479 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10480 let pane = workspace
10481 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10482 .unwrap();
10483
10484 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10485
10486 let leader = pane.update_in(cx, |_, window, cx| {
10487 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10488 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10489 });
10490
10491 // Start following the editor when it has no excerpts.
10492 let mut state_message =
10493 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10494 let workspace_entity = workspace.root(cx).unwrap();
10495 let follower_1 = cx
10496 .update_window(*workspace.deref(), |_, window, cx| {
10497 Editor::from_state_proto(
10498 workspace_entity,
10499 ViewId {
10500 creator: Default::default(),
10501 id: 0,
10502 },
10503 &mut state_message,
10504 window,
10505 cx,
10506 )
10507 })
10508 .unwrap()
10509 .unwrap()
10510 .await
10511 .unwrap();
10512
10513 let update_message = Rc::new(RefCell::new(None));
10514 follower_1.update_in(cx, {
10515 let update = update_message.clone();
10516 |_, window, cx| {
10517 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10518 leader.read(cx).add_event_to_update_proto(
10519 event,
10520 &mut update.borrow_mut(),
10521 window,
10522 cx,
10523 );
10524 })
10525 .detach();
10526 }
10527 });
10528
10529 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10530 (
10531 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10532 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10533 )
10534 });
10535
10536 // Insert some excerpts.
10537 leader.update(cx, |leader, cx| {
10538 leader.buffer.update(cx, |multibuffer, cx| {
10539 let excerpt_ids = multibuffer.push_excerpts(
10540 buffer_1.clone(),
10541 [
10542 ExcerptRange {
10543 context: 1..6,
10544 primary: None,
10545 },
10546 ExcerptRange {
10547 context: 12..15,
10548 primary: None,
10549 },
10550 ExcerptRange {
10551 context: 0..3,
10552 primary: None,
10553 },
10554 ],
10555 cx,
10556 );
10557 multibuffer.insert_excerpts_after(
10558 excerpt_ids[0],
10559 buffer_2.clone(),
10560 [
10561 ExcerptRange {
10562 context: 8..12,
10563 primary: None,
10564 },
10565 ExcerptRange {
10566 context: 0..6,
10567 primary: None,
10568 },
10569 ],
10570 cx,
10571 );
10572 });
10573 });
10574
10575 // Apply the update of adding the excerpts.
10576 follower_1
10577 .update_in(cx, |follower, window, cx| {
10578 follower.apply_update_proto(
10579 &project,
10580 update_message.borrow().clone().unwrap(),
10581 window,
10582 cx,
10583 )
10584 })
10585 .await
10586 .unwrap();
10587 assert_eq!(
10588 follower_1.update(cx, |editor, cx| editor.text(cx)),
10589 leader.update(cx, |editor, cx| editor.text(cx))
10590 );
10591 update_message.borrow_mut().take();
10592
10593 // Start following separately after it already has excerpts.
10594 let mut state_message =
10595 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10596 let workspace_entity = workspace.root(cx).unwrap();
10597 let follower_2 = cx
10598 .update_window(*workspace.deref(), |_, window, cx| {
10599 Editor::from_state_proto(
10600 workspace_entity,
10601 ViewId {
10602 creator: Default::default(),
10603 id: 0,
10604 },
10605 &mut state_message,
10606 window,
10607 cx,
10608 )
10609 })
10610 .unwrap()
10611 .unwrap()
10612 .await
10613 .unwrap();
10614 assert_eq!(
10615 follower_2.update(cx, |editor, cx| editor.text(cx)),
10616 leader.update(cx, |editor, cx| editor.text(cx))
10617 );
10618
10619 // Remove some excerpts.
10620 leader.update(cx, |leader, cx| {
10621 leader.buffer.update(cx, |multibuffer, cx| {
10622 let excerpt_ids = multibuffer.excerpt_ids();
10623 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10624 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10625 });
10626 });
10627
10628 // Apply the update of removing the excerpts.
10629 follower_1
10630 .update_in(cx, |follower, window, cx| {
10631 follower.apply_update_proto(
10632 &project,
10633 update_message.borrow().clone().unwrap(),
10634 window,
10635 cx,
10636 )
10637 })
10638 .await
10639 .unwrap();
10640 follower_2
10641 .update_in(cx, |follower, window, cx| {
10642 follower.apply_update_proto(
10643 &project,
10644 update_message.borrow().clone().unwrap(),
10645 window,
10646 cx,
10647 )
10648 })
10649 .await
10650 .unwrap();
10651 update_message.borrow_mut().take();
10652 assert_eq!(
10653 follower_1.update(cx, |editor, cx| editor.text(cx)),
10654 leader.update(cx, |editor, cx| editor.text(cx))
10655 );
10656}
10657
10658#[gpui::test]
10659async fn go_to_prev_overlapping_diagnostic(
10660 executor: BackgroundExecutor,
10661 cx: &mut gpui::TestAppContext,
10662) {
10663 init_test(cx, |_| {});
10664
10665 let mut cx = EditorTestContext::new(cx).await;
10666 let lsp_store =
10667 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10668
10669 cx.set_state(indoc! {"
10670 ˇfn func(abc def: i32) -> u32 {
10671 }
10672 "});
10673
10674 cx.update(|_, cx| {
10675 lsp_store.update(cx, |lsp_store, cx| {
10676 lsp_store
10677 .update_diagnostics(
10678 LanguageServerId(0),
10679 lsp::PublishDiagnosticsParams {
10680 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10681 version: None,
10682 diagnostics: vec![
10683 lsp::Diagnostic {
10684 range: lsp::Range::new(
10685 lsp::Position::new(0, 11),
10686 lsp::Position::new(0, 12),
10687 ),
10688 severity: Some(lsp::DiagnosticSeverity::ERROR),
10689 ..Default::default()
10690 },
10691 lsp::Diagnostic {
10692 range: lsp::Range::new(
10693 lsp::Position::new(0, 12),
10694 lsp::Position::new(0, 15),
10695 ),
10696 severity: Some(lsp::DiagnosticSeverity::ERROR),
10697 ..Default::default()
10698 },
10699 lsp::Diagnostic {
10700 range: lsp::Range::new(
10701 lsp::Position::new(0, 25),
10702 lsp::Position::new(0, 28),
10703 ),
10704 severity: Some(lsp::DiagnosticSeverity::ERROR),
10705 ..Default::default()
10706 },
10707 ],
10708 },
10709 &[],
10710 cx,
10711 )
10712 .unwrap()
10713 });
10714 });
10715
10716 executor.run_until_parked();
10717
10718 cx.update_editor(|editor, window, cx| {
10719 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10720 });
10721
10722 cx.assert_editor_state(indoc! {"
10723 fn func(abc def: i32) -> ˇu32 {
10724 }
10725 "});
10726
10727 cx.update_editor(|editor, window, cx| {
10728 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10729 });
10730
10731 cx.assert_editor_state(indoc! {"
10732 fn func(abc ˇdef: i32) -> u32 {
10733 }
10734 "});
10735
10736 cx.update_editor(|editor, window, cx| {
10737 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10738 });
10739
10740 cx.assert_editor_state(indoc! {"
10741 fn func(abcˇ def: i32) -> u32 {
10742 }
10743 "});
10744
10745 cx.update_editor(|editor, window, cx| {
10746 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10747 });
10748
10749 cx.assert_editor_state(indoc! {"
10750 fn func(abc def: i32) -> ˇu32 {
10751 }
10752 "});
10753}
10754
10755#[gpui::test]
10756async fn cycle_through_same_place_diagnostics(
10757 executor: BackgroundExecutor,
10758 cx: &mut gpui::TestAppContext,
10759) {
10760 init_test(cx, |_| {});
10761
10762 let mut cx = EditorTestContext::new(cx).await;
10763 let lsp_store =
10764 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10765
10766 cx.set_state(indoc! {"
10767 ˇfn func(abc def: i32) -> u32 {
10768 }
10769 "});
10770
10771 cx.update(|_, cx| {
10772 lsp_store.update(cx, |lsp_store, cx| {
10773 lsp_store
10774 .update_diagnostics(
10775 LanguageServerId(0),
10776 lsp::PublishDiagnosticsParams {
10777 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10778 version: None,
10779 diagnostics: vec![
10780 lsp::Diagnostic {
10781 range: lsp::Range::new(
10782 lsp::Position::new(0, 11),
10783 lsp::Position::new(0, 12),
10784 ),
10785 severity: Some(lsp::DiagnosticSeverity::ERROR),
10786 ..Default::default()
10787 },
10788 lsp::Diagnostic {
10789 range: lsp::Range::new(
10790 lsp::Position::new(0, 12),
10791 lsp::Position::new(0, 15),
10792 ),
10793 severity: Some(lsp::DiagnosticSeverity::ERROR),
10794 ..Default::default()
10795 },
10796 lsp::Diagnostic {
10797 range: lsp::Range::new(
10798 lsp::Position::new(0, 12),
10799 lsp::Position::new(0, 15),
10800 ),
10801 severity: Some(lsp::DiagnosticSeverity::ERROR),
10802 ..Default::default()
10803 },
10804 lsp::Diagnostic {
10805 range: lsp::Range::new(
10806 lsp::Position::new(0, 25),
10807 lsp::Position::new(0, 28),
10808 ),
10809 severity: Some(lsp::DiagnosticSeverity::ERROR),
10810 ..Default::default()
10811 },
10812 ],
10813 },
10814 &[],
10815 cx,
10816 )
10817 .unwrap()
10818 });
10819 });
10820 executor.run_until_parked();
10821
10822 //// Backward
10823
10824 // Fourth diagnostic
10825 cx.update_editor(|editor, window, cx| {
10826 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10827 });
10828 cx.assert_editor_state(indoc! {"
10829 fn func(abc def: i32) -> ˇu32 {
10830 }
10831 "});
10832
10833 // Third diagnostic
10834 cx.update_editor(|editor, window, cx| {
10835 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10836 });
10837 cx.assert_editor_state(indoc! {"
10838 fn func(abc ˇdef: i32) -> u32 {
10839 }
10840 "});
10841
10842 // Second diagnostic, same place
10843 cx.update_editor(|editor, window, cx| {
10844 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10845 });
10846 cx.assert_editor_state(indoc! {"
10847 fn func(abc ˇdef: i32) -> u32 {
10848 }
10849 "});
10850
10851 // First diagnostic
10852 cx.update_editor(|editor, window, cx| {
10853 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10854 });
10855 cx.assert_editor_state(indoc! {"
10856 fn func(abcˇ def: i32) -> u32 {
10857 }
10858 "});
10859
10860 // Wrapped over, fourth diagnostic
10861 cx.update_editor(|editor, window, cx| {
10862 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10863 });
10864 cx.assert_editor_state(indoc! {"
10865 fn func(abc def: i32) -> ˇu32 {
10866 }
10867 "});
10868
10869 cx.update_editor(|editor, window, cx| {
10870 editor.move_to_beginning(&MoveToBeginning, window, cx);
10871 });
10872 cx.assert_editor_state(indoc! {"
10873 ˇfn func(abc def: i32) -> u32 {
10874 }
10875 "});
10876
10877 //// Forward
10878
10879 // First diagnostic
10880 cx.update_editor(|editor, window, cx| {
10881 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10882 });
10883 cx.assert_editor_state(indoc! {"
10884 fn func(abcˇ def: i32) -> u32 {
10885 }
10886 "});
10887
10888 // Second diagnostic
10889 cx.update_editor(|editor, window, cx| {
10890 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10891 });
10892 cx.assert_editor_state(indoc! {"
10893 fn func(abc ˇdef: i32) -> u32 {
10894 }
10895 "});
10896
10897 // Third diagnostic, same place
10898 cx.update_editor(|editor, window, cx| {
10899 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10900 });
10901 cx.assert_editor_state(indoc! {"
10902 fn func(abc ˇdef: i32) -> u32 {
10903 }
10904 "});
10905
10906 // Fourth diagnostic
10907 cx.update_editor(|editor, window, cx| {
10908 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10909 });
10910 cx.assert_editor_state(indoc! {"
10911 fn func(abc def: i32) -> ˇu32 {
10912 }
10913 "});
10914
10915 // Wrapped around, first diagnostic
10916 cx.update_editor(|editor, window, cx| {
10917 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10918 });
10919 cx.assert_editor_state(indoc! {"
10920 fn func(abcˇ def: i32) -> u32 {
10921 }
10922 "});
10923}
10924
10925#[gpui::test]
10926async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10927 init_test(cx, |_| {});
10928
10929 let mut cx = EditorTestContext::new(cx).await;
10930
10931 cx.set_state(indoc! {"
10932 fn func(abˇc def: i32) -> u32 {
10933 }
10934 "});
10935 let lsp_store =
10936 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10937
10938 cx.update(|_, cx| {
10939 lsp_store.update(cx, |lsp_store, cx| {
10940 lsp_store.update_diagnostics(
10941 LanguageServerId(0),
10942 lsp::PublishDiagnosticsParams {
10943 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10944 version: None,
10945 diagnostics: vec![lsp::Diagnostic {
10946 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10947 severity: Some(lsp::DiagnosticSeverity::ERROR),
10948 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10949 ..Default::default()
10950 }],
10951 },
10952 &[],
10953 cx,
10954 )
10955 })
10956 }).unwrap();
10957 cx.run_until_parked();
10958 cx.update_editor(|editor, window, cx| {
10959 hover_popover::hover(editor, &Default::default(), window, cx)
10960 });
10961 cx.run_until_parked();
10962 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10963}
10964
10965#[gpui::test]
10966async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10967 init_test(cx, |_| {});
10968
10969 let mut cx = EditorTestContext::new(cx).await;
10970
10971 let diff_base = r#"
10972 use some::mod;
10973
10974 const A: u32 = 42;
10975
10976 fn main() {
10977 println!("hello");
10978
10979 println!("world");
10980 }
10981 "#
10982 .unindent();
10983
10984 // Edits are modified, removed, modified, added
10985 cx.set_state(
10986 &r#"
10987 use some::modified;
10988
10989 ˇ
10990 fn main() {
10991 println!("hello there");
10992
10993 println!("around the");
10994 println!("world");
10995 }
10996 "#
10997 .unindent(),
10998 );
10999
11000 cx.set_diff_base(&diff_base);
11001 executor.run_until_parked();
11002
11003 cx.update_editor(|editor, window, cx| {
11004 //Wrap around the bottom of the buffer
11005 for _ in 0..3 {
11006 editor.go_to_next_hunk(&GoToHunk, window, cx);
11007 }
11008 });
11009
11010 cx.assert_editor_state(
11011 &r#"
11012 ˇuse some::modified;
11013
11014
11015 fn main() {
11016 println!("hello there");
11017
11018 println!("around the");
11019 println!("world");
11020 }
11021 "#
11022 .unindent(),
11023 );
11024
11025 cx.update_editor(|editor, window, cx| {
11026 //Wrap around the top of the buffer
11027 for _ in 0..2 {
11028 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11029 }
11030 });
11031
11032 cx.assert_editor_state(
11033 &r#"
11034 use some::modified;
11035
11036
11037 fn main() {
11038 ˇ println!("hello there");
11039
11040 println!("around the");
11041 println!("world");
11042 }
11043 "#
11044 .unindent(),
11045 );
11046
11047 cx.update_editor(|editor, window, cx| {
11048 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11049 });
11050
11051 cx.assert_editor_state(
11052 &r#"
11053 use some::modified;
11054
11055 ˇ
11056 fn main() {
11057 println!("hello there");
11058
11059 println!("around the");
11060 println!("world");
11061 }
11062 "#
11063 .unindent(),
11064 );
11065
11066 cx.update_editor(|editor, window, cx| {
11067 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11068 });
11069
11070 cx.assert_editor_state(
11071 &r#"
11072 ˇuse some::modified;
11073
11074
11075 fn main() {
11076 println!("hello there");
11077
11078 println!("around the");
11079 println!("world");
11080 }
11081 "#
11082 .unindent(),
11083 );
11084
11085 cx.update_editor(|editor, window, cx| {
11086 for _ in 0..2 {
11087 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11088 }
11089 });
11090
11091 cx.assert_editor_state(
11092 &r#"
11093 use some::modified;
11094
11095
11096 fn main() {
11097 ˇ println!("hello there");
11098
11099 println!("around the");
11100 println!("world");
11101 }
11102 "#
11103 .unindent(),
11104 );
11105
11106 cx.update_editor(|editor, window, cx| {
11107 editor.fold(&Fold, window, cx);
11108 });
11109
11110 cx.update_editor(|editor, window, cx| {
11111 editor.go_to_next_hunk(&GoToHunk, window, cx);
11112 });
11113
11114 cx.assert_editor_state(
11115 &r#"
11116 ˇuse some::modified;
11117
11118
11119 fn main() {
11120 println!("hello there");
11121
11122 println!("around the");
11123 println!("world");
11124 }
11125 "#
11126 .unindent(),
11127 );
11128}
11129
11130#[test]
11131fn test_split_words() {
11132 fn split(text: &str) -> Vec<&str> {
11133 split_words(text).collect()
11134 }
11135
11136 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11137 assert_eq!(split("hello_world"), &["hello_", "world"]);
11138 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11139 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11140 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11141 assert_eq!(split("helloworld"), &["helloworld"]);
11142
11143 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11144}
11145
11146#[gpui::test]
11147async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
11148 init_test(cx, |_| {});
11149
11150 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11151 let mut assert = |before, after| {
11152 let _state_context = cx.set_state(before);
11153 cx.run_until_parked();
11154 cx.update_editor(|editor, window, cx| {
11155 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11156 });
11157 cx.assert_editor_state(after);
11158 };
11159
11160 // Outside bracket jumps to outside of matching bracket
11161 assert("console.logˇ(var);", "console.log(var)ˇ;");
11162 assert("console.log(var)ˇ;", "console.logˇ(var);");
11163
11164 // Inside bracket jumps to inside of matching bracket
11165 assert("console.log(ˇvar);", "console.log(varˇ);");
11166 assert("console.log(varˇ);", "console.log(ˇvar);");
11167
11168 // When outside a bracket and inside, favor jumping to the inside bracket
11169 assert(
11170 "console.log('foo', [1, 2, 3]ˇ);",
11171 "console.log(ˇ'foo', [1, 2, 3]);",
11172 );
11173 assert(
11174 "console.log(ˇ'foo', [1, 2, 3]);",
11175 "console.log('foo', [1, 2, 3]ˇ);",
11176 );
11177
11178 // Bias forward if two options are equally likely
11179 assert(
11180 "let result = curried_fun()ˇ();",
11181 "let result = curried_fun()()ˇ;",
11182 );
11183
11184 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11185 assert(
11186 indoc! {"
11187 function test() {
11188 console.log('test')ˇ
11189 }"},
11190 indoc! {"
11191 function test() {
11192 console.logˇ('test')
11193 }"},
11194 );
11195}
11196
11197#[gpui::test]
11198async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
11199 init_test(cx, |_| {});
11200
11201 let fs = FakeFs::new(cx.executor());
11202 fs.insert_tree(
11203 path!("/a"),
11204 json!({
11205 "main.rs": "fn main() { let a = 5; }",
11206 "other.rs": "// Test file",
11207 }),
11208 )
11209 .await;
11210 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11211
11212 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11213 language_registry.add(Arc::new(Language::new(
11214 LanguageConfig {
11215 name: "Rust".into(),
11216 matcher: LanguageMatcher {
11217 path_suffixes: vec!["rs".to_string()],
11218 ..Default::default()
11219 },
11220 brackets: BracketPairConfig {
11221 pairs: vec![BracketPair {
11222 start: "{".to_string(),
11223 end: "}".to_string(),
11224 close: true,
11225 surround: true,
11226 newline: true,
11227 }],
11228 disabled_scopes_by_bracket_ix: Vec::new(),
11229 },
11230 ..Default::default()
11231 },
11232 Some(tree_sitter_rust::LANGUAGE.into()),
11233 )));
11234 let mut fake_servers = language_registry.register_fake_lsp(
11235 "Rust",
11236 FakeLspAdapter {
11237 capabilities: lsp::ServerCapabilities {
11238 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11239 first_trigger_character: "{".to_string(),
11240 more_trigger_character: None,
11241 }),
11242 ..Default::default()
11243 },
11244 ..Default::default()
11245 },
11246 );
11247
11248 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11249
11250 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11251
11252 let worktree_id = workspace
11253 .update(cx, |workspace, _, cx| {
11254 workspace.project().update(cx, |project, cx| {
11255 project.worktrees(cx).next().unwrap().read(cx).id()
11256 })
11257 })
11258 .unwrap();
11259
11260 let buffer = project
11261 .update(cx, |project, cx| {
11262 project.open_local_buffer(path!("/a/main.rs"), cx)
11263 })
11264 .await
11265 .unwrap();
11266 let editor_handle = workspace
11267 .update(cx, |workspace, window, cx| {
11268 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11269 })
11270 .unwrap()
11271 .await
11272 .unwrap()
11273 .downcast::<Editor>()
11274 .unwrap();
11275
11276 cx.executor().start_waiting();
11277 let fake_server = fake_servers.next().await.unwrap();
11278
11279 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11280 assert_eq!(
11281 params.text_document_position.text_document.uri,
11282 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11283 );
11284 assert_eq!(
11285 params.text_document_position.position,
11286 lsp::Position::new(0, 21),
11287 );
11288
11289 Ok(Some(vec![lsp::TextEdit {
11290 new_text: "]".to_string(),
11291 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11292 }]))
11293 });
11294
11295 editor_handle.update_in(cx, |editor, window, cx| {
11296 window.focus(&editor.focus_handle(cx));
11297 editor.change_selections(None, window, cx, |s| {
11298 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11299 });
11300 editor.handle_input("{", window, cx);
11301 });
11302
11303 cx.executor().run_until_parked();
11304
11305 buffer.update(cx, |buffer, _| {
11306 assert_eq!(
11307 buffer.text(),
11308 "fn main() { let a = {5}; }",
11309 "No extra braces from on type formatting should appear in the buffer"
11310 )
11311 });
11312}
11313
11314#[gpui::test]
11315async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
11316 init_test(cx, |_| {});
11317
11318 let fs = FakeFs::new(cx.executor());
11319 fs.insert_tree(
11320 path!("/a"),
11321 json!({
11322 "main.rs": "fn main() { let a = 5; }",
11323 "other.rs": "// Test file",
11324 }),
11325 )
11326 .await;
11327
11328 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11329
11330 let server_restarts = Arc::new(AtomicUsize::new(0));
11331 let closure_restarts = Arc::clone(&server_restarts);
11332 let language_server_name = "test language server";
11333 let language_name: LanguageName = "Rust".into();
11334
11335 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11336 language_registry.add(Arc::new(Language::new(
11337 LanguageConfig {
11338 name: language_name.clone(),
11339 matcher: LanguageMatcher {
11340 path_suffixes: vec!["rs".to_string()],
11341 ..Default::default()
11342 },
11343 ..Default::default()
11344 },
11345 Some(tree_sitter_rust::LANGUAGE.into()),
11346 )));
11347 let mut fake_servers = language_registry.register_fake_lsp(
11348 "Rust",
11349 FakeLspAdapter {
11350 name: language_server_name,
11351 initialization_options: Some(json!({
11352 "testOptionValue": true
11353 })),
11354 initializer: Some(Box::new(move |fake_server| {
11355 let task_restarts = Arc::clone(&closure_restarts);
11356 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11357 task_restarts.fetch_add(1, atomic::Ordering::Release);
11358 futures::future::ready(Ok(()))
11359 });
11360 })),
11361 ..Default::default()
11362 },
11363 );
11364
11365 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11366 let _buffer = project
11367 .update(cx, |project, cx| {
11368 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11369 })
11370 .await
11371 .unwrap();
11372 let _fake_server = fake_servers.next().await.unwrap();
11373 update_test_language_settings(cx, |language_settings| {
11374 language_settings.languages.insert(
11375 language_name.clone(),
11376 LanguageSettingsContent {
11377 tab_size: NonZeroU32::new(8),
11378 ..Default::default()
11379 },
11380 );
11381 });
11382 cx.executor().run_until_parked();
11383 assert_eq!(
11384 server_restarts.load(atomic::Ordering::Acquire),
11385 0,
11386 "Should not restart LSP server on an unrelated change"
11387 );
11388
11389 update_test_project_settings(cx, |project_settings| {
11390 project_settings.lsp.insert(
11391 "Some other server name".into(),
11392 LspSettings {
11393 binary: None,
11394 settings: None,
11395 initialization_options: Some(json!({
11396 "some other init value": false
11397 })),
11398 },
11399 );
11400 });
11401 cx.executor().run_until_parked();
11402 assert_eq!(
11403 server_restarts.load(atomic::Ordering::Acquire),
11404 0,
11405 "Should not restart LSP server on an unrelated LSP settings change"
11406 );
11407
11408 update_test_project_settings(cx, |project_settings| {
11409 project_settings.lsp.insert(
11410 language_server_name.into(),
11411 LspSettings {
11412 binary: None,
11413 settings: None,
11414 initialization_options: Some(json!({
11415 "anotherInitValue": false
11416 })),
11417 },
11418 );
11419 });
11420 cx.executor().run_until_parked();
11421 assert_eq!(
11422 server_restarts.load(atomic::Ordering::Acquire),
11423 1,
11424 "Should restart LSP server on a related LSP settings change"
11425 );
11426
11427 update_test_project_settings(cx, |project_settings| {
11428 project_settings.lsp.insert(
11429 language_server_name.into(),
11430 LspSettings {
11431 binary: None,
11432 settings: None,
11433 initialization_options: Some(json!({
11434 "anotherInitValue": false
11435 })),
11436 },
11437 );
11438 });
11439 cx.executor().run_until_parked();
11440 assert_eq!(
11441 server_restarts.load(atomic::Ordering::Acquire),
11442 1,
11443 "Should not restart LSP server on a related LSP settings change that is the same"
11444 );
11445
11446 update_test_project_settings(cx, |project_settings| {
11447 project_settings.lsp.insert(
11448 language_server_name.into(),
11449 LspSettings {
11450 binary: None,
11451 settings: None,
11452 initialization_options: None,
11453 },
11454 );
11455 });
11456 cx.executor().run_until_parked();
11457 assert_eq!(
11458 server_restarts.load(atomic::Ordering::Acquire),
11459 2,
11460 "Should restart LSP server on another related LSP settings change"
11461 );
11462}
11463
11464#[gpui::test]
11465async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
11466 init_test(cx, |_| {});
11467
11468 let mut cx = EditorLspTestContext::new_rust(
11469 lsp::ServerCapabilities {
11470 completion_provider: Some(lsp::CompletionOptions {
11471 trigger_characters: Some(vec![".".to_string()]),
11472 resolve_provider: Some(true),
11473 ..Default::default()
11474 }),
11475 ..Default::default()
11476 },
11477 cx,
11478 )
11479 .await;
11480
11481 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11482 cx.simulate_keystroke(".");
11483 let completion_item = lsp::CompletionItem {
11484 label: "some".into(),
11485 kind: Some(lsp::CompletionItemKind::SNIPPET),
11486 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11487 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11488 kind: lsp::MarkupKind::Markdown,
11489 value: "```rust\nSome(2)\n```".to_string(),
11490 })),
11491 deprecated: Some(false),
11492 sort_text: Some("fffffff2".to_string()),
11493 filter_text: Some("some".to_string()),
11494 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11495 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11496 range: lsp::Range {
11497 start: lsp::Position {
11498 line: 0,
11499 character: 22,
11500 },
11501 end: lsp::Position {
11502 line: 0,
11503 character: 22,
11504 },
11505 },
11506 new_text: "Some(2)".to_string(),
11507 })),
11508 additional_text_edits: Some(vec![lsp::TextEdit {
11509 range: lsp::Range {
11510 start: lsp::Position {
11511 line: 0,
11512 character: 20,
11513 },
11514 end: lsp::Position {
11515 line: 0,
11516 character: 22,
11517 },
11518 },
11519 new_text: "".to_string(),
11520 }]),
11521 ..Default::default()
11522 };
11523
11524 let closure_completion_item = completion_item.clone();
11525 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11526 let task_completion_item = closure_completion_item.clone();
11527 async move {
11528 Ok(Some(lsp::CompletionResponse::Array(vec![
11529 task_completion_item,
11530 ])))
11531 }
11532 });
11533
11534 request.next().await;
11535
11536 cx.condition(|editor, _| editor.context_menu_visible())
11537 .await;
11538 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11539 editor
11540 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11541 .unwrap()
11542 });
11543 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11544
11545 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11546 let task_completion_item = completion_item.clone();
11547 async move { Ok(task_completion_item) }
11548 })
11549 .next()
11550 .await
11551 .unwrap();
11552 apply_additional_edits.await.unwrap();
11553 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11554}
11555
11556#[gpui::test]
11557async fn test_completions_resolve_updates_labels_if_filter_text_matches(
11558 cx: &mut gpui::TestAppContext,
11559) {
11560 init_test(cx, |_| {});
11561
11562 let mut cx = EditorLspTestContext::new_rust(
11563 lsp::ServerCapabilities {
11564 completion_provider: Some(lsp::CompletionOptions {
11565 trigger_characters: Some(vec![".".to_string()]),
11566 resolve_provider: Some(true),
11567 ..Default::default()
11568 }),
11569 ..Default::default()
11570 },
11571 cx,
11572 )
11573 .await;
11574
11575 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11576 cx.simulate_keystroke(".");
11577
11578 let item1 = lsp::CompletionItem {
11579 label: "method id()".to_string(),
11580 filter_text: Some("id".to_string()),
11581 detail: None,
11582 documentation: None,
11583 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11584 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11585 new_text: ".id".to_string(),
11586 })),
11587 ..lsp::CompletionItem::default()
11588 };
11589
11590 let item2 = lsp::CompletionItem {
11591 label: "other".to_string(),
11592 filter_text: Some("other".to_string()),
11593 detail: None,
11594 documentation: None,
11595 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11596 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11597 new_text: ".other".to_string(),
11598 })),
11599 ..lsp::CompletionItem::default()
11600 };
11601
11602 let item1 = item1.clone();
11603 cx.handle_request::<lsp::request::Completion, _, _>({
11604 let item1 = item1.clone();
11605 move |_, _, _| {
11606 let item1 = item1.clone();
11607 let item2 = item2.clone();
11608 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11609 }
11610 })
11611 .next()
11612 .await;
11613
11614 cx.condition(|editor, _| editor.context_menu_visible())
11615 .await;
11616 cx.update_editor(|editor, _, _| {
11617 let context_menu = editor.context_menu.borrow_mut();
11618 let context_menu = context_menu
11619 .as_ref()
11620 .expect("Should have the context menu deployed");
11621 match context_menu {
11622 CodeContextMenu::Completions(completions_menu) => {
11623 let completions = completions_menu.completions.borrow_mut();
11624 assert_eq!(
11625 completions
11626 .iter()
11627 .map(|completion| &completion.label.text)
11628 .collect::<Vec<_>>(),
11629 vec!["method id()", "other"]
11630 )
11631 }
11632 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11633 }
11634 });
11635
11636 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11637 let item1 = item1.clone();
11638 move |_, item_to_resolve, _| {
11639 let item1 = item1.clone();
11640 async move {
11641 if item1 == item_to_resolve {
11642 Ok(lsp::CompletionItem {
11643 label: "method id()".to_string(),
11644 filter_text: Some("id".to_string()),
11645 detail: Some("Now resolved!".to_string()),
11646 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11647 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11648 range: lsp::Range::new(
11649 lsp::Position::new(0, 22),
11650 lsp::Position::new(0, 22),
11651 ),
11652 new_text: ".id".to_string(),
11653 })),
11654 ..lsp::CompletionItem::default()
11655 })
11656 } else {
11657 Ok(item_to_resolve)
11658 }
11659 }
11660 }
11661 })
11662 .next()
11663 .await
11664 .unwrap();
11665 cx.run_until_parked();
11666
11667 cx.update_editor(|editor, window, cx| {
11668 editor.context_menu_next(&Default::default(), window, cx);
11669 });
11670
11671 cx.update_editor(|editor, _, _| {
11672 let context_menu = editor.context_menu.borrow_mut();
11673 let context_menu = context_menu
11674 .as_ref()
11675 .expect("Should have the context menu deployed");
11676 match context_menu {
11677 CodeContextMenu::Completions(completions_menu) => {
11678 let completions = completions_menu.completions.borrow_mut();
11679 assert_eq!(
11680 completions
11681 .iter()
11682 .map(|completion| &completion.label.text)
11683 .collect::<Vec<_>>(),
11684 vec!["method id() Now resolved!", "other"],
11685 "Should update first completion label, but not second as the filter text did not match."
11686 );
11687 }
11688 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11689 }
11690 });
11691}
11692
11693#[gpui::test]
11694async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
11695 init_test(cx, |_| {});
11696
11697 let mut cx = EditorLspTestContext::new_rust(
11698 lsp::ServerCapabilities {
11699 completion_provider: Some(lsp::CompletionOptions {
11700 trigger_characters: Some(vec![".".to_string()]),
11701 resolve_provider: Some(true),
11702 ..Default::default()
11703 }),
11704 ..Default::default()
11705 },
11706 cx,
11707 )
11708 .await;
11709
11710 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11711 cx.simulate_keystroke(".");
11712
11713 let unresolved_item_1 = lsp::CompletionItem {
11714 label: "id".to_string(),
11715 filter_text: Some("id".to_string()),
11716 detail: None,
11717 documentation: None,
11718 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11719 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11720 new_text: ".id".to_string(),
11721 })),
11722 ..lsp::CompletionItem::default()
11723 };
11724 let resolved_item_1 = lsp::CompletionItem {
11725 additional_text_edits: Some(vec![lsp::TextEdit {
11726 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11727 new_text: "!!".to_string(),
11728 }]),
11729 ..unresolved_item_1.clone()
11730 };
11731 let unresolved_item_2 = lsp::CompletionItem {
11732 label: "other".to_string(),
11733 filter_text: Some("other".to_string()),
11734 detail: None,
11735 documentation: None,
11736 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11737 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11738 new_text: ".other".to_string(),
11739 })),
11740 ..lsp::CompletionItem::default()
11741 };
11742 let resolved_item_2 = lsp::CompletionItem {
11743 additional_text_edits: Some(vec![lsp::TextEdit {
11744 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11745 new_text: "??".to_string(),
11746 }]),
11747 ..unresolved_item_2.clone()
11748 };
11749
11750 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11751 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11752 cx.lsp
11753 .server
11754 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11755 let unresolved_item_1 = unresolved_item_1.clone();
11756 let resolved_item_1 = resolved_item_1.clone();
11757 let unresolved_item_2 = unresolved_item_2.clone();
11758 let resolved_item_2 = resolved_item_2.clone();
11759 let resolve_requests_1 = resolve_requests_1.clone();
11760 let resolve_requests_2 = resolve_requests_2.clone();
11761 move |unresolved_request, _| {
11762 let unresolved_item_1 = unresolved_item_1.clone();
11763 let resolved_item_1 = resolved_item_1.clone();
11764 let unresolved_item_2 = unresolved_item_2.clone();
11765 let resolved_item_2 = resolved_item_2.clone();
11766 let resolve_requests_1 = resolve_requests_1.clone();
11767 let resolve_requests_2 = resolve_requests_2.clone();
11768 async move {
11769 if unresolved_request == unresolved_item_1 {
11770 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11771 Ok(resolved_item_1.clone())
11772 } else if unresolved_request == unresolved_item_2 {
11773 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11774 Ok(resolved_item_2.clone())
11775 } else {
11776 panic!("Unexpected completion item {unresolved_request:?}")
11777 }
11778 }
11779 }
11780 })
11781 .detach();
11782
11783 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11784 let unresolved_item_1 = unresolved_item_1.clone();
11785 let unresolved_item_2 = unresolved_item_2.clone();
11786 async move {
11787 Ok(Some(lsp::CompletionResponse::Array(vec![
11788 unresolved_item_1,
11789 unresolved_item_2,
11790 ])))
11791 }
11792 })
11793 .next()
11794 .await;
11795
11796 cx.condition(|editor, _| editor.context_menu_visible())
11797 .await;
11798 cx.update_editor(|editor, _, _| {
11799 let context_menu = editor.context_menu.borrow_mut();
11800 let context_menu = context_menu
11801 .as_ref()
11802 .expect("Should have the context menu deployed");
11803 match context_menu {
11804 CodeContextMenu::Completions(completions_menu) => {
11805 let completions = completions_menu.completions.borrow_mut();
11806 assert_eq!(
11807 completions
11808 .iter()
11809 .map(|completion| &completion.label.text)
11810 .collect::<Vec<_>>(),
11811 vec!["id", "other"]
11812 )
11813 }
11814 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11815 }
11816 });
11817 cx.run_until_parked();
11818
11819 cx.update_editor(|editor, window, cx| {
11820 editor.context_menu_next(&ContextMenuNext, window, cx);
11821 });
11822 cx.run_until_parked();
11823 cx.update_editor(|editor, window, cx| {
11824 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11825 });
11826 cx.run_until_parked();
11827 cx.update_editor(|editor, window, cx| {
11828 editor.context_menu_next(&ContextMenuNext, window, cx);
11829 });
11830 cx.run_until_parked();
11831 cx.update_editor(|editor, window, cx| {
11832 editor
11833 .compose_completion(&ComposeCompletion::default(), window, cx)
11834 .expect("No task returned")
11835 })
11836 .await
11837 .expect("Completion failed");
11838 cx.run_until_parked();
11839
11840 cx.update_editor(|editor, _, cx| {
11841 assert_eq!(
11842 resolve_requests_1.load(atomic::Ordering::Acquire),
11843 1,
11844 "Should always resolve once despite multiple selections"
11845 );
11846 assert_eq!(
11847 resolve_requests_2.load(atomic::Ordering::Acquire),
11848 1,
11849 "Should always resolve once after multiple selections and applying the completion"
11850 );
11851 assert_eq!(
11852 editor.text(cx),
11853 "fn main() { let a = ??.other; }",
11854 "Should use resolved data when applying the completion"
11855 );
11856 });
11857}
11858
11859#[gpui::test]
11860async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
11861 init_test(cx, |_| {});
11862
11863 let item_0 = lsp::CompletionItem {
11864 label: "abs".into(),
11865 insert_text: Some("abs".into()),
11866 data: Some(json!({ "very": "special"})),
11867 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11868 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11869 lsp::InsertReplaceEdit {
11870 new_text: "abs".to_string(),
11871 insert: lsp::Range::default(),
11872 replace: lsp::Range::default(),
11873 },
11874 )),
11875 ..lsp::CompletionItem::default()
11876 };
11877 let items = iter::once(item_0.clone())
11878 .chain((11..51).map(|i| lsp::CompletionItem {
11879 label: format!("item_{}", i),
11880 insert_text: Some(format!("item_{}", i)),
11881 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11882 ..lsp::CompletionItem::default()
11883 }))
11884 .collect::<Vec<_>>();
11885
11886 let default_commit_characters = vec!["?".to_string()];
11887 let default_data = json!({ "default": "data"});
11888 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11889 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11890 let default_edit_range = lsp::Range {
11891 start: lsp::Position {
11892 line: 0,
11893 character: 5,
11894 },
11895 end: lsp::Position {
11896 line: 0,
11897 character: 5,
11898 },
11899 };
11900
11901 let item_0_out = lsp::CompletionItem {
11902 commit_characters: Some(default_commit_characters.clone()),
11903 insert_text_format: Some(default_insert_text_format),
11904 ..item_0
11905 };
11906 let items_out = iter::once(item_0_out)
11907 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11908 commit_characters: Some(default_commit_characters.clone()),
11909 data: Some(default_data.clone()),
11910 insert_text_mode: Some(default_insert_text_mode),
11911 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11912 range: default_edit_range,
11913 new_text: item.label.clone(),
11914 })),
11915 ..item.clone()
11916 }))
11917 .collect::<Vec<lsp::CompletionItem>>();
11918
11919 let mut cx = EditorLspTestContext::new_rust(
11920 lsp::ServerCapabilities {
11921 completion_provider: Some(lsp::CompletionOptions {
11922 trigger_characters: Some(vec![".".to_string()]),
11923 resolve_provider: Some(true),
11924 ..Default::default()
11925 }),
11926 ..Default::default()
11927 },
11928 cx,
11929 )
11930 .await;
11931
11932 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11933 cx.simulate_keystroke(".");
11934
11935 let completion_data = default_data.clone();
11936 let completion_characters = default_commit_characters.clone();
11937 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11938 let default_data = completion_data.clone();
11939 let default_commit_characters = completion_characters.clone();
11940 let items = items.clone();
11941 async move {
11942 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11943 items,
11944 item_defaults: Some(lsp::CompletionListItemDefaults {
11945 data: Some(default_data.clone()),
11946 commit_characters: Some(default_commit_characters.clone()),
11947 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11948 default_edit_range,
11949 )),
11950 insert_text_format: Some(default_insert_text_format),
11951 insert_text_mode: Some(default_insert_text_mode),
11952 }),
11953 ..lsp::CompletionList::default()
11954 })))
11955 }
11956 })
11957 .next()
11958 .await;
11959
11960 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11961 cx.lsp
11962 .server
11963 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11964 let closure_resolved_items = resolved_items.clone();
11965 move |item_to_resolve, _| {
11966 let closure_resolved_items = closure_resolved_items.clone();
11967 async move {
11968 closure_resolved_items.lock().push(item_to_resolve.clone());
11969 Ok(item_to_resolve)
11970 }
11971 }
11972 })
11973 .detach();
11974
11975 cx.condition(|editor, _| editor.context_menu_visible())
11976 .await;
11977 cx.run_until_parked();
11978 cx.update_editor(|editor, _, _| {
11979 let menu = editor.context_menu.borrow_mut();
11980 match menu.as_ref().expect("should have the completions menu") {
11981 CodeContextMenu::Completions(completions_menu) => {
11982 assert_eq!(
11983 completions_menu
11984 .entries
11985 .borrow()
11986 .iter()
11987 .map(|mat| mat.string.clone())
11988 .collect::<Vec<String>>(),
11989 items_out
11990 .iter()
11991 .map(|completion| completion.label.clone())
11992 .collect::<Vec<String>>()
11993 );
11994 }
11995 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11996 }
11997 });
11998 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11999 // with 4 from the end.
12000 assert_eq!(
12001 *resolved_items.lock(),
12002 [
12003 &items_out[0..16],
12004 &items_out[items_out.len() - 4..items_out.len()]
12005 ]
12006 .concat()
12007 .iter()
12008 .cloned()
12009 .collect::<Vec<lsp::CompletionItem>>()
12010 );
12011 resolved_items.lock().clear();
12012
12013 cx.update_editor(|editor, window, cx| {
12014 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12015 });
12016 cx.run_until_parked();
12017 // Completions that have already been resolved are skipped.
12018 assert_eq!(
12019 *resolved_items.lock(),
12020 items_out[items_out.len() - 16..items_out.len() - 4]
12021 .iter()
12022 .cloned()
12023 .collect::<Vec<lsp::CompletionItem>>()
12024 );
12025 resolved_items.lock().clear();
12026}
12027
12028#[gpui::test]
12029async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
12030 init_test(cx, |_| {});
12031
12032 let mut cx = EditorLspTestContext::new(
12033 Language::new(
12034 LanguageConfig {
12035 matcher: LanguageMatcher {
12036 path_suffixes: vec!["jsx".into()],
12037 ..Default::default()
12038 },
12039 overrides: [(
12040 "element".into(),
12041 LanguageConfigOverride {
12042 word_characters: Override::Set(['-'].into_iter().collect()),
12043 ..Default::default()
12044 },
12045 )]
12046 .into_iter()
12047 .collect(),
12048 ..Default::default()
12049 },
12050 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12051 )
12052 .with_override_query("(jsx_self_closing_element) @element")
12053 .unwrap(),
12054 lsp::ServerCapabilities {
12055 completion_provider: Some(lsp::CompletionOptions {
12056 trigger_characters: Some(vec![":".to_string()]),
12057 ..Default::default()
12058 }),
12059 ..Default::default()
12060 },
12061 cx,
12062 )
12063 .await;
12064
12065 cx.lsp
12066 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12067 Ok(Some(lsp::CompletionResponse::Array(vec![
12068 lsp::CompletionItem {
12069 label: "bg-blue".into(),
12070 ..Default::default()
12071 },
12072 lsp::CompletionItem {
12073 label: "bg-red".into(),
12074 ..Default::default()
12075 },
12076 lsp::CompletionItem {
12077 label: "bg-yellow".into(),
12078 ..Default::default()
12079 },
12080 ])))
12081 });
12082
12083 cx.set_state(r#"<p class="bgˇ" />"#);
12084
12085 // Trigger completion when typing a dash, because the dash is an extra
12086 // word character in the 'element' scope, which contains the cursor.
12087 cx.simulate_keystroke("-");
12088 cx.executor().run_until_parked();
12089 cx.update_editor(|editor, _, _| {
12090 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12091 {
12092 assert_eq!(
12093 completion_menu_entries(&menu),
12094 &["bg-red", "bg-blue", "bg-yellow"]
12095 );
12096 } else {
12097 panic!("expected completion menu to be open");
12098 }
12099 });
12100
12101 cx.simulate_keystroke("l");
12102 cx.executor().run_until_parked();
12103 cx.update_editor(|editor, _, _| {
12104 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12105 {
12106 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12107 } else {
12108 panic!("expected completion menu to be open");
12109 }
12110 });
12111
12112 // When filtering completions, consider the character after the '-' to
12113 // be the start of a subword.
12114 cx.set_state(r#"<p class="yelˇ" />"#);
12115 cx.simulate_keystroke("l");
12116 cx.executor().run_until_parked();
12117 cx.update_editor(|editor, _, _| {
12118 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12119 {
12120 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12121 } else {
12122 panic!("expected completion menu to be open");
12123 }
12124 });
12125}
12126
12127fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12128 let entries = menu.entries.borrow();
12129 entries.iter().map(|mat| mat.string.clone()).collect()
12130}
12131
12132#[gpui::test]
12133async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
12134 init_test(cx, |settings| {
12135 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12136 FormatterList(vec![Formatter::Prettier].into()),
12137 ))
12138 });
12139
12140 let fs = FakeFs::new(cx.executor());
12141 fs.insert_file(path!("/file.ts"), Default::default()).await;
12142
12143 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12144 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12145
12146 language_registry.add(Arc::new(Language::new(
12147 LanguageConfig {
12148 name: "TypeScript".into(),
12149 matcher: LanguageMatcher {
12150 path_suffixes: vec!["ts".to_string()],
12151 ..Default::default()
12152 },
12153 ..Default::default()
12154 },
12155 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12156 )));
12157 update_test_language_settings(cx, |settings| {
12158 settings.defaults.prettier = Some(PrettierSettings {
12159 allowed: true,
12160 ..PrettierSettings::default()
12161 });
12162 });
12163
12164 let test_plugin = "test_plugin";
12165 let _ = language_registry.register_fake_lsp(
12166 "TypeScript",
12167 FakeLspAdapter {
12168 prettier_plugins: vec![test_plugin],
12169 ..Default::default()
12170 },
12171 );
12172
12173 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12174 let buffer = project
12175 .update(cx, |project, cx| {
12176 project.open_local_buffer(path!("/file.ts"), cx)
12177 })
12178 .await
12179 .unwrap();
12180
12181 let buffer_text = "one\ntwo\nthree\n";
12182 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12183 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12184 editor.update_in(cx, |editor, window, cx| {
12185 editor.set_text(buffer_text, window, cx)
12186 });
12187
12188 editor
12189 .update_in(cx, |editor, window, cx| {
12190 editor.perform_format(
12191 project.clone(),
12192 FormatTrigger::Manual,
12193 FormatTarget::Buffers,
12194 window,
12195 cx,
12196 )
12197 })
12198 .unwrap()
12199 .await;
12200 assert_eq!(
12201 editor.update(cx, |editor, cx| editor.text(cx)),
12202 buffer_text.to_string() + prettier_format_suffix,
12203 "Test prettier formatting was not applied to the original buffer text",
12204 );
12205
12206 update_test_language_settings(cx, |settings| {
12207 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12208 });
12209 let format = editor.update_in(cx, |editor, window, cx| {
12210 editor.perform_format(
12211 project.clone(),
12212 FormatTrigger::Manual,
12213 FormatTarget::Buffers,
12214 window,
12215 cx,
12216 )
12217 });
12218 format.await.unwrap();
12219 assert_eq!(
12220 editor.update(cx, |editor, cx| editor.text(cx)),
12221 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12222 "Autoformatting (via test prettier) was not applied to the original buffer text",
12223 );
12224}
12225
12226#[gpui::test]
12227async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
12228 init_test(cx, |_| {});
12229 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12230 let base_text = indoc! {r#"
12231 struct Row;
12232 struct Row1;
12233 struct Row2;
12234
12235 struct Row4;
12236 struct Row5;
12237 struct Row6;
12238
12239 struct Row8;
12240 struct Row9;
12241 struct Row10;"#};
12242
12243 // When addition hunks are not adjacent to carets, no hunk revert is performed
12244 assert_hunk_revert(
12245 indoc! {r#"struct Row;
12246 struct Row1;
12247 struct Row1.1;
12248 struct Row1.2;
12249 struct Row2;ˇ
12250
12251 struct Row4;
12252 struct Row5;
12253 struct Row6;
12254
12255 struct Row8;
12256 ˇstruct Row9;
12257 struct Row9.1;
12258 struct Row9.2;
12259 struct Row9.3;
12260 struct Row10;"#},
12261 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12262 indoc! {r#"struct Row;
12263 struct Row1;
12264 struct Row1.1;
12265 struct Row1.2;
12266 struct Row2;ˇ
12267
12268 struct Row4;
12269 struct Row5;
12270 struct Row6;
12271
12272 struct Row8;
12273 ˇstruct Row9;
12274 struct Row9.1;
12275 struct Row9.2;
12276 struct Row9.3;
12277 struct Row10;"#},
12278 base_text,
12279 &mut cx,
12280 );
12281 // Same for selections
12282 assert_hunk_revert(
12283 indoc! {r#"struct Row;
12284 struct Row1;
12285 struct Row2;
12286 struct Row2.1;
12287 struct Row2.2;
12288 «ˇ
12289 struct Row4;
12290 struct» Row5;
12291 «struct Row6;
12292 ˇ»
12293 struct Row9.1;
12294 struct Row9.2;
12295 struct Row9.3;
12296 struct Row8;
12297 struct Row9;
12298 struct Row10;"#},
12299 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12300 indoc! {r#"struct Row;
12301 struct Row1;
12302 struct Row2;
12303 struct Row2.1;
12304 struct Row2.2;
12305 «ˇ
12306 struct Row4;
12307 struct» Row5;
12308 «struct Row6;
12309 ˇ»
12310 struct Row9.1;
12311 struct Row9.2;
12312 struct Row9.3;
12313 struct Row8;
12314 struct Row9;
12315 struct Row10;"#},
12316 base_text,
12317 &mut cx,
12318 );
12319
12320 // When carets and selections intersect the addition hunks, those are reverted.
12321 // Adjacent carets got merged.
12322 assert_hunk_revert(
12323 indoc! {r#"struct Row;
12324 ˇ// something on the top
12325 struct Row1;
12326 struct Row2;
12327 struct Roˇw3.1;
12328 struct Row2.2;
12329 struct Row2.3;ˇ
12330
12331 struct Row4;
12332 struct ˇRow5.1;
12333 struct Row5.2;
12334 struct «Rowˇ»5.3;
12335 struct Row5;
12336 struct Row6;
12337 ˇ
12338 struct Row9.1;
12339 struct «Rowˇ»9.2;
12340 struct «ˇRow»9.3;
12341 struct Row8;
12342 struct Row9;
12343 «ˇ// something on bottom»
12344 struct Row10;"#},
12345 vec![
12346 DiffHunkStatus::added_none(),
12347 DiffHunkStatus::added_none(),
12348 DiffHunkStatus::added_none(),
12349 DiffHunkStatus::added_none(),
12350 DiffHunkStatus::added_none(),
12351 ],
12352 indoc! {r#"struct Row;
12353 ˇstruct Row1;
12354 struct Row2;
12355 ˇ
12356 struct Row4;
12357 ˇstruct Row5;
12358 struct Row6;
12359 ˇ
12360 ˇstruct Row8;
12361 struct Row9;
12362 ˇstruct Row10;"#},
12363 base_text,
12364 &mut cx,
12365 );
12366}
12367
12368#[gpui::test]
12369async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
12370 init_test(cx, |_| {});
12371 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12372 let base_text = indoc! {r#"
12373 struct Row;
12374 struct Row1;
12375 struct Row2;
12376
12377 struct Row4;
12378 struct Row5;
12379 struct Row6;
12380
12381 struct Row8;
12382 struct Row9;
12383 struct Row10;"#};
12384
12385 // Modification hunks behave the same as the addition ones.
12386 assert_hunk_revert(
12387 indoc! {r#"struct Row;
12388 struct Row1;
12389 struct Row33;
12390 ˇ
12391 struct Row4;
12392 struct Row5;
12393 struct Row6;
12394 ˇ
12395 struct Row99;
12396 struct Row9;
12397 struct Row10;"#},
12398 vec![
12399 DiffHunkStatus::modified_none(),
12400 DiffHunkStatus::modified_none(),
12401 ],
12402 indoc! {r#"struct Row;
12403 struct Row1;
12404 struct Row33;
12405 ˇ
12406 struct Row4;
12407 struct Row5;
12408 struct Row6;
12409 ˇ
12410 struct Row99;
12411 struct Row9;
12412 struct Row10;"#},
12413 base_text,
12414 &mut cx,
12415 );
12416 assert_hunk_revert(
12417 indoc! {r#"struct Row;
12418 struct Row1;
12419 struct Row33;
12420 «ˇ
12421 struct Row4;
12422 struct» Row5;
12423 «struct Row6;
12424 ˇ»
12425 struct Row99;
12426 struct Row9;
12427 struct Row10;"#},
12428 vec![
12429 DiffHunkStatus::modified_none(),
12430 DiffHunkStatus::modified_none(),
12431 ],
12432 indoc! {r#"struct Row;
12433 struct Row1;
12434 struct Row33;
12435 «ˇ
12436 struct Row4;
12437 struct» Row5;
12438 «struct Row6;
12439 ˇ»
12440 struct Row99;
12441 struct Row9;
12442 struct Row10;"#},
12443 base_text,
12444 &mut cx,
12445 );
12446
12447 assert_hunk_revert(
12448 indoc! {r#"ˇstruct Row1.1;
12449 struct Row1;
12450 «ˇstr»uct Row22;
12451
12452 struct ˇRow44;
12453 struct Row5;
12454 struct «Rˇ»ow66;ˇ
12455
12456 «struˇ»ct Row88;
12457 struct Row9;
12458 struct Row1011;ˇ"#},
12459 vec![
12460 DiffHunkStatus::modified_none(),
12461 DiffHunkStatus::modified_none(),
12462 DiffHunkStatus::modified_none(),
12463 DiffHunkStatus::modified_none(),
12464 DiffHunkStatus::modified_none(),
12465 DiffHunkStatus::modified_none(),
12466 ],
12467 indoc! {r#"struct Row;
12468 ˇstruct Row1;
12469 struct Row2;
12470 ˇ
12471 struct Row4;
12472 ˇstruct Row5;
12473 struct Row6;
12474 ˇ
12475 struct Row8;
12476 ˇstruct Row9;
12477 struct Row10;ˇ"#},
12478 base_text,
12479 &mut cx,
12480 );
12481}
12482
12483#[gpui::test]
12484async fn test_deleting_over_diff_hunk(cx: &mut gpui::TestAppContext) {
12485 init_test(cx, |_| {});
12486 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12487 let base_text = indoc! {r#"
12488 one
12489
12490 two
12491 three
12492 "#};
12493
12494 cx.set_diff_base(base_text);
12495 cx.set_state("\nˇ\n");
12496 cx.executor().run_until_parked();
12497 cx.update_editor(|editor, _window, cx| {
12498 editor.expand_selected_diff_hunks(cx);
12499 });
12500 cx.executor().run_until_parked();
12501 cx.update_editor(|editor, window, cx| {
12502 editor.backspace(&Default::default(), window, cx);
12503 });
12504 cx.run_until_parked();
12505 cx.assert_state_with_diff(
12506 indoc! {r#"
12507
12508 - two
12509 - threeˇ
12510 +
12511 "#}
12512 .to_string(),
12513 );
12514}
12515
12516#[gpui::test]
12517async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
12518 init_test(cx, |_| {});
12519 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12520 let base_text = indoc! {r#"struct Row;
12521struct Row1;
12522struct Row2;
12523
12524struct Row4;
12525struct Row5;
12526struct Row6;
12527
12528struct Row8;
12529struct Row9;
12530struct Row10;"#};
12531
12532 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12533 assert_hunk_revert(
12534 indoc! {r#"struct Row;
12535 struct Row2;
12536
12537 ˇstruct Row4;
12538 struct Row5;
12539 struct Row6;
12540 ˇ
12541 struct Row8;
12542 struct Row10;"#},
12543 vec![
12544 DiffHunkStatus::deleted_none(),
12545 DiffHunkStatus::deleted_none(),
12546 ],
12547 indoc! {r#"struct Row;
12548 struct Row2;
12549
12550 ˇstruct Row4;
12551 struct Row5;
12552 struct Row6;
12553 ˇ
12554 struct Row8;
12555 struct Row10;"#},
12556 base_text,
12557 &mut cx,
12558 );
12559 assert_hunk_revert(
12560 indoc! {r#"struct Row;
12561 struct Row2;
12562
12563 «ˇstruct Row4;
12564 struct» Row5;
12565 «struct Row6;
12566 ˇ»
12567 struct Row8;
12568 struct Row10;"#},
12569 vec![
12570 DiffHunkStatus::deleted_none(),
12571 DiffHunkStatus::deleted_none(),
12572 ],
12573 indoc! {r#"struct Row;
12574 struct Row2;
12575
12576 «ˇstruct Row4;
12577 struct» Row5;
12578 «struct Row6;
12579 ˇ»
12580 struct Row8;
12581 struct Row10;"#},
12582 base_text,
12583 &mut cx,
12584 );
12585
12586 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12587 assert_hunk_revert(
12588 indoc! {r#"struct Row;
12589 ˇstruct Row2;
12590
12591 struct Row4;
12592 struct Row5;
12593 struct Row6;
12594
12595 struct Row8;ˇ
12596 struct Row10;"#},
12597 vec![
12598 DiffHunkStatus::deleted_none(),
12599 DiffHunkStatus::deleted_none(),
12600 ],
12601 indoc! {r#"struct Row;
12602 struct Row1;
12603 ˇstruct Row2;
12604
12605 struct Row4;
12606 struct Row5;
12607 struct Row6;
12608
12609 struct Row8;ˇ
12610 struct Row9;
12611 struct Row10;"#},
12612 base_text,
12613 &mut cx,
12614 );
12615 assert_hunk_revert(
12616 indoc! {r#"struct Row;
12617 struct Row2«ˇ;
12618 struct Row4;
12619 struct» Row5;
12620 «struct Row6;
12621
12622 struct Row8;ˇ»
12623 struct Row10;"#},
12624 vec![
12625 DiffHunkStatus::deleted_none(),
12626 DiffHunkStatus::deleted_none(),
12627 DiffHunkStatus::deleted_none(),
12628 ],
12629 indoc! {r#"struct Row;
12630 struct Row1;
12631 struct Row2«ˇ;
12632
12633 struct Row4;
12634 struct» Row5;
12635 «struct Row6;
12636
12637 struct Row8;ˇ»
12638 struct Row9;
12639 struct Row10;"#},
12640 base_text,
12641 &mut cx,
12642 );
12643}
12644
12645#[gpui::test]
12646async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
12647 init_test(cx, |_| {});
12648
12649 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12650 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12651 let base_text_3 =
12652 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12653
12654 let text_1 = edit_first_char_of_every_line(base_text_1);
12655 let text_2 = edit_first_char_of_every_line(base_text_2);
12656 let text_3 = edit_first_char_of_every_line(base_text_3);
12657
12658 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12659 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12660 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12661
12662 let multibuffer = cx.new(|cx| {
12663 let mut multibuffer = MultiBuffer::new(ReadWrite);
12664 multibuffer.push_excerpts(
12665 buffer_1.clone(),
12666 [
12667 ExcerptRange {
12668 context: Point::new(0, 0)..Point::new(3, 0),
12669 primary: None,
12670 },
12671 ExcerptRange {
12672 context: Point::new(5, 0)..Point::new(7, 0),
12673 primary: None,
12674 },
12675 ExcerptRange {
12676 context: Point::new(9, 0)..Point::new(10, 4),
12677 primary: None,
12678 },
12679 ],
12680 cx,
12681 );
12682 multibuffer.push_excerpts(
12683 buffer_2.clone(),
12684 [
12685 ExcerptRange {
12686 context: Point::new(0, 0)..Point::new(3, 0),
12687 primary: None,
12688 },
12689 ExcerptRange {
12690 context: Point::new(5, 0)..Point::new(7, 0),
12691 primary: None,
12692 },
12693 ExcerptRange {
12694 context: Point::new(9, 0)..Point::new(10, 4),
12695 primary: None,
12696 },
12697 ],
12698 cx,
12699 );
12700 multibuffer.push_excerpts(
12701 buffer_3.clone(),
12702 [
12703 ExcerptRange {
12704 context: Point::new(0, 0)..Point::new(3, 0),
12705 primary: None,
12706 },
12707 ExcerptRange {
12708 context: Point::new(5, 0)..Point::new(7, 0),
12709 primary: None,
12710 },
12711 ExcerptRange {
12712 context: Point::new(9, 0)..Point::new(10, 4),
12713 primary: None,
12714 },
12715 ],
12716 cx,
12717 );
12718 multibuffer
12719 });
12720
12721 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12722 editor.update_in(cx, |editor, _window, cx| {
12723 for (buffer, diff_base) in [
12724 (buffer_1.clone(), base_text_1),
12725 (buffer_2.clone(), base_text_2),
12726 (buffer_3.clone(), base_text_3),
12727 ] {
12728 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
12729 editor
12730 .buffer
12731 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
12732 }
12733 });
12734 cx.executor().run_until_parked();
12735
12736 editor.update_in(cx, |editor, window, cx| {
12737 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}");
12738 editor.select_all(&SelectAll, window, cx);
12739 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
12740 });
12741 cx.executor().run_until_parked();
12742
12743 // When all ranges are selected, all buffer hunks are reverted.
12744 editor.update(cx, |editor, cx| {
12745 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");
12746 });
12747 buffer_1.update(cx, |buffer, _| {
12748 assert_eq!(buffer.text(), base_text_1);
12749 });
12750 buffer_2.update(cx, |buffer, _| {
12751 assert_eq!(buffer.text(), base_text_2);
12752 });
12753 buffer_3.update(cx, |buffer, _| {
12754 assert_eq!(buffer.text(), base_text_3);
12755 });
12756
12757 editor.update_in(cx, |editor, window, cx| {
12758 editor.undo(&Default::default(), window, cx);
12759 });
12760
12761 editor.update_in(cx, |editor, window, cx| {
12762 editor.change_selections(None, window, cx, |s| {
12763 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12764 });
12765 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
12766 });
12767
12768 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12769 // but not affect buffer_2 and its related excerpts.
12770 editor.update(cx, |editor, cx| {
12771 assert_eq!(
12772 editor.text(cx),
12773 "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}"
12774 );
12775 });
12776 buffer_1.update(cx, |buffer, _| {
12777 assert_eq!(buffer.text(), base_text_1);
12778 });
12779 buffer_2.update(cx, |buffer, _| {
12780 assert_eq!(
12781 buffer.text(),
12782 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12783 );
12784 });
12785 buffer_3.update(cx, |buffer, _| {
12786 assert_eq!(
12787 buffer.text(),
12788 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12789 );
12790 });
12791
12792 fn edit_first_char_of_every_line(text: &str) -> String {
12793 text.split('\n')
12794 .map(|line| format!("X{}", &line[1..]))
12795 .collect::<Vec<_>>()
12796 .join("\n")
12797 }
12798}
12799
12800#[gpui::test]
12801async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
12802 init_test(cx, |_| {});
12803
12804 let cols = 4;
12805 let rows = 10;
12806 let sample_text_1 = sample_text(rows, cols, 'a');
12807 assert_eq!(
12808 sample_text_1,
12809 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12810 );
12811 let sample_text_2 = sample_text(rows, cols, 'l');
12812 assert_eq!(
12813 sample_text_2,
12814 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12815 );
12816 let sample_text_3 = sample_text(rows, cols, 'v');
12817 assert_eq!(
12818 sample_text_3,
12819 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12820 );
12821
12822 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12823 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12824 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12825
12826 let multi_buffer = cx.new(|cx| {
12827 let mut multibuffer = MultiBuffer::new(ReadWrite);
12828 multibuffer.push_excerpts(
12829 buffer_1.clone(),
12830 [
12831 ExcerptRange {
12832 context: Point::new(0, 0)..Point::new(3, 0),
12833 primary: None,
12834 },
12835 ExcerptRange {
12836 context: Point::new(5, 0)..Point::new(7, 0),
12837 primary: None,
12838 },
12839 ExcerptRange {
12840 context: Point::new(9, 0)..Point::new(10, 4),
12841 primary: None,
12842 },
12843 ],
12844 cx,
12845 );
12846 multibuffer.push_excerpts(
12847 buffer_2.clone(),
12848 [
12849 ExcerptRange {
12850 context: Point::new(0, 0)..Point::new(3, 0),
12851 primary: None,
12852 },
12853 ExcerptRange {
12854 context: Point::new(5, 0)..Point::new(7, 0),
12855 primary: None,
12856 },
12857 ExcerptRange {
12858 context: Point::new(9, 0)..Point::new(10, 4),
12859 primary: None,
12860 },
12861 ],
12862 cx,
12863 );
12864 multibuffer.push_excerpts(
12865 buffer_3.clone(),
12866 [
12867 ExcerptRange {
12868 context: Point::new(0, 0)..Point::new(3, 0),
12869 primary: None,
12870 },
12871 ExcerptRange {
12872 context: Point::new(5, 0)..Point::new(7, 0),
12873 primary: None,
12874 },
12875 ExcerptRange {
12876 context: Point::new(9, 0)..Point::new(10, 4),
12877 primary: None,
12878 },
12879 ],
12880 cx,
12881 );
12882 multibuffer
12883 });
12884
12885 let fs = FakeFs::new(cx.executor());
12886 fs.insert_tree(
12887 "/a",
12888 json!({
12889 "main.rs": sample_text_1,
12890 "other.rs": sample_text_2,
12891 "lib.rs": sample_text_3,
12892 }),
12893 )
12894 .await;
12895 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12896 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12897 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12898 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12899 Editor::new(
12900 EditorMode::Full,
12901 multi_buffer,
12902 Some(project.clone()),
12903 true,
12904 window,
12905 cx,
12906 )
12907 });
12908 let multibuffer_item_id = workspace
12909 .update(cx, |workspace, window, cx| {
12910 assert!(
12911 workspace.active_item(cx).is_none(),
12912 "active item should be None before the first item is added"
12913 );
12914 workspace.add_item_to_active_pane(
12915 Box::new(multi_buffer_editor.clone()),
12916 None,
12917 true,
12918 window,
12919 cx,
12920 );
12921 let active_item = workspace
12922 .active_item(cx)
12923 .expect("should have an active item after adding the multi buffer");
12924 assert!(
12925 !active_item.is_singleton(cx),
12926 "A multi buffer was expected to active after adding"
12927 );
12928 active_item.item_id()
12929 })
12930 .unwrap();
12931 cx.executor().run_until_parked();
12932
12933 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12934 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12935 s.select_ranges(Some(1..2))
12936 });
12937 editor.open_excerpts(&OpenExcerpts, window, cx);
12938 });
12939 cx.executor().run_until_parked();
12940 let first_item_id = workspace
12941 .update(cx, |workspace, window, cx| {
12942 let active_item = workspace
12943 .active_item(cx)
12944 .expect("should have an active item after navigating into the 1st buffer");
12945 let first_item_id = active_item.item_id();
12946 assert_ne!(
12947 first_item_id, multibuffer_item_id,
12948 "Should navigate into the 1st buffer and activate it"
12949 );
12950 assert!(
12951 active_item.is_singleton(cx),
12952 "New active item should be a singleton buffer"
12953 );
12954 assert_eq!(
12955 active_item
12956 .act_as::<Editor>(cx)
12957 .expect("should have navigated into an editor for the 1st buffer")
12958 .read(cx)
12959 .text(cx),
12960 sample_text_1
12961 );
12962
12963 workspace
12964 .go_back(workspace.active_pane().downgrade(), window, cx)
12965 .detach_and_log_err(cx);
12966
12967 first_item_id
12968 })
12969 .unwrap();
12970 cx.executor().run_until_parked();
12971 workspace
12972 .update(cx, |workspace, _, cx| {
12973 let active_item = workspace
12974 .active_item(cx)
12975 .expect("should have an active item after navigating back");
12976 assert_eq!(
12977 active_item.item_id(),
12978 multibuffer_item_id,
12979 "Should navigate back to the multi buffer"
12980 );
12981 assert!(!active_item.is_singleton(cx));
12982 })
12983 .unwrap();
12984
12985 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12986 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12987 s.select_ranges(Some(39..40))
12988 });
12989 editor.open_excerpts(&OpenExcerpts, window, cx);
12990 });
12991 cx.executor().run_until_parked();
12992 let second_item_id = workspace
12993 .update(cx, |workspace, window, cx| {
12994 let active_item = workspace
12995 .active_item(cx)
12996 .expect("should have an active item after navigating into the 2nd buffer");
12997 let second_item_id = active_item.item_id();
12998 assert_ne!(
12999 second_item_id, multibuffer_item_id,
13000 "Should navigate away from the multibuffer"
13001 );
13002 assert_ne!(
13003 second_item_id, first_item_id,
13004 "Should navigate into the 2nd buffer and activate it"
13005 );
13006 assert!(
13007 active_item.is_singleton(cx),
13008 "New active item should be a singleton buffer"
13009 );
13010 assert_eq!(
13011 active_item
13012 .act_as::<Editor>(cx)
13013 .expect("should have navigated into an editor")
13014 .read(cx)
13015 .text(cx),
13016 sample_text_2
13017 );
13018
13019 workspace
13020 .go_back(workspace.active_pane().downgrade(), window, cx)
13021 .detach_and_log_err(cx);
13022
13023 second_item_id
13024 })
13025 .unwrap();
13026 cx.executor().run_until_parked();
13027 workspace
13028 .update(cx, |workspace, _, cx| {
13029 let active_item = workspace
13030 .active_item(cx)
13031 .expect("should have an active item after navigating back from the 2nd buffer");
13032 assert_eq!(
13033 active_item.item_id(),
13034 multibuffer_item_id,
13035 "Should navigate back from the 2nd buffer to the multi buffer"
13036 );
13037 assert!(!active_item.is_singleton(cx));
13038 })
13039 .unwrap();
13040
13041 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13042 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13043 s.select_ranges(Some(70..70))
13044 });
13045 editor.open_excerpts(&OpenExcerpts, window, cx);
13046 });
13047 cx.executor().run_until_parked();
13048 workspace
13049 .update(cx, |workspace, window, cx| {
13050 let active_item = workspace
13051 .active_item(cx)
13052 .expect("should have an active item after navigating into the 3rd buffer");
13053 let third_item_id = active_item.item_id();
13054 assert_ne!(
13055 third_item_id, multibuffer_item_id,
13056 "Should navigate into the 3rd buffer and activate it"
13057 );
13058 assert_ne!(third_item_id, first_item_id);
13059 assert_ne!(third_item_id, second_item_id);
13060 assert!(
13061 active_item.is_singleton(cx),
13062 "New active item should be a singleton buffer"
13063 );
13064 assert_eq!(
13065 active_item
13066 .act_as::<Editor>(cx)
13067 .expect("should have navigated into an editor")
13068 .read(cx)
13069 .text(cx),
13070 sample_text_3
13071 );
13072
13073 workspace
13074 .go_back(workspace.active_pane().downgrade(), window, cx)
13075 .detach_and_log_err(cx);
13076 })
13077 .unwrap();
13078 cx.executor().run_until_parked();
13079 workspace
13080 .update(cx, |workspace, _, cx| {
13081 let active_item = workspace
13082 .active_item(cx)
13083 .expect("should have an active item after navigating back from the 3rd buffer");
13084 assert_eq!(
13085 active_item.item_id(),
13086 multibuffer_item_id,
13087 "Should navigate back from the 3rd buffer to the multi buffer"
13088 );
13089 assert!(!active_item.is_singleton(cx));
13090 })
13091 .unwrap();
13092}
13093
13094#[gpui::test]
13095async fn test_toggle_selected_diff_hunks(
13096 executor: BackgroundExecutor,
13097 cx: &mut gpui::TestAppContext,
13098) {
13099 init_test(cx, |_| {});
13100
13101 let mut cx = EditorTestContext::new(cx).await;
13102
13103 let diff_base = r#"
13104 use some::mod;
13105
13106 const A: u32 = 42;
13107
13108 fn main() {
13109 println!("hello");
13110
13111 println!("world");
13112 }
13113 "#
13114 .unindent();
13115
13116 cx.set_state(
13117 &r#"
13118 use some::modified;
13119
13120 ˇ
13121 fn main() {
13122 println!("hello there");
13123
13124 println!("around the");
13125 println!("world");
13126 }
13127 "#
13128 .unindent(),
13129 );
13130
13131 cx.set_diff_base(&diff_base);
13132 executor.run_until_parked();
13133
13134 cx.update_editor(|editor, window, cx| {
13135 editor.go_to_next_hunk(&GoToHunk, window, cx);
13136 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13137 });
13138 executor.run_until_parked();
13139 cx.assert_state_with_diff(
13140 r#"
13141 use some::modified;
13142
13143
13144 fn main() {
13145 - println!("hello");
13146 + ˇ println!("hello there");
13147
13148 println!("around the");
13149 println!("world");
13150 }
13151 "#
13152 .unindent(),
13153 );
13154
13155 cx.update_editor(|editor, window, cx| {
13156 for _ in 0..2 {
13157 editor.go_to_next_hunk(&GoToHunk, window, cx);
13158 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13159 }
13160 });
13161 executor.run_until_parked();
13162 cx.assert_state_with_diff(
13163 r#"
13164 - use some::mod;
13165 + ˇuse some::modified;
13166
13167
13168 fn main() {
13169 - println!("hello");
13170 + println!("hello there");
13171
13172 + println!("around the");
13173 println!("world");
13174 }
13175 "#
13176 .unindent(),
13177 );
13178
13179 cx.update_editor(|editor, window, cx| {
13180 editor.go_to_next_hunk(&GoToHunk, window, cx);
13181 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13182 });
13183 executor.run_until_parked();
13184 cx.assert_state_with_diff(
13185 r#"
13186 - use some::mod;
13187 + use some::modified;
13188
13189 - const A: u32 = 42;
13190 ˇ
13191 fn main() {
13192 - println!("hello");
13193 + println!("hello there");
13194
13195 + println!("around the");
13196 println!("world");
13197 }
13198 "#
13199 .unindent(),
13200 );
13201
13202 cx.update_editor(|editor, window, cx| {
13203 editor.cancel(&Cancel, window, cx);
13204 });
13205
13206 cx.assert_state_with_diff(
13207 r#"
13208 use some::modified;
13209
13210 ˇ
13211 fn main() {
13212 println!("hello there");
13213
13214 println!("around the");
13215 println!("world");
13216 }
13217 "#
13218 .unindent(),
13219 );
13220}
13221
13222#[gpui::test]
13223async fn test_diff_base_change_with_expanded_diff_hunks(
13224 executor: BackgroundExecutor,
13225 cx: &mut gpui::TestAppContext,
13226) {
13227 init_test(cx, |_| {});
13228
13229 let mut cx = EditorTestContext::new(cx).await;
13230
13231 let diff_base = r#"
13232 use some::mod1;
13233 use some::mod2;
13234
13235 const A: u32 = 42;
13236 const B: u32 = 42;
13237 const C: u32 = 42;
13238
13239 fn main() {
13240 println!("hello");
13241
13242 println!("world");
13243 }
13244 "#
13245 .unindent();
13246
13247 cx.set_state(
13248 &r#"
13249 use some::mod2;
13250
13251 const A: u32 = 42;
13252 const C: u32 = 42;
13253
13254 fn main(ˇ) {
13255 //println!("hello");
13256
13257 println!("world");
13258 //
13259 //
13260 }
13261 "#
13262 .unindent(),
13263 );
13264
13265 cx.set_diff_base(&diff_base);
13266 executor.run_until_parked();
13267
13268 cx.update_editor(|editor, window, cx| {
13269 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13270 });
13271 executor.run_until_parked();
13272 cx.assert_state_with_diff(
13273 r#"
13274 - use some::mod1;
13275 use some::mod2;
13276
13277 const A: u32 = 42;
13278 - const B: u32 = 42;
13279 const C: u32 = 42;
13280
13281 fn main(ˇ) {
13282 - println!("hello");
13283 + //println!("hello");
13284
13285 println!("world");
13286 + //
13287 + //
13288 }
13289 "#
13290 .unindent(),
13291 );
13292
13293 cx.set_diff_base("new diff base!");
13294 executor.run_until_parked();
13295 cx.assert_state_with_diff(
13296 r#"
13297 - new diff base!
13298 + use some::mod2;
13299 +
13300 + const A: u32 = 42;
13301 + const C: u32 = 42;
13302 +
13303 + fn main(ˇ) {
13304 + //println!("hello");
13305 +
13306 + println!("world");
13307 + //
13308 + //
13309 + }
13310 "#
13311 .unindent(),
13312 );
13313}
13314
13315#[gpui::test]
13316async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
13317 init_test(cx, |_| {});
13318
13319 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13320 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13321 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13322 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13323 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13324 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13325
13326 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13327 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13328 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13329
13330 let multi_buffer = cx.new(|cx| {
13331 let mut multibuffer = MultiBuffer::new(ReadWrite);
13332 multibuffer.push_excerpts(
13333 buffer_1.clone(),
13334 [
13335 ExcerptRange {
13336 context: Point::new(0, 0)..Point::new(3, 0),
13337 primary: None,
13338 },
13339 ExcerptRange {
13340 context: Point::new(5, 0)..Point::new(7, 0),
13341 primary: None,
13342 },
13343 ExcerptRange {
13344 context: Point::new(9, 0)..Point::new(10, 3),
13345 primary: None,
13346 },
13347 ],
13348 cx,
13349 );
13350 multibuffer.push_excerpts(
13351 buffer_2.clone(),
13352 [
13353 ExcerptRange {
13354 context: Point::new(0, 0)..Point::new(3, 0),
13355 primary: None,
13356 },
13357 ExcerptRange {
13358 context: Point::new(5, 0)..Point::new(7, 0),
13359 primary: None,
13360 },
13361 ExcerptRange {
13362 context: Point::new(9, 0)..Point::new(10, 3),
13363 primary: None,
13364 },
13365 ],
13366 cx,
13367 );
13368 multibuffer.push_excerpts(
13369 buffer_3.clone(),
13370 [
13371 ExcerptRange {
13372 context: Point::new(0, 0)..Point::new(3, 0),
13373 primary: None,
13374 },
13375 ExcerptRange {
13376 context: Point::new(5, 0)..Point::new(7, 0),
13377 primary: None,
13378 },
13379 ExcerptRange {
13380 context: Point::new(9, 0)..Point::new(10, 3),
13381 primary: None,
13382 },
13383 ],
13384 cx,
13385 );
13386 multibuffer
13387 });
13388
13389 let editor = cx.add_window(|window, cx| {
13390 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13391 });
13392 editor
13393 .update(cx, |editor, _window, cx| {
13394 for (buffer, diff_base) in [
13395 (buffer_1.clone(), file_1_old),
13396 (buffer_2.clone(), file_2_old),
13397 (buffer_3.clone(), file_3_old),
13398 ] {
13399 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13400 editor
13401 .buffer
13402 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13403 }
13404 })
13405 .unwrap();
13406
13407 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13408 cx.run_until_parked();
13409
13410 cx.assert_editor_state(
13411 &"
13412 ˇaaa
13413 ccc
13414 ddd
13415
13416 ggg
13417 hhh
13418
13419
13420 lll
13421 mmm
13422 NNN
13423
13424 qqq
13425 rrr
13426
13427 uuu
13428 111
13429 222
13430 333
13431
13432 666
13433 777
13434
13435 000
13436 !!!"
13437 .unindent(),
13438 );
13439
13440 cx.update_editor(|editor, window, cx| {
13441 editor.select_all(&SelectAll, window, cx);
13442 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13443 });
13444 cx.executor().run_until_parked();
13445
13446 cx.assert_state_with_diff(
13447 "
13448 «aaa
13449 - bbb
13450 ccc
13451 ddd
13452
13453 ggg
13454 hhh
13455
13456
13457 lll
13458 mmm
13459 - nnn
13460 + NNN
13461
13462 qqq
13463 rrr
13464
13465 uuu
13466 111
13467 222
13468 333
13469
13470 + 666
13471 777
13472
13473 000
13474 !!!ˇ»"
13475 .unindent(),
13476 );
13477}
13478
13479#[gpui::test]
13480async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
13481 init_test(cx, |_| {});
13482
13483 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13484 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13485
13486 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13487 let multi_buffer = cx.new(|cx| {
13488 let mut multibuffer = MultiBuffer::new(ReadWrite);
13489 multibuffer.push_excerpts(
13490 buffer.clone(),
13491 [
13492 ExcerptRange {
13493 context: Point::new(0, 0)..Point::new(2, 0),
13494 primary: None,
13495 },
13496 ExcerptRange {
13497 context: Point::new(4, 0)..Point::new(7, 0),
13498 primary: None,
13499 },
13500 ExcerptRange {
13501 context: Point::new(9, 0)..Point::new(10, 0),
13502 primary: None,
13503 },
13504 ],
13505 cx,
13506 );
13507 multibuffer
13508 });
13509
13510 let editor = cx.add_window(|window, cx| {
13511 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13512 });
13513 editor
13514 .update(cx, |editor, _window, cx| {
13515 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13516 editor
13517 .buffer
13518 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13519 })
13520 .unwrap();
13521
13522 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13523 cx.run_until_parked();
13524
13525 cx.update_editor(|editor, window, cx| {
13526 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13527 });
13528 cx.executor().run_until_parked();
13529
13530 // When the start of a hunk coincides with the start of its excerpt,
13531 // the hunk is expanded. When the start of a a hunk is earlier than
13532 // the start of its excerpt, the hunk is not expanded.
13533 cx.assert_state_with_diff(
13534 "
13535 ˇaaa
13536 - bbb
13537 + BBB
13538
13539 - ddd
13540 - eee
13541 + DDD
13542 + EEE
13543 fff
13544
13545 iii
13546 "
13547 .unindent(),
13548 );
13549}
13550
13551#[gpui::test]
13552async fn test_edits_around_expanded_insertion_hunks(
13553 executor: BackgroundExecutor,
13554 cx: &mut gpui::TestAppContext,
13555) {
13556 init_test(cx, |_| {});
13557
13558 let mut cx = EditorTestContext::new(cx).await;
13559
13560 let diff_base = r#"
13561 use some::mod1;
13562 use some::mod2;
13563
13564 const A: u32 = 42;
13565
13566 fn main() {
13567 println!("hello");
13568
13569 println!("world");
13570 }
13571 "#
13572 .unindent();
13573 executor.run_until_parked();
13574 cx.set_state(
13575 &r#"
13576 use some::mod1;
13577 use some::mod2;
13578
13579 const A: u32 = 42;
13580 const B: u32 = 42;
13581 const C: u32 = 42;
13582 ˇ
13583
13584 fn main() {
13585 println!("hello");
13586
13587 println!("world");
13588 }
13589 "#
13590 .unindent(),
13591 );
13592
13593 cx.set_diff_base(&diff_base);
13594 executor.run_until_parked();
13595
13596 cx.update_editor(|editor, window, cx| {
13597 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13598 });
13599 executor.run_until_parked();
13600
13601 cx.assert_state_with_diff(
13602 r#"
13603 use some::mod1;
13604 use some::mod2;
13605
13606 const A: u32 = 42;
13607 + const B: u32 = 42;
13608 + const C: u32 = 42;
13609 + ˇ
13610
13611 fn main() {
13612 println!("hello");
13613
13614 println!("world");
13615 }
13616 "#
13617 .unindent(),
13618 );
13619
13620 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13621 executor.run_until_parked();
13622
13623 cx.assert_state_with_diff(
13624 r#"
13625 use some::mod1;
13626 use some::mod2;
13627
13628 const A: u32 = 42;
13629 + const B: u32 = 42;
13630 + const C: u32 = 42;
13631 + const D: u32 = 42;
13632 + ˇ
13633
13634 fn main() {
13635 println!("hello");
13636
13637 println!("world");
13638 }
13639 "#
13640 .unindent(),
13641 );
13642
13643 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13644 executor.run_until_parked();
13645
13646 cx.assert_state_with_diff(
13647 r#"
13648 use some::mod1;
13649 use some::mod2;
13650
13651 const A: u32 = 42;
13652 + const B: u32 = 42;
13653 + const C: u32 = 42;
13654 + const D: u32 = 42;
13655 + const E: u32 = 42;
13656 + ˇ
13657
13658 fn main() {
13659 println!("hello");
13660
13661 println!("world");
13662 }
13663 "#
13664 .unindent(),
13665 );
13666
13667 cx.update_editor(|editor, window, cx| {
13668 editor.delete_line(&DeleteLine, window, cx);
13669 });
13670 executor.run_until_parked();
13671
13672 cx.assert_state_with_diff(
13673 r#"
13674 use some::mod1;
13675 use some::mod2;
13676
13677 const A: u32 = 42;
13678 + const B: u32 = 42;
13679 + const C: u32 = 42;
13680 + const D: u32 = 42;
13681 + const E: u32 = 42;
13682 ˇ
13683 fn main() {
13684 println!("hello");
13685
13686 println!("world");
13687 }
13688 "#
13689 .unindent(),
13690 );
13691
13692 cx.update_editor(|editor, window, cx| {
13693 editor.move_up(&MoveUp, window, cx);
13694 editor.delete_line(&DeleteLine, window, cx);
13695 editor.move_up(&MoveUp, window, cx);
13696 editor.delete_line(&DeleteLine, window, cx);
13697 editor.move_up(&MoveUp, window, cx);
13698 editor.delete_line(&DeleteLine, window, cx);
13699 });
13700 executor.run_until_parked();
13701 cx.assert_state_with_diff(
13702 r#"
13703 use some::mod1;
13704 use some::mod2;
13705
13706 const A: u32 = 42;
13707 + const B: u32 = 42;
13708 ˇ
13709 fn main() {
13710 println!("hello");
13711
13712 println!("world");
13713 }
13714 "#
13715 .unindent(),
13716 );
13717
13718 cx.update_editor(|editor, window, cx| {
13719 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13720 editor.delete_line(&DeleteLine, window, cx);
13721 });
13722 executor.run_until_parked();
13723 cx.assert_state_with_diff(
13724 r#"
13725 ˇ
13726 fn main() {
13727 println!("hello");
13728
13729 println!("world");
13730 }
13731 "#
13732 .unindent(),
13733 );
13734}
13735
13736#[gpui::test]
13737async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13738 init_test(cx, |_| {});
13739
13740 let mut cx = EditorTestContext::new(cx).await;
13741 cx.set_diff_base(indoc! { "
13742 one
13743 two
13744 three
13745 four
13746 five
13747 "
13748 });
13749 cx.set_state(indoc! { "
13750 one
13751 ˇthree
13752 five
13753 "});
13754 cx.run_until_parked();
13755 cx.update_editor(|editor, window, cx| {
13756 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13757 });
13758 cx.assert_state_with_diff(
13759 indoc! { "
13760 one
13761 - two
13762 ˇthree
13763 - four
13764 five
13765 "}
13766 .to_string(),
13767 );
13768 cx.update_editor(|editor, window, cx| {
13769 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13770 });
13771
13772 cx.assert_state_with_diff(
13773 indoc! { "
13774 one
13775 ˇthree
13776 five
13777 "}
13778 .to_string(),
13779 );
13780
13781 cx.set_state(indoc! { "
13782 one
13783 ˇTWO
13784 three
13785 four
13786 five
13787 "});
13788 cx.run_until_parked();
13789 cx.update_editor(|editor, window, cx| {
13790 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13791 });
13792
13793 cx.assert_state_with_diff(
13794 indoc! { "
13795 one
13796 - two
13797 + ˇTWO
13798 three
13799 four
13800 five
13801 "}
13802 .to_string(),
13803 );
13804 cx.update_editor(|editor, window, cx| {
13805 editor.move_up(&Default::default(), window, cx);
13806 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13807 });
13808 cx.assert_state_with_diff(
13809 indoc! { "
13810 one
13811 ˇTWO
13812 three
13813 four
13814 five
13815 "}
13816 .to_string(),
13817 );
13818}
13819
13820#[gpui::test]
13821async fn test_edits_around_expanded_deletion_hunks(
13822 executor: BackgroundExecutor,
13823 cx: &mut gpui::TestAppContext,
13824) {
13825 init_test(cx, |_| {});
13826
13827 let mut cx = EditorTestContext::new(cx).await;
13828
13829 let diff_base = r#"
13830 use some::mod1;
13831 use some::mod2;
13832
13833 const A: u32 = 42;
13834 const B: u32 = 42;
13835 const C: u32 = 42;
13836
13837
13838 fn main() {
13839 println!("hello");
13840
13841 println!("world");
13842 }
13843 "#
13844 .unindent();
13845 executor.run_until_parked();
13846 cx.set_state(
13847 &r#"
13848 use some::mod1;
13849 use some::mod2;
13850
13851 ˇconst B: u32 = 42;
13852 const C: u32 = 42;
13853
13854
13855 fn main() {
13856 println!("hello");
13857
13858 println!("world");
13859 }
13860 "#
13861 .unindent(),
13862 );
13863
13864 cx.set_diff_base(&diff_base);
13865 executor.run_until_parked();
13866
13867 cx.update_editor(|editor, window, cx| {
13868 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13869 });
13870 executor.run_until_parked();
13871
13872 cx.assert_state_with_diff(
13873 r#"
13874 use some::mod1;
13875 use some::mod2;
13876
13877 - const A: u32 = 42;
13878 ˇconst B: u32 = 42;
13879 const C: u32 = 42;
13880
13881
13882 fn main() {
13883 println!("hello");
13884
13885 println!("world");
13886 }
13887 "#
13888 .unindent(),
13889 );
13890
13891 cx.update_editor(|editor, window, cx| {
13892 editor.delete_line(&DeleteLine, window, cx);
13893 });
13894 executor.run_until_parked();
13895 cx.assert_state_with_diff(
13896 r#"
13897 use some::mod1;
13898 use some::mod2;
13899
13900 - const A: u32 = 42;
13901 - const B: u32 = 42;
13902 ˇconst C: u32 = 42;
13903
13904
13905 fn main() {
13906 println!("hello");
13907
13908 println!("world");
13909 }
13910 "#
13911 .unindent(),
13912 );
13913
13914 cx.update_editor(|editor, window, cx| {
13915 editor.delete_line(&DeleteLine, window, cx);
13916 });
13917 executor.run_until_parked();
13918 cx.assert_state_with_diff(
13919 r#"
13920 use some::mod1;
13921 use some::mod2;
13922
13923 - const A: u32 = 42;
13924 - const B: u32 = 42;
13925 - const C: u32 = 42;
13926 ˇ
13927
13928 fn main() {
13929 println!("hello");
13930
13931 println!("world");
13932 }
13933 "#
13934 .unindent(),
13935 );
13936
13937 cx.update_editor(|editor, window, cx| {
13938 editor.handle_input("replacement", window, cx);
13939 });
13940 executor.run_until_parked();
13941 cx.assert_state_with_diff(
13942 r#"
13943 use some::mod1;
13944 use some::mod2;
13945
13946 - const A: u32 = 42;
13947 - const B: u32 = 42;
13948 - const C: u32 = 42;
13949 -
13950 + replacementˇ
13951
13952 fn main() {
13953 println!("hello");
13954
13955 println!("world");
13956 }
13957 "#
13958 .unindent(),
13959 );
13960}
13961
13962#[gpui::test]
13963async fn test_backspace_after_deletion_hunk(
13964 executor: BackgroundExecutor,
13965 cx: &mut gpui::TestAppContext,
13966) {
13967 init_test(cx, |_| {});
13968
13969 let mut cx = EditorTestContext::new(cx).await;
13970
13971 let base_text = r#"
13972 one
13973 two
13974 three
13975 four
13976 five
13977 "#
13978 .unindent();
13979 executor.run_until_parked();
13980 cx.set_state(
13981 &r#"
13982 one
13983 two
13984 fˇour
13985 five
13986 "#
13987 .unindent(),
13988 );
13989
13990 cx.set_diff_base(&base_text);
13991 executor.run_until_parked();
13992
13993 cx.update_editor(|editor, window, cx| {
13994 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13995 });
13996 executor.run_until_parked();
13997
13998 cx.assert_state_with_diff(
13999 r#"
14000 one
14001 two
14002 - three
14003 fˇour
14004 five
14005 "#
14006 .unindent(),
14007 );
14008
14009 cx.update_editor(|editor, window, cx| {
14010 editor.backspace(&Backspace, window, cx);
14011 editor.backspace(&Backspace, window, cx);
14012 });
14013 executor.run_until_parked();
14014 cx.assert_state_with_diff(
14015 r#"
14016 one
14017 two
14018 - threeˇ
14019 - four
14020 + our
14021 five
14022 "#
14023 .unindent(),
14024 );
14025}
14026
14027#[gpui::test]
14028async fn test_edit_after_expanded_modification_hunk(
14029 executor: BackgroundExecutor,
14030 cx: &mut gpui::TestAppContext,
14031) {
14032 init_test(cx, |_| {});
14033
14034 let mut cx = EditorTestContext::new(cx).await;
14035
14036 let diff_base = r#"
14037 use some::mod1;
14038 use some::mod2;
14039
14040 const A: u32 = 42;
14041 const B: u32 = 42;
14042 const C: u32 = 42;
14043 const D: u32 = 42;
14044
14045
14046 fn main() {
14047 println!("hello");
14048
14049 println!("world");
14050 }"#
14051 .unindent();
14052
14053 cx.set_state(
14054 &r#"
14055 use some::mod1;
14056 use some::mod2;
14057
14058 const A: u32 = 42;
14059 const B: u32 = 42;
14060 const C: u32 = 43ˇ
14061 const D: u32 = 42;
14062
14063
14064 fn main() {
14065 println!("hello");
14066
14067 println!("world");
14068 }"#
14069 .unindent(),
14070 );
14071
14072 cx.set_diff_base(&diff_base);
14073 executor.run_until_parked();
14074 cx.update_editor(|editor, window, cx| {
14075 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
14076 });
14077 executor.run_until_parked();
14078
14079 cx.assert_state_with_diff(
14080 r#"
14081 use some::mod1;
14082 use some::mod2;
14083
14084 const A: u32 = 42;
14085 const B: u32 = 42;
14086 - const C: u32 = 42;
14087 + const C: u32 = 43ˇ
14088 const D: u32 = 42;
14089
14090
14091 fn main() {
14092 println!("hello");
14093
14094 println!("world");
14095 }"#
14096 .unindent(),
14097 );
14098
14099 cx.update_editor(|editor, window, cx| {
14100 editor.handle_input("\nnew_line\n", window, cx);
14101 });
14102 executor.run_until_parked();
14103
14104 cx.assert_state_with_diff(
14105 r#"
14106 use some::mod1;
14107 use some::mod2;
14108
14109 const A: u32 = 42;
14110 const B: u32 = 42;
14111 - const C: u32 = 42;
14112 + const C: u32 = 43
14113 + new_line
14114 + ˇ
14115 const D: u32 = 42;
14116
14117
14118 fn main() {
14119 println!("hello");
14120
14121 println!("world");
14122 }"#
14123 .unindent(),
14124 );
14125}
14126
14127#[gpui::test]
14128async fn test_stage_and_unstage_added_file_hunk(
14129 executor: BackgroundExecutor,
14130 cx: &mut gpui::TestAppContext,
14131) {
14132 init_test(cx, |_| {});
14133
14134 let mut cx = EditorTestContext::new(cx).await;
14135 cx.update_editor(|editor, _, cx| {
14136 editor.set_expand_all_diff_hunks(cx);
14137 });
14138
14139 let working_copy = r#"
14140 ˇfn main() {
14141 println!("hello, world!");
14142 }
14143 "#
14144 .unindent();
14145
14146 cx.set_state(&working_copy);
14147 executor.run_until_parked();
14148
14149 cx.assert_state_with_diff(
14150 r#"
14151 + ˇfn main() {
14152 + println!("hello, world!");
14153 + }
14154 "#
14155 .unindent(),
14156 );
14157 cx.assert_index_text(None);
14158
14159 cx.update_editor(|editor, window, cx| {
14160 editor.toggle_staged_selected_diff_hunks(&ToggleStagedSelectedDiffHunks, window, cx);
14161 });
14162 executor.run_until_parked();
14163 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14164 cx.assert_state_with_diff(
14165 r#"
14166 + ˇfn main() {
14167 + println!("hello, world!");
14168 + }
14169 "#
14170 .unindent(),
14171 );
14172
14173 cx.update_editor(|editor, window, cx| {
14174 editor.toggle_staged_selected_diff_hunks(&ToggleStagedSelectedDiffHunks, window, cx);
14175 });
14176 executor.run_until_parked();
14177 cx.assert_index_text(None);
14178}
14179
14180async fn setup_indent_guides_editor(
14181 text: &str,
14182 cx: &mut gpui::TestAppContext,
14183) -> (BufferId, EditorTestContext) {
14184 init_test(cx, |_| {});
14185
14186 let mut cx = EditorTestContext::new(cx).await;
14187
14188 let buffer_id = cx.update_editor(|editor, window, cx| {
14189 editor.set_text(text, window, cx);
14190 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14191
14192 buffer_ids[0]
14193 });
14194
14195 (buffer_id, cx)
14196}
14197
14198fn assert_indent_guides(
14199 range: Range<u32>,
14200 expected: Vec<IndentGuide>,
14201 active_indices: Option<Vec<usize>>,
14202 cx: &mut EditorTestContext,
14203) {
14204 let indent_guides = cx.update_editor(|editor, window, cx| {
14205 let snapshot = editor.snapshot(window, cx).display_snapshot;
14206 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14207 editor,
14208 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14209 true,
14210 &snapshot,
14211 cx,
14212 );
14213
14214 indent_guides.sort_by(|a, b| {
14215 a.depth.cmp(&b.depth).then(
14216 a.start_row
14217 .cmp(&b.start_row)
14218 .then(a.end_row.cmp(&b.end_row)),
14219 )
14220 });
14221 indent_guides
14222 });
14223
14224 if let Some(expected) = active_indices {
14225 let active_indices = cx.update_editor(|editor, window, cx| {
14226 let snapshot = editor.snapshot(window, cx).display_snapshot;
14227 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14228 });
14229
14230 assert_eq!(
14231 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14232 expected,
14233 "Active indent guide indices do not match"
14234 );
14235 }
14236
14237 assert_eq!(indent_guides, expected, "Indent guides do not match");
14238}
14239
14240fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14241 IndentGuide {
14242 buffer_id,
14243 start_row: MultiBufferRow(start_row),
14244 end_row: MultiBufferRow(end_row),
14245 depth,
14246 tab_size: 4,
14247 settings: IndentGuideSettings {
14248 enabled: true,
14249 line_width: 1,
14250 active_line_width: 1,
14251 ..Default::default()
14252 },
14253 }
14254}
14255
14256#[gpui::test]
14257async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
14258 let (buffer_id, mut cx) = setup_indent_guides_editor(
14259 &"
14260 fn main() {
14261 let a = 1;
14262 }"
14263 .unindent(),
14264 cx,
14265 )
14266 .await;
14267
14268 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14269}
14270
14271#[gpui::test]
14272async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
14273 let (buffer_id, mut cx) = setup_indent_guides_editor(
14274 &"
14275 fn main() {
14276 let a = 1;
14277 let b = 2;
14278 }"
14279 .unindent(),
14280 cx,
14281 )
14282 .await;
14283
14284 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14285}
14286
14287#[gpui::test]
14288async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
14289 let (buffer_id, mut cx) = setup_indent_guides_editor(
14290 &"
14291 fn main() {
14292 let a = 1;
14293 if a == 3 {
14294 let b = 2;
14295 } else {
14296 let c = 3;
14297 }
14298 }"
14299 .unindent(),
14300 cx,
14301 )
14302 .await;
14303
14304 assert_indent_guides(
14305 0..8,
14306 vec![
14307 indent_guide(buffer_id, 1, 6, 0),
14308 indent_guide(buffer_id, 3, 3, 1),
14309 indent_guide(buffer_id, 5, 5, 1),
14310 ],
14311 None,
14312 &mut cx,
14313 );
14314}
14315
14316#[gpui::test]
14317async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
14318 let (buffer_id, mut cx) = setup_indent_guides_editor(
14319 &"
14320 fn main() {
14321 let a = 1;
14322 let b = 2;
14323 let c = 3;
14324 }"
14325 .unindent(),
14326 cx,
14327 )
14328 .await;
14329
14330 assert_indent_guides(
14331 0..5,
14332 vec![
14333 indent_guide(buffer_id, 1, 3, 0),
14334 indent_guide(buffer_id, 2, 2, 1),
14335 ],
14336 None,
14337 &mut cx,
14338 );
14339}
14340
14341#[gpui::test]
14342async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
14343 let (buffer_id, mut cx) = setup_indent_guides_editor(
14344 &"
14345 fn main() {
14346 let a = 1;
14347
14348 let c = 3;
14349 }"
14350 .unindent(),
14351 cx,
14352 )
14353 .await;
14354
14355 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14356}
14357
14358#[gpui::test]
14359async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
14360 let (buffer_id, mut cx) = setup_indent_guides_editor(
14361 &"
14362 fn main() {
14363 let a = 1;
14364
14365 let c = 3;
14366
14367 if a == 3 {
14368 let b = 2;
14369 } else {
14370 let c = 3;
14371 }
14372 }"
14373 .unindent(),
14374 cx,
14375 )
14376 .await;
14377
14378 assert_indent_guides(
14379 0..11,
14380 vec![
14381 indent_guide(buffer_id, 1, 9, 0),
14382 indent_guide(buffer_id, 6, 6, 1),
14383 indent_guide(buffer_id, 8, 8, 1),
14384 ],
14385 None,
14386 &mut cx,
14387 );
14388}
14389
14390#[gpui::test]
14391async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
14392 let (buffer_id, mut cx) = setup_indent_guides_editor(
14393 &"
14394 fn main() {
14395 let a = 1;
14396
14397 let c = 3;
14398
14399 if a == 3 {
14400 let b = 2;
14401 } else {
14402 let c = 3;
14403 }
14404 }"
14405 .unindent(),
14406 cx,
14407 )
14408 .await;
14409
14410 assert_indent_guides(
14411 1..11,
14412 vec![
14413 indent_guide(buffer_id, 1, 9, 0),
14414 indent_guide(buffer_id, 6, 6, 1),
14415 indent_guide(buffer_id, 8, 8, 1),
14416 ],
14417 None,
14418 &mut cx,
14419 );
14420}
14421
14422#[gpui::test]
14423async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
14424 let (buffer_id, mut cx) = setup_indent_guides_editor(
14425 &"
14426 fn main() {
14427 let a = 1;
14428
14429 let c = 3;
14430
14431 if a == 3 {
14432 let b = 2;
14433 } else {
14434 let c = 3;
14435 }
14436 }"
14437 .unindent(),
14438 cx,
14439 )
14440 .await;
14441
14442 assert_indent_guides(
14443 1..10,
14444 vec![
14445 indent_guide(buffer_id, 1, 9, 0),
14446 indent_guide(buffer_id, 6, 6, 1),
14447 indent_guide(buffer_id, 8, 8, 1),
14448 ],
14449 None,
14450 &mut cx,
14451 );
14452}
14453
14454#[gpui::test]
14455async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
14456 let (buffer_id, mut cx) = setup_indent_guides_editor(
14457 &"
14458 block1
14459 block2
14460 block3
14461 block4
14462 block2
14463 block1
14464 block1"
14465 .unindent(),
14466 cx,
14467 )
14468 .await;
14469
14470 assert_indent_guides(
14471 1..10,
14472 vec![
14473 indent_guide(buffer_id, 1, 4, 0),
14474 indent_guide(buffer_id, 2, 3, 1),
14475 indent_guide(buffer_id, 3, 3, 2),
14476 ],
14477 None,
14478 &mut cx,
14479 );
14480}
14481
14482#[gpui::test]
14483async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
14484 let (buffer_id, mut cx) = setup_indent_guides_editor(
14485 &"
14486 block1
14487 block2
14488 block3
14489
14490 block1
14491 block1"
14492 .unindent(),
14493 cx,
14494 )
14495 .await;
14496
14497 assert_indent_guides(
14498 0..6,
14499 vec![
14500 indent_guide(buffer_id, 1, 2, 0),
14501 indent_guide(buffer_id, 2, 2, 1),
14502 ],
14503 None,
14504 &mut cx,
14505 );
14506}
14507
14508#[gpui::test]
14509async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
14510 let (buffer_id, mut cx) = setup_indent_guides_editor(
14511 &"
14512 block1
14513
14514
14515
14516 block2
14517 "
14518 .unindent(),
14519 cx,
14520 )
14521 .await;
14522
14523 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14524}
14525
14526#[gpui::test]
14527async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
14528 let (buffer_id, mut cx) = setup_indent_guides_editor(
14529 &"
14530 def a:
14531 \tb = 3
14532 \tif True:
14533 \t\tc = 4
14534 \t\td = 5
14535 \tprint(b)
14536 "
14537 .unindent(),
14538 cx,
14539 )
14540 .await;
14541
14542 assert_indent_guides(
14543 0..6,
14544 vec![
14545 indent_guide(buffer_id, 1, 6, 0),
14546 indent_guide(buffer_id, 3, 4, 1),
14547 ],
14548 None,
14549 &mut cx,
14550 );
14551}
14552
14553#[gpui::test]
14554async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
14555 let (buffer_id, mut cx) = setup_indent_guides_editor(
14556 &"
14557 fn main() {
14558 let a = 1;
14559 }"
14560 .unindent(),
14561 cx,
14562 )
14563 .await;
14564
14565 cx.update_editor(|editor, window, cx| {
14566 editor.change_selections(None, window, cx, |s| {
14567 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14568 });
14569 });
14570
14571 assert_indent_guides(
14572 0..3,
14573 vec![indent_guide(buffer_id, 1, 1, 0)],
14574 Some(vec![0]),
14575 &mut cx,
14576 );
14577}
14578
14579#[gpui::test]
14580async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
14581 let (buffer_id, mut cx) = setup_indent_guides_editor(
14582 &"
14583 fn main() {
14584 if 1 == 2 {
14585 let a = 1;
14586 }
14587 }"
14588 .unindent(),
14589 cx,
14590 )
14591 .await;
14592
14593 cx.update_editor(|editor, window, cx| {
14594 editor.change_selections(None, window, cx, |s| {
14595 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14596 });
14597 });
14598
14599 assert_indent_guides(
14600 0..4,
14601 vec![
14602 indent_guide(buffer_id, 1, 3, 0),
14603 indent_guide(buffer_id, 2, 2, 1),
14604 ],
14605 Some(vec![1]),
14606 &mut cx,
14607 );
14608
14609 cx.update_editor(|editor, window, cx| {
14610 editor.change_selections(None, window, cx, |s| {
14611 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14612 });
14613 });
14614
14615 assert_indent_guides(
14616 0..4,
14617 vec![
14618 indent_guide(buffer_id, 1, 3, 0),
14619 indent_guide(buffer_id, 2, 2, 1),
14620 ],
14621 Some(vec![1]),
14622 &mut cx,
14623 );
14624
14625 cx.update_editor(|editor, window, cx| {
14626 editor.change_selections(None, window, cx, |s| {
14627 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14628 });
14629 });
14630
14631 assert_indent_guides(
14632 0..4,
14633 vec![
14634 indent_guide(buffer_id, 1, 3, 0),
14635 indent_guide(buffer_id, 2, 2, 1),
14636 ],
14637 Some(vec![0]),
14638 &mut cx,
14639 );
14640}
14641
14642#[gpui::test]
14643async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
14644 let (buffer_id, mut cx) = setup_indent_guides_editor(
14645 &"
14646 fn main() {
14647 let a = 1;
14648
14649 let b = 2;
14650 }"
14651 .unindent(),
14652 cx,
14653 )
14654 .await;
14655
14656 cx.update_editor(|editor, window, cx| {
14657 editor.change_selections(None, window, cx, |s| {
14658 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14659 });
14660 });
14661
14662 assert_indent_guides(
14663 0..5,
14664 vec![indent_guide(buffer_id, 1, 3, 0)],
14665 Some(vec![0]),
14666 &mut cx,
14667 );
14668}
14669
14670#[gpui::test]
14671async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
14672 let (buffer_id, mut cx) = setup_indent_guides_editor(
14673 &"
14674 def m:
14675 a = 1
14676 pass"
14677 .unindent(),
14678 cx,
14679 )
14680 .await;
14681
14682 cx.update_editor(|editor, window, cx| {
14683 editor.change_selections(None, window, cx, |s| {
14684 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14685 });
14686 });
14687
14688 assert_indent_guides(
14689 0..3,
14690 vec![indent_guide(buffer_id, 1, 2, 0)],
14691 Some(vec![0]),
14692 &mut cx,
14693 );
14694}
14695
14696#[gpui::test]
14697async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContext) {
14698 init_test(cx, |_| {});
14699 let mut cx = EditorTestContext::new(cx).await;
14700 let text = indoc! {
14701 "
14702 impl A {
14703 fn b() {
14704 0;
14705 3;
14706 5;
14707 6;
14708 7;
14709 }
14710 }
14711 "
14712 };
14713 let base_text = indoc! {
14714 "
14715 impl A {
14716 fn b() {
14717 0;
14718 1;
14719 2;
14720 3;
14721 4;
14722 }
14723 fn c() {
14724 5;
14725 6;
14726 7;
14727 }
14728 }
14729 "
14730 };
14731
14732 cx.update_editor(|editor, window, cx| {
14733 editor.set_text(text, window, cx);
14734
14735 editor.buffer().update(cx, |multibuffer, cx| {
14736 let buffer = multibuffer.as_singleton().unwrap();
14737 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
14738
14739 multibuffer.set_all_diff_hunks_expanded(cx);
14740 multibuffer.add_diff(diff, cx);
14741
14742 buffer.read(cx).remote_id()
14743 })
14744 });
14745 cx.run_until_parked();
14746
14747 cx.assert_state_with_diff(
14748 indoc! { "
14749 impl A {
14750 fn b() {
14751 0;
14752 - 1;
14753 - 2;
14754 3;
14755 - 4;
14756 - }
14757 - fn c() {
14758 5;
14759 6;
14760 7;
14761 }
14762 }
14763 ˇ"
14764 }
14765 .to_string(),
14766 );
14767
14768 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14769 editor
14770 .snapshot(window, cx)
14771 .buffer_snapshot
14772 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14773 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14774 .collect::<Vec<_>>()
14775 });
14776 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14777 assert_eq!(
14778 actual_guides,
14779 vec![
14780 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14781 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14782 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14783 ]
14784 );
14785}
14786
14787#[gpui::test]
14788fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
14789 init_test(cx, |_| {});
14790
14791 let editor = cx.add_window(|window, cx| {
14792 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
14793 build_editor(buffer, window, cx)
14794 });
14795
14796 let render_args = Arc::new(Mutex::new(None));
14797 let snapshot = editor
14798 .update(cx, |editor, window, cx| {
14799 let snapshot = editor.buffer().read(cx).snapshot(cx);
14800 let range =
14801 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
14802
14803 struct RenderArgs {
14804 row: MultiBufferRow,
14805 folded: bool,
14806 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
14807 }
14808
14809 let crease = Crease::inline(
14810 range,
14811 FoldPlaceholder::test(),
14812 {
14813 let toggle_callback = render_args.clone();
14814 move |row, folded, callback, _window, _cx| {
14815 *toggle_callback.lock() = Some(RenderArgs {
14816 row,
14817 folded,
14818 callback,
14819 });
14820 div()
14821 }
14822 },
14823 |_row, _folded, _window, _cx| div(),
14824 );
14825
14826 editor.insert_creases(Some(crease), cx);
14827 let snapshot = editor.snapshot(window, cx);
14828 let _div = snapshot.render_crease_toggle(
14829 MultiBufferRow(1),
14830 false,
14831 cx.entity().clone(),
14832 window,
14833 cx,
14834 );
14835 snapshot
14836 })
14837 .unwrap();
14838
14839 let render_args = render_args.lock().take().unwrap();
14840 assert_eq!(render_args.row, MultiBufferRow(1));
14841 assert!(!render_args.folded);
14842 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14843
14844 cx.update_window(*editor, |_, window, cx| {
14845 (render_args.callback)(true, window, cx)
14846 })
14847 .unwrap();
14848 let snapshot = editor
14849 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14850 .unwrap();
14851 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
14852
14853 cx.update_window(*editor, |_, window, cx| {
14854 (render_args.callback)(false, window, cx)
14855 })
14856 .unwrap();
14857 let snapshot = editor
14858 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14859 .unwrap();
14860 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14861}
14862
14863#[gpui::test]
14864async fn test_input_text(cx: &mut gpui::TestAppContext) {
14865 init_test(cx, |_| {});
14866 let mut cx = EditorTestContext::new(cx).await;
14867
14868 cx.set_state(
14869 &r#"ˇone
14870 two
14871
14872 three
14873 fourˇ
14874 five
14875
14876 siˇx"#
14877 .unindent(),
14878 );
14879
14880 cx.dispatch_action(HandleInput(String::new()));
14881 cx.assert_editor_state(
14882 &r#"ˇone
14883 two
14884
14885 three
14886 fourˇ
14887 five
14888
14889 siˇx"#
14890 .unindent(),
14891 );
14892
14893 cx.dispatch_action(HandleInput("AAAA".to_string()));
14894 cx.assert_editor_state(
14895 &r#"AAAAˇone
14896 two
14897
14898 three
14899 fourAAAAˇ
14900 five
14901
14902 siAAAAˇx"#
14903 .unindent(),
14904 );
14905}
14906
14907#[gpui::test]
14908async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
14909 init_test(cx, |_| {});
14910
14911 let mut cx = EditorTestContext::new(cx).await;
14912 cx.set_state(
14913 r#"let foo = 1;
14914let foo = 2;
14915let foo = 3;
14916let fooˇ = 4;
14917let foo = 5;
14918let foo = 6;
14919let foo = 7;
14920let foo = 8;
14921let foo = 9;
14922let foo = 10;
14923let foo = 11;
14924let foo = 12;
14925let foo = 13;
14926let foo = 14;
14927let foo = 15;"#,
14928 );
14929
14930 cx.update_editor(|e, window, cx| {
14931 assert_eq!(
14932 e.next_scroll_position,
14933 NextScrollCursorCenterTopBottom::Center,
14934 "Default next scroll direction is center",
14935 );
14936
14937 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14938 assert_eq!(
14939 e.next_scroll_position,
14940 NextScrollCursorCenterTopBottom::Top,
14941 "After center, next scroll direction should be top",
14942 );
14943
14944 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14945 assert_eq!(
14946 e.next_scroll_position,
14947 NextScrollCursorCenterTopBottom::Bottom,
14948 "After top, next scroll direction should be bottom",
14949 );
14950
14951 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14952 assert_eq!(
14953 e.next_scroll_position,
14954 NextScrollCursorCenterTopBottom::Center,
14955 "After bottom, scrolling should start over",
14956 );
14957
14958 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14959 assert_eq!(
14960 e.next_scroll_position,
14961 NextScrollCursorCenterTopBottom::Top,
14962 "Scrolling continues if retriggered fast enough"
14963 );
14964 });
14965
14966 cx.executor()
14967 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
14968 cx.executor().run_until_parked();
14969 cx.update_editor(|e, _, _| {
14970 assert_eq!(
14971 e.next_scroll_position,
14972 NextScrollCursorCenterTopBottom::Center,
14973 "If scrolling is not triggered fast enough, it should reset"
14974 );
14975 });
14976}
14977
14978#[gpui::test]
14979async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
14980 init_test(cx, |_| {});
14981 let mut cx = EditorLspTestContext::new_rust(
14982 lsp::ServerCapabilities {
14983 definition_provider: Some(lsp::OneOf::Left(true)),
14984 references_provider: Some(lsp::OneOf::Left(true)),
14985 ..lsp::ServerCapabilities::default()
14986 },
14987 cx,
14988 )
14989 .await;
14990
14991 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
14992 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
14993 move |params, _| async move {
14994 if empty_go_to_definition {
14995 Ok(None)
14996 } else {
14997 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
14998 uri: params.text_document_position_params.text_document.uri,
14999 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15000 })))
15001 }
15002 },
15003 );
15004 let references =
15005 cx.lsp
15006 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15007 Ok(Some(vec![lsp::Location {
15008 uri: params.text_document_position.text_document.uri,
15009 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15010 }]))
15011 });
15012 (go_to_definition, references)
15013 };
15014
15015 cx.set_state(
15016 &r#"fn one() {
15017 let mut a = ˇtwo();
15018 }
15019
15020 fn two() {}"#
15021 .unindent(),
15022 );
15023 set_up_lsp_handlers(false, &mut cx);
15024 let navigated = cx
15025 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15026 .await
15027 .expect("Failed to navigate to definition");
15028 assert_eq!(
15029 navigated,
15030 Navigated::Yes,
15031 "Should have navigated to definition from the GetDefinition response"
15032 );
15033 cx.assert_editor_state(
15034 &r#"fn one() {
15035 let mut a = two();
15036 }
15037
15038 fn «twoˇ»() {}"#
15039 .unindent(),
15040 );
15041
15042 let editors = cx.update_workspace(|workspace, _, cx| {
15043 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15044 });
15045 cx.update_editor(|_, _, test_editor_cx| {
15046 assert_eq!(
15047 editors.len(),
15048 1,
15049 "Initially, only one, test, editor should be open in the workspace"
15050 );
15051 assert_eq!(
15052 test_editor_cx.entity(),
15053 editors.last().expect("Asserted len is 1").clone()
15054 );
15055 });
15056
15057 set_up_lsp_handlers(true, &mut cx);
15058 let navigated = cx
15059 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15060 .await
15061 .expect("Failed to navigate to lookup references");
15062 assert_eq!(
15063 navigated,
15064 Navigated::Yes,
15065 "Should have navigated to references as a fallback after empty GoToDefinition response"
15066 );
15067 // We should not change the selections in the existing file,
15068 // if opening another milti buffer with the references
15069 cx.assert_editor_state(
15070 &r#"fn one() {
15071 let mut a = two();
15072 }
15073
15074 fn «twoˇ»() {}"#
15075 .unindent(),
15076 );
15077 let editors = cx.update_workspace(|workspace, _, cx| {
15078 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15079 });
15080 cx.update_editor(|_, _, test_editor_cx| {
15081 assert_eq!(
15082 editors.len(),
15083 2,
15084 "After falling back to references search, we open a new editor with the results"
15085 );
15086 let references_fallback_text = editors
15087 .into_iter()
15088 .find(|new_editor| *new_editor != test_editor_cx.entity())
15089 .expect("Should have one non-test editor now")
15090 .read(test_editor_cx)
15091 .text(test_editor_cx);
15092 assert_eq!(
15093 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15094 "Should use the range from the references response and not the GoToDefinition one"
15095 );
15096 });
15097}
15098
15099#[gpui::test]
15100async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
15101 init_test(cx, |_| {});
15102
15103 let language = Arc::new(Language::new(
15104 LanguageConfig::default(),
15105 Some(tree_sitter_rust::LANGUAGE.into()),
15106 ));
15107
15108 let text = r#"
15109 #[cfg(test)]
15110 mod tests() {
15111 #[test]
15112 fn runnable_1() {
15113 let a = 1;
15114 }
15115
15116 #[test]
15117 fn runnable_2() {
15118 let a = 1;
15119 let b = 2;
15120 }
15121 }
15122 "#
15123 .unindent();
15124
15125 let fs = FakeFs::new(cx.executor());
15126 fs.insert_file("/file.rs", Default::default()).await;
15127
15128 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15129 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15130 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15131 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15132 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15133
15134 let editor = cx.new_window_entity(|window, cx| {
15135 Editor::new(
15136 EditorMode::Full,
15137 multi_buffer,
15138 Some(project.clone()),
15139 true,
15140 window,
15141 cx,
15142 )
15143 });
15144
15145 editor.update_in(cx, |editor, window, cx| {
15146 editor.tasks.insert(
15147 (buffer.read(cx).remote_id(), 3),
15148 RunnableTasks {
15149 templates: vec![],
15150 offset: MultiBufferOffset(43),
15151 column: 0,
15152 extra_variables: HashMap::default(),
15153 context_range: BufferOffset(43)..BufferOffset(85),
15154 },
15155 );
15156 editor.tasks.insert(
15157 (buffer.read(cx).remote_id(), 8),
15158 RunnableTasks {
15159 templates: vec![],
15160 offset: MultiBufferOffset(86),
15161 column: 0,
15162 extra_variables: HashMap::default(),
15163 context_range: BufferOffset(86)..BufferOffset(191),
15164 },
15165 );
15166
15167 // Test finding task when cursor is inside function body
15168 editor.change_selections(None, window, cx, |s| {
15169 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15170 });
15171 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15172 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15173
15174 // Test finding task when cursor is on function name
15175 editor.change_selections(None, window, cx, |s| {
15176 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15177 });
15178 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15179 assert_eq!(row, 8, "Should find task when cursor is on function name");
15180 });
15181}
15182
15183#[gpui::test]
15184async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
15185 init_test(cx, |_| {});
15186
15187 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15188 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15189 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15190
15191 let fs = FakeFs::new(cx.executor());
15192 fs.insert_tree(
15193 path!("/a"),
15194 json!({
15195 "first.rs": sample_text_1,
15196 "second.rs": sample_text_2,
15197 "third.rs": sample_text_3,
15198 }),
15199 )
15200 .await;
15201 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15202 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15203 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15204 let worktree = project.update(cx, |project, cx| {
15205 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15206 assert_eq!(worktrees.len(), 1);
15207 worktrees.pop().unwrap()
15208 });
15209 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15210
15211 let buffer_1 = project
15212 .update(cx, |project, cx| {
15213 project.open_buffer((worktree_id, "first.rs"), cx)
15214 })
15215 .await
15216 .unwrap();
15217 let buffer_2 = project
15218 .update(cx, |project, cx| {
15219 project.open_buffer((worktree_id, "second.rs"), cx)
15220 })
15221 .await
15222 .unwrap();
15223 let buffer_3 = project
15224 .update(cx, |project, cx| {
15225 project.open_buffer((worktree_id, "third.rs"), cx)
15226 })
15227 .await
15228 .unwrap();
15229
15230 let multi_buffer = cx.new(|cx| {
15231 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15232 multi_buffer.push_excerpts(
15233 buffer_1.clone(),
15234 [
15235 ExcerptRange {
15236 context: Point::new(0, 0)..Point::new(3, 0),
15237 primary: None,
15238 },
15239 ExcerptRange {
15240 context: Point::new(5, 0)..Point::new(7, 0),
15241 primary: None,
15242 },
15243 ExcerptRange {
15244 context: Point::new(9, 0)..Point::new(10, 4),
15245 primary: None,
15246 },
15247 ],
15248 cx,
15249 );
15250 multi_buffer.push_excerpts(
15251 buffer_2.clone(),
15252 [
15253 ExcerptRange {
15254 context: Point::new(0, 0)..Point::new(3, 0),
15255 primary: None,
15256 },
15257 ExcerptRange {
15258 context: Point::new(5, 0)..Point::new(7, 0),
15259 primary: None,
15260 },
15261 ExcerptRange {
15262 context: Point::new(9, 0)..Point::new(10, 4),
15263 primary: None,
15264 },
15265 ],
15266 cx,
15267 );
15268 multi_buffer.push_excerpts(
15269 buffer_3.clone(),
15270 [
15271 ExcerptRange {
15272 context: Point::new(0, 0)..Point::new(3, 0),
15273 primary: None,
15274 },
15275 ExcerptRange {
15276 context: Point::new(5, 0)..Point::new(7, 0),
15277 primary: None,
15278 },
15279 ExcerptRange {
15280 context: Point::new(9, 0)..Point::new(10, 4),
15281 primary: None,
15282 },
15283 ],
15284 cx,
15285 );
15286 multi_buffer
15287 });
15288 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15289 Editor::new(
15290 EditorMode::Full,
15291 multi_buffer,
15292 Some(project.clone()),
15293 true,
15294 window,
15295 cx,
15296 )
15297 });
15298
15299 let full_text = "\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";
15300 assert_eq!(
15301 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15302 full_text,
15303 );
15304
15305 multi_buffer_editor.update(cx, |editor, cx| {
15306 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15307 });
15308 assert_eq!(
15309 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15310 "\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",
15311 "After folding the first buffer, its text should not be displayed"
15312 );
15313
15314 multi_buffer_editor.update(cx, |editor, cx| {
15315 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15316 });
15317 assert_eq!(
15318 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15319 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15320 "After folding the second buffer, its text should not be displayed"
15321 );
15322
15323 multi_buffer_editor.update(cx, |editor, cx| {
15324 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15325 });
15326 assert_eq!(
15327 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15328 "\n\n\n\n\n",
15329 "After folding the third buffer, its text should not be displayed"
15330 );
15331
15332 // Emulate selection inside the fold logic, that should work
15333 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15334 editor
15335 .snapshot(window, cx)
15336 .next_line_boundary(Point::new(0, 4));
15337 });
15338
15339 multi_buffer_editor.update(cx, |editor, cx| {
15340 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15341 });
15342 assert_eq!(
15343 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15344 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15345 "After unfolding the second buffer, its text should be displayed"
15346 );
15347
15348 multi_buffer_editor.update(cx, |editor, cx| {
15349 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15350 });
15351 assert_eq!(
15352 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15353 "\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",
15354 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15355 );
15356
15357 multi_buffer_editor.update(cx, |editor, cx| {
15358 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15359 });
15360 assert_eq!(
15361 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15362 full_text,
15363 "After unfolding the all buffers, all original text should be displayed"
15364 );
15365}
15366
15367#[gpui::test]
15368async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
15369 init_test(cx, |_| {});
15370
15371 let sample_text_1 = "1111\n2222\n3333".to_string();
15372 let sample_text_2 = "4444\n5555\n6666".to_string();
15373 let sample_text_3 = "7777\n8888\n9999".to_string();
15374
15375 let fs = FakeFs::new(cx.executor());
15376 fs.insert_tree(
15377 path!("/a"),
15378 json!({
15379 "first.rs": sample_text_1,
15380 "second.rs": sample_text_2,
15381 "third.rs": sample_text_3,
15382 }),
15383 )
15384 .await;
15385 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15386 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15387 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15388 let worktree = project.update(cx, |project, cx| {
15389 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15390 assert_eq!(worktrees.len(), 1);
15391 worktrees.pop().unwrap()
15392 });
15393 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15394
15395 let buffer_1 = project
15396 .update(cx, |project, cx| {
15397 project.open_buffer((worktree_id, "first.rs"), cx)
15398 })
15399 .await
15400 .unwrap();
15401 let buffer_2 = project
15402 .update(cx, |project, cx| {
15403 project.open_buffer((worktree_id, "second.rs"), cx)
15404 })
15405 .await
15406 .unwrap();
15407 let buffer_3 = project
15408 .update(cx, |project, cx| {
15409 project.open_buffer((worktree_id, "third.rs"), cx)
15410 })
15411 .await
15412 .unwrap();
15413
15414 let multi_buffer = cx.new(|cx| {
15415 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15416 multi_buffer.push_excerpts(
15417 buffer_1.clone(),
15418 [ExcerptRange {
15419 context: Point::new(0, 0)..Point::new(3, 0),
15420 primary: None,
15421 }],
15422 cx,
15423 );
15424 multi_buffer.push_excerpts(
15425 buffer_2.clone(),
15426 [ExcerptRange {
15427 context: Point::new(0, 0)..Point::new(3, 0),
15428 primary: None,
15429 }],
15430 cx,
15431 );
15432 multi_buffer.push_excerpts(
15433 buffer_3.clone(),
15434 [ExcerptRange {
15435 context: Point::new(0, 0)..Point::new(3, 0),
15436 primary: None,
15437 }],
15438 cx,
15439 );
15440 multi_buffer
15441 });
15442
15443 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15444 Editor::new(
15445 EditorMode::Full,
15446 multi_buffer,
15447 Some(project.clone()),
15448 true,
15449 window,
15450 cx,
15451 )
15452 });
15453
15454 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
15455 assert_eq!(
15456 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15457 full_text,
15458 );
15459
15460 multi_buffer_editor.update(cx, |editor, cx| {
15461 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15462 });
15463 assert_eq!(
15464 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15465 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
15466 "After folding the first buffer, its text should not be displayed"
15467 );
15468
15469 multi_buffer_editor.update(cx, |editor, cx| {
15470 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15471 });
15472
15473 assert_eq!(
15474 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15475 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
15476 "After folding the second buffer, its text should not be displayed"
15477 );
15478
15479 multi_buffer_editor.update(cx, |editor, cx| {
15480 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15481 });
15482 assert_eq!(
15483 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15484 "\n\n\n\n\n",
15485 "After folding the third buffer, its text should not be displayed"
15486 );
15487
15488 multi_buffer_editor.update(cx, |editor, cx| {
15489 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15490 });
15491 assert_eq!(
15492 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15493 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
15494 "After unfolding the second buffer, its text should be displayed"
15495 );
15496
15497 multi_buffer_editor.update(cx, |editor, cx| {
15498 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15499 });
15500 assert_eq!(
15501 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15502 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
15503 "After unfolding the first buffer, its text should be displayed"
15504 );
15505
15506 multi_buffer_editor.update(cx, |editor, cx| {
15507 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15508 });
15509 assert_eq!(
15510 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15511 full_text,
15512 "After unfolding all buffers, all original text should be displayed"
15513 );
15514}
15515
15516#[gpui::test]
15517async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
15518 init_test(cx, |_| {});
15519
15520 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15521
15522 let fs = FakeFs::new(cx.executor());
15523 fs.insert_tree(
15524 path!("/a"),
15525 json!({
15526 "main.rs": sample_text,
15527 }),
15528 )
15529 .await;
15530 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15531 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15532 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15533 let worktree = project.update(cx, |project, cx| {
15534 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15535 assert_eq!(worktrees.len(), 1);
15536 worktrees.pop().unwrap()
15537 });
15538 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15539
15540 let buffer_1 = project
15541 .update(cx, |project, cx| {
15542 project.open_buffer((worktree_id, "main.rs"), cx)
15543 })
15544 .await
15545 .unwrap();
15546
15547 let multi_buffer = cx.new(|cx| {
15548 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15549 multi_buffer.push_excerpts(
15550 buffer_1.clone(),
15551 [ExcerptRange {
15552 context: Point::new(0, 0)
15553 ..Point::new(
15554 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
15555 0,
15556 ),
15557 primary: None,
15558 }],
15559 cx,
15560 );
15561 multi_buffer
15562 });
15563 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15564 Editor::new(
15565 EditorMode::Full,
15566 multi_buffer,
15567 Some(project.clone()),
15568 true,
15569 window,
15570 cx,
15571 )
15572 });
15573
15574 let selection_range = Point::new(1, 0)..Point::new(2, 0);
15575 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15576 enum TestHighlight {}
15577 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
15578 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
15579 editor.highlight_text::<TestHighlight>(
15580 vec![highlight_range.clone()],
15581 HighlightStyle::color(Hsla::green()),
15582 cx,
15583 );
15584 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
15585 });
15586
15587 let full_text = format!("\n\n\n{sample_text}\n");
15588 assert_eq!(
15589 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15590 full_text,
15591 );
15592}
15593
15594#[gpui::test]
15595async fn test_inline_completion_text(cx: &mut TestAppContext) {
15596 init_test(cx, |_| {});
15597
15598 // Simple insertion
15599 assert_highlighted_edits(
15600 "Hello, world!",
15601 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
15602 true,
15603 cx,
15604 |highlighted_edits, cx| {
15605 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
15606 assert_eq!(highlighted_edits.highlights.len(), 1);
15607 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
15608 assert_eq!(
15609 highlighted_edits.highlights[0].1.background_color,
15610 Some(cx.theme().status().created_background)
15611 );
15612 },
15613 )
15614 .await;
15615
15616 // Replacement
15617 assert_highlighted_edits(
15618 "This is a test.",
15619 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
15620 false,
15621 cx,
15622 |highlighted_edits, cx| {
15623 assert_eq!(highlighted_edits.text, "That is a test.");
15624 assert_eq!(highlighted_edits.highlights.len(), 1);
15625 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
15626 assert_eq!(
15627 highlighted_edits.highlights[0].1.background_color,
15628 Some(cx.theme().status().created_background)
15629 );
15630 },
15631 )
15632 .await;
15633
15634 // Multiple edits
15635 assert_highlighted_edits(
15636 "Hello, world!",
15637 vec![
15638 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
15639 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
15640 ],
15641 false,
15642 cx,
15643 |highlighted_edits, cx| {
15644 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
15645 assert_eq!(highlighted_edits.highlights.len(), 2);
15646 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
15647 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
15648 assert_eq!(
15649 highlighted_edits.highlights[0].1.background_color,
15650 Some(cx.theme().status().created_background)
15651 );
15652 assert_eq!(
15653 highlighted_edits.highlights[1].1.background_color,
15654 Some(cx.theme().status().created_background)
15655 );
15656 },
15657 )
15658 .await;
15659
15660 // Multiple lines with edits
15661 assert_highlighted_edits(
15662 "First line\nSecond line\nThird line\nFourth line",
15663 vec![
15664 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
15665 (
15666 Point::new(2, 0)..Point::new(2, 10),
15667 "New third line".to_string(),
15668 ),
15669 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
15670 ],
15671 false,
15672 cx,
15673 |highlighted_edits, cx| {
15674 assert_eq!(
15675 highlighted_edits.text,
15676 "Second modified\nNew third line\nFourth updated line"
15677 );
15678 assert_eq!(highlighted_edits.highlights.len(), 3);
15679 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
15680 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
15681 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
15682 for highlight in &highlighted_edits.highlights {
15683 assert_eq!(
15684 highlight.1.background_color,
15685 Some(cx.theme().status().created_background)
15686 );
15687 }
15688 },
15689 )
15690 .await;
15691}
15692
15693#[gpui::test]
15694async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
15695 init_test(cx, |_| {});
15696
15697 // Deletion
15698 assert_highlighted_edits(
15699 "Hello, world!",
15700 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
15701 true,
15702 cx,
15703 |highlighted_edits, cx| {
15704 assert_eq!(highlighted_edits.text, "Hello, world!");
15705 assert_eq!(highlighted_edits.highlights.len(), 1);
15706 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
15707 assert_eq!(
15708 highlighted_edits.highlights[0].1.background_color,
15709 Some(cx.theme().status().deleted_background)
15710 );
15711 },
15712 )
15713 .await;
15714
15715 // Insertion
15716 assert_highlighted_edits(
15717 "Hello, world!",
15718 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
15719 true,
15720 cx,
15721 |highlighted_edits, cx| {
15722 assert_eq!(highlighted_edits.highlights.len(), 1);
15723 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
15724 assert_eq!(
15725 highlighted_edits.highlights[0].1.background_color,
15726 Some(cx.theme().status().created_background)
15727 );
15728 },
15729 )
15730 .await;
15731}
15732
15733async fn assert_highlighted_edits(
15734 text: &str,
15735 edits: Vec<(Range<Point>, String)>,
15736 include_deletions: bool,
15737 cx: &mut TestAppContext,
15738 assertion_fn: impl Fn(HighlightedText, &App),
15739) {
15740 let window = cx.add_window(|window, cx| {
15741 let buffer = MultiBuffer::build_simple(text, cx);
15742 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
15743 });
15744 let cx = &mut VisualTestContext::from_window(*window, cx);
15745
15746 let (buffer, snapshot) = window
15747 .update(cx, |editor, _window, cx| {
15748 (
15749 editor.buffer().clone(),
15750 editor.buffer().read(cx).snapshot(cx),
15751 )
15752 })
15753 .unwrap();
15754
15755 let edits = edits
15756 .into_iter()
15757 .map(|(range, edit)| {
15758 (
15759 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
15760 edit,
15761 )
15762 })
15763 .collect::<Vec<_>>();
15764
15765 let text_anchor_edits = edits
15766 .clone()
15767 .into_iter()
15768 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
15769 .collect::<Vec<_>>();
15770
15771 let edit_preview = window
15772 .update(cx, |_, _window, cx| {
15773 buffer
15774 .read(cx)
15775 .as_singleton()
15776 .unwrap()
15777 .read(cx)
15778 .preview_edits(text_anchor_edits.into(), cx)
15779 })
15780 .unwrap()
15781 .await;
15782
15783 cx.update(|_window, cx| {
15784 let highlighted_edits = inline_completion_edit_text(
15785 &snapshot.as_singleton().unwrap().2,
15786 &edits,
15787 &edit_preview,
15788 include_deletions,
15789 cx,
15790 );
15791 assertion_fn(highlighted_edits, cx)
15792 });
15793}
15794
15795#[gpui::test]
15796async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
15797 init_test(cx, |_| {});
15798 let capabilities = lsp::ServerCapabilities {
15799 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
15800 prepare_provider: Some(true),
15801 work_done_progress_options: Default::default(),
15802 })),
15803 ..Default::default()
15804 };
15805 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15806
15807 cx.set_state(indoc! {"
15808 struct Fˇoo {}
15809 "});
15810
15811 cx.update_editor(|editor, _, cx| {
15812 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15813 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15814 editor.highlight_background::<DocumentHighlightRead>(
15815 &[highlight_range],
15816 |c| c.editor_document_highlight_read_background,
15817 cx,
15818 );
15819 });
15820
15821 let mut prepare_rename_handler =
15822 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
15823 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
15824 start: lsp::Position {
15825 line: 0,
15826 character: 7,
15827 },
15828 end: lsp::Position {
15829 line: 0,
15830 character: 10,
15831 },
15832 })))
15833 });
15834 let prepare_rename_task = cx
15835 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15836 .expect("Prepare rename was not started");
15837 prepare_rename_handler.next().await.unwrap();
15838 prepare_rename_task.await.expect("Prepare rename failed");
15839
15840 let mut rename_handler =
15841 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15842 let edit = lsp::TextEdit {
15843 range: lsp::Range {
15844 start: lsp::Position {
15845 line: 0,
15846 character: 7,
15847 },
15848 end: lsp::Position {
15849 line: 0,
15850 character: 10,
15851 },
15852 },
15853 new_text: "FooRenamed".to_string(),
15854 };
15855 Ok(Some(lsp::WorkspaceEdit::new(
15856 // Specify the same edit twice
15857 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
15858 )))
15859 });
15860 let rename_task = cx
15861 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15862 .expect("Confirm rename was not started");
15863 rename_handler.next().await.unwrap();
15864 rename_task.await.expect("Confirm rename failed");
15865 cx.run_until_parked();
15866
15867 // Despite two edits, only one is actually applied as those are identical
15868 cx.assert_editor_state(indoc! {"
15869 struct FooRenamedˇ {}
15870 "});
15871}
15872
15873#[gpui::test]
15874async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
15875 init_test(cx, |_| {});
15876 // These capabilities indicate that the server does not support prepare rename.
15877 let capabilities = lsp::ServerCapabilities {
15878 rename_provider: Some(lsp::OneOf::Left(true)),
15879 ..Default::default()
15880 };
15881 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15882
15883 cx.set_state(indoc! {"
15884 struct Fˇoo {}
15885 "});
15886
15887 cx.update_editor(|editor, _window, cx| {
15888 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15889 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15890 editor.highlight_background::<DocumentHighlightRead>(
15891 &[highlight_range],
15892 |c| c.editor_document_highlight_read_background,
15893 cx,
15894 );
15895 });
15896
15897 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15898 .expect("Prepare rename was not started")
15899 .await
15900 .expect("Prepare rename failed");
15901
15902 let mut rename_handler =
15903 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15904 let edit = lsp::TextEdit {
15905 range: lsp::Range {
15906 start: lsp::Position {
15907 line: 0,
15908 character: 7,
15909 },
15910 end: lsp::Position {
15911 line: 0,
15912 character: 10,
15913 },
15914 },
15915 new_text: "FooRenamed".to_string(),
15916 };
15917 Ok(Some(lsp::WorkspaceEdit::new(
15918 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
15919 )))
15920 });
15921 let rename_task = cx
15922 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15923 .expect("Confirm rename was not started");
15924 rename_handler.next().await.unwrap();
15925 rename_task.await.expect("Confirm rename failed");
15926 cx.run_until_parked();
15927
15928 // Correct range is renamed, as `surrounding_word` is used to find it.
15929 cx.assert_editor_state(indoc! {"
15930 struct FooRenamedˇ {}
15931 "});
15932}
15933
15934fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
15935 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
15936 point..point
15937}
15938
15939fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
15940 let (text, ranges) = marked_text_ranges(marked_text, true);
15941 assert_eq!(editor.text(cx), text);
15942 assert_eq!(
15943 editor.selections.ranges(cx),
15944 ranges,
15945 "Assert selections are {}",
15946 marked_text
15947 );
15948}
15949
15950pub fn handle_signature_help_request(
15951 cx: &mut EditorLspTestContext,
15952 mocked_response: lsp::SignatureHelp,
15953) -> impl Future<Output = ()> {
15954 let mut request =
15955 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
15956 let mocked_response = mocked_response.clone();
15957 async move { Ok(Some(mocked_response)) }
15958 });
15959
15960 async move {
15961 request.next().await;
15962 }
15963}
15964
15965/// Handle completion request passing a marked string specifying where the completion
15966/// should be triggered from using '|' character, what range should be replaced, and what completions
15967/// should be returned using '<' and '>' to delimit the range
15968pub fn handle_completion_request(
15969 cx: &mut EditorLspTestContext,
15970 marked_string: &str,
15971 completions: Vec<&'static str>,
15972 counter: Arc<AtomicUsize>,
15973) -> impl Future<Output = ()> {
15974 let complete_from_marker: TextRangeMarker = '|'.into();
15975 let replace_range_marker: TextRangeMarker = ('<', '>').into();
15976 let (_, mut marked_ranges) = marked_text_ranges_by(
15977 marked_string,
15978 vec![complete_from_marker.clone(), replace_range_marker.clone()],
15979 );
15980
15981 let complete_from_position =
15982 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
15983 let replace_range =
15984 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
15985
15986 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
15987 let completions = completions.clone();
15988 counter.fetch_add(1, atomic::Ordering::Release);
15989 async move {
15990 assert_eq!(params.text_document_position.text_document.uri, url.clone());
15991 assert_eq!(
15992 params.text_document_position.position,
15993 complete_from_position
15994 );
15995 Ok(Some(lsp::CompletionResponse::Array(
15996 completions
15997 .iter()
15998 .map(|completion_text| lsp::CompletionItem {
15999 label: completion_text.to_string(),
16000 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16001 range: replace_range,
16002 new_text: completion_text.to_string(),
16003 })),
16004 ..Default::default()
16005 })
16006 .collect(),
16007 )))
16008 }
16009 });
16010
16011 async move {
16012 request.next().await;
16013 }
16014}
16015
16016fn handle_resolve_completion_request(
16017 cx: &mut EditorLspTestContext,
16018 edits: Option<Vec<(&'static str, &'static str)>>,
16019) -> impl Future<Output = ()> {
16020 let edits = edits.map(|edits| {
16021 edits
16022 .iter()
16023 .map(|(marked_string, new_text)| {
16024 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16025 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16026 lsp::TextEdit::new(replace_range, new_text.to_string())
16027 })
16028 .collect::<Vec<_>>()
16029 });
16030
16031 let mut request =
16032 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16033 let edits = edits.clone();
16034 async move {
16035 Ok(lsp::CompletionItem {
16036 additional_text_edits: edits,
16037 ..Default::default()
16038 })
16039 }
16040 });
16041
16042 async move {
16043 request.next().await;
16044 }
16045}
16046
16047pub(crate) fn update_test_language_settings(
16048 cx: &mut TestAppContext,
16049 f: impl Fn(&mut AllLanguageSettingsContent),
16050) {
16051 cx.update(|cx| {
16052 SettingsStore::update_global(cx, |store, cx| {
16053 store.update_user_settings::<AllLanguageSettings>(cx, f);
16054 });
16055 });
16056}
16057
16058pub(crate) fn update_test_project_settings(
16059 cx: &mut TestAppContext,
16060 f: impl Fn(&mut ProjectSettings),
16061) {
16062 cx.update(|cx| {
16063 SettingsStore::update_global(cx, |store, cx| {
16064 store.update_user_settings::<ProjectSettings>(cx, f);
16065 });
16066 });
16067}
16068
16069pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16070 cx.update(|cx| {
16071 assets::Assets.load_test_fonts(cx);
16072 let store = SettingsStore::test(cx);
16073 cx.set_global(store);
16074 theme::init(theme::LoadThemes::JustBase, cx);
16075 release_channel::init(SemanticVersion::default(), cx);
16076 client::init_settings(cx);
16077 language::init(cx);
16078 Project::init_settings(cx);
16079 workspace::init_settings(cx);
16080 crate::init(cx);
16081 });
16082
16083 update_test_language_settings(cx, f);
16084}
16085
16086#[track_caller]
16087fn assert_hunk_revert(
16088 not_reverted_text_with_selections: &str,
16089 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
16090 expected_reverted_text_with_selections: &str,
16091 base_text: &str,
16092 cx: &mut EditorLspTestContext,
16093) {
16094 cx.set_state(not_reverted_text_with_selections);
16095 cx.set_diff_base(base_text);
16096 cx.executor().run_until_parked();
16097
16098 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16099 let snapshot = editor.snapshot(window, cx);
16100 let reverted_hunk_statuses = snapshot
16101 .buffer_snapshot
16102 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16103 .map(|hunk| hunk.status())
16104 .collect::<Vec<_>>();
16105
16106 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
16107 reverted_hunk_statuses
16108 });
16109 cx.executor().run_until_parked();
16110 cx.assert_editor_state(expected_reverted_text_with_selections);
16111 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16112}