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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3370 init_test(cx, |_| {});
3371
3372 let mut cx = EditorTestContext::new(cx).await;
3373
3374 let diff_base = r#"
3375 Line 0
3376 Line 1
3377 Line 2
3378 Line 3
3379 "#
3380 .unindent();
3381
3382 cx.set_state(
3383 &r#"
3384 ˇLine 0
3385 Line 1
3386 Line 2
3387 Line 3
3388 "#
3389 .unindent(),
3390 );
3391
3392 cx.set_diff_base(&diff_base);
3393 executor.run_until_parked();
3394
3395 // Join lines
3396 cx.update_editor(|editor, window, cx| {
3397 editor.join_lines(&JoinLines, window, cx);
3398 });
3399 executor.run_until_parked();
3400
3401 cx.assert_editor_state(
3402 &r#"
3403 Line 0ˇ Line 1
3404 Line 2
3405 Line 3
3406 "#
3407 .unindent(),
3408 );
3409 // Join again
3410 cx.update_editor(|editor, window, cx| {
3411 editor.join_lines(&JoinLines, window, cx);
3412 });
3413 executor.run_until_parked();
3414
3415 cx.assert_editor_state(
3416 &r#"
3417 Line 0 Line 1ˇ Line 2
3418 Line 3
3419 "#
3420 .unindent(),
3421 );
3422}
3423
3424#[gpui::test]
3425async fn test_custom_newlines_cause_no_false_positive_diffs(
3426 executor: BackgroundExecutor,
3427 cx: &mut TestAppContext,
3428) {
3429 init_test(cx, |_| {});
3430 let mut cx = EditorTestContext::new(cx).await;
3431 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3432 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3433 executor.run_until_parked();
3434
3435 cx.update_editor(|editor, window, cx| {
3436 let snapshot = editor.snapshot(window, cx);
3437 assert_eq!(
3438 snapshot
3439 .buffer_snapshot
3440 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3441 .collect::<Vec<_>>(),
3442 Vec::new(),
3443 "Should not have any diffs for files with custom newlines"
3444 );
3445 });
3446}
3447
3448#[gpui::test]
3449async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3450 init_test(cx, |_| {});
3451
3452 let mut cx = EditorTestContext::new(cx).await;
3453
3454 // Test sort_lines_case_insensitive()
3455 cx.set_state(indoc! {"
3456 «z
3457 y
3458 x
3459 Z
3460 Y
3461 Xˇ»
3462 "});
3463 cx.update_editor(|e, window, cx| {
3464 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3465 });
3466 cx.assert_editor_state(indoc! {"
3467 «x
3468 X
3469 y
3470 Y
3471 z
3472 Zˇ»
3473 "});
3474
3475 // Test reverse_lines()
3476 cx.set_state(indoc! {"
3477 «5
3478 4
3479 3
3480 2
3481 1ˇ»
3482 "});
3483 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3484 cx.assert_editor_state(indoc! {"
3485 «1
3486 2
3487 3
3488 4
3489 5ˇ»
3490 "});
3491
3492 // Skip testing shuffle_line()
3493
3494 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3495 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3496
3497 // Don't manipulate when cursor is on single line, but expand the selection
3498 cx.set_state(indoc! {"
3499 ddˇdd
3500 ccc
3501 bb
3502 a
3503 "});
3504 cx.update_editor(|e, window, cx| {
3505 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3506 });
3507 cx.assert_editor_state(indoc! {"
3508 «ddddˇ»
3509 ccc
3510 bb
3511 a
3512 "});
3513
3514 // Basic manipulate case
3515 // Start selection moves to column 0
3516 // End of selection shrinks to fit shorter line
3517 cx.set_state(indoc! {"
3518 dd«d
3519 ccc
3520 bb
3521 aaaaaˇ»
3522 "});
3523 cx.update_editor(|e, window, cx| {
3524 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3525 });
3526 cx.assert_editor_state(indoc! {"
3527 «aaaaa
3528 bb
3529 ccc
3530 dddˇ»
3531 "});
3532
3533 // Manipulate case with newlines
3534 cx.set_state(indoc! {"
3535 dd«d
3536 ccc
3537
3538 bb
3539 aaaaa
3540
3541 ˇ»
3542 "});
3543 cx.update_editor(|e, window, cx| {
3544 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3545 });
3546 cx.assert_editor_state(indoc! {"
3547 «
3548
3549 aaaaa
3550 bb
3551 ccc
3552 dddˇ»
3553
3554 "});
3555
3556 // Adding new line
3557 cx.set_state(indoc! {"
3558 aa«a
3559 bbˇ»b
3560 "});
3561 cx.update_editor(|e, window, cx| {
3562 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3563 });
3564 cx.assert_editor_state(indoc! {"
3565 «aaa
3566 bbb
3567 added_lineˇ»
3568 "});
3569
3570 // Removing line
3571 cx.set_state(indoc! {"
3572 aa«a
3573 bbbˇ»
3574 "});
3575 cx.update_editor(|e, window, cx| {
3576 e.manipulate_lines(window, cx, |lines| {
3577 lines.pop();
3578 })
3579 });
3580 cx.assert_editor_state(indoc! {"
3581 «aaaˇ»
3582 "});
3583
3584 // Removing all lines
3585 cx.set_state(indoc! {"
3586 aa«a
3587 bbbˇ»
3588 "});
3589 cx.update_editor(|e, window, cx| {
3590 e.manipulate_lines(window, cx, |lines| {
3591 lines.drain(..);
3592 })
3593 });
3594 cx.assert_editor_state(indoc! {"
3595 ˇ
3596 "});
3597}
3598
3599#[gpui::test]
3600async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3601 init_test(cx, |_| {});
3602
3603 let mut cx = EditorTestContext::new(cx).await;
3604
3605 // Consider continuous selection as single selection
3606 cx.set_state(indoc! {"
3607 Aaa«aa
3608 cˇ»c«c
3609 bb
3610 aaaˇ»aa
3611 "});
3612 cx.update_editor(|e, window, cx| {
3613 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3614 });
3615 cx.assert_editor_state(indoc! {"
3616 «Aaaaa
3617 ccc
3618 bb
3619 aaaaaˇ»
3620 "});
3621
3622 cx.set_state(indoc! {"
3623 Aaa«aa
3624 cˇ»c«c
3625 bb
3626 aaaˇ»aa
3627 "});
3628 cx.update_editor(|e, window, cx| {
3629 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3630 });
3631 cx.assert_editor_state(indoc! {"
3632 «Aaaaa
3633 ccc
3634 bbˇ»
3635 "});
3636
3637 // Consider non continuous selection as distinct dedup operations
3638 cx.set_state(indoc! {"
3639 «aaaaa
3640 bb
3641 aaaaa
3642 aaaaaˇ»
3643
3644 aaa«aaˇ»
3645 "});
3646 cx.update_editor(|e, window, cx| {
3647 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3648 });
3649 cx.assert_editor_state(indoc! {"
3650 «aaaaa
3651 bbˇ»
3652
3653 «aaaaaˇ»
3654 "});
3655}
3656
3657#[gpui::test]
3658async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3659 init_test(cx, |_| {});
3660
3661 let mut cx = EditorTestContext::new(cx).await;
3662
3663 cx.set_state(indoc! {"
3664 «Aaa
3665 aAa
3666 Aaaˇ»
3667 "});
3668 cx.update_editor(|e, window, cx| {
3669 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3670 });
3671 cx.assert_editor_state(indoc! {"
3672 «Aaa
3673 aAaˇ»
3674 "});
3675
3676 cx.set_state(indoc! {"
3677 «Aaa
3678 aAa
3679 aaAˇ»
3680 "});
3681 cx.update_editor(|e, window, cx| {
3682 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3683 });
3684 cx.assert_editor_state(indoc! {"
3685 «Aaaˇ»
3686 "});
3687}
3688
3689#[gpui::test]
3690async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3691 init_test(cx, |_| {});
3692
3693 let mut cx = EditorTestContext::new(cx).await;
3694
3695 // Manipulate with multiple selections on a single line
3696 cx.set_state(indoc! {"
3697 dd«dd
3698 cˇ»c«c
3699 bb
3700 aaaˇ»aa
3701 "});
3702 cx.update_editor(|e, window, cx| {
3703 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3704 });
3705 cx.assert_editor_state(indoc! {"
3706 «aaaaa
3707 bb
3708 ccc
3709 ddddˇ»
3710 "});
3711
3712 // Manipulate with multiple disjoin selections
3713 cx.set_state(indoc! {"
3714 5«
3715 4
3716 3
3717 2
3718 1ˇ»
3719
3720 dd«dd
3721 ccc
3722 bb
3723 aaaˇ»aa
3724 "});
3725 cx.update_editor(|e, window, cx| {
3726 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3727 });
3728 cx.assert_editor_state(indoc! {"
3729 «1
3730 2
3731 3
3732 4
3733 5ˇ»
3734
3735 «aaaaa
3736 bb
3737 ccc
3738 ddddˇ»
3739 "});
3740
3741 // Adding lines on each selection
3742 cx.set_state(indoc! {"
3743 2«
3744 1ˇ»
3745
3746 bb«bb
3747 aaaˇ»aa
3748 "});
3749 cx.update_editor(|e, window, cx| {
3750 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3751 });
3752 cx.assert_editor_state(indoc! {"
3753 «2
3754 1
3755 added lineˇ»
3756
3757 «bbbb
3758 aaaaa
3759 added lineˇ»
3760 "});
3761
3762 // Removing lines on each selection
3763 cx.set_state(indoc! {"
3764 2«
3765 1ˇ»
3766
3767 bb«bb
3768 aaaˇ»aa
3769 "});
3770 cx.update_editor(|e, window, cx| {
3771 e.manipulate_lines(window, cx, |lines| {
3772 lines.pop();
3773 })
3774 });
3775 cx.assert_editor_state(indoc! {"
3776 «2ˇ»
3777
3778 «bbbbˇ»
3779 "});
3780}
3781
3782#[gpui::test]
3783async fn test_manipulate_text(cx: &mut TestAppContext) {
3784 init_test(cx, |_| {});
3785
3786 let mut cx = EditorTestContext::new(cx).await;
3787
3788 // Test convert_to_upper_case()
3789 cx.set_state(indoc! {"
3790 «hello worldˇ»
3791 "});
3792 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3793 cx.assert_editor_state(indoc! {"
3794 «HELLO WORLDˇ»
3795 "});
3796
3797 // Test convert_to_lower_case()
3798 cx.set_state(indoc! {"
3799 «HELLO WORLDˇ»
3800 "});
3801 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3802 cx.assert_editor_state(indoc! {"
3803 «hello worldˇ»
3804 "});
3805
3806 // Test multiple line, single selection case
3807 cx.set_state(indoc! {"
3808 «The quick brown
3809 fox jumps over
3810 the lazy dogˇ»
3811 "});
3812 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3813 cx.assert_editor_state(indoc! {"
3814 «The Quick Brown
3815 Fox Jumps Over
3816 The Lazy Dogˇ»
3817 "});
3818
3819 // Test multiple line, single selection case
3820 cx.set_state(indoc! {"
3821 «The quick brown
3822 fox jumps over
3823 the lazy dogˇ»
3824 "});
3825 cx.update_editor(|e, window, cx| {
3826 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3827 });
3828 cx.assert_editor_state(indoc! {"
3829 «TheQuickBrown
3830 FoxJumpsOver
3831 TheLazyDogˇ»
3832 "});
3833
3834 // From here on out, test more complex cases of manipulate_text()
3835
3836 // Test no selection case - should affect words cursors are in
3837 // Cursor at beginning, middle, and end of word
3838 cx.set_state(indoc! {"
3839 ˇhello big beauˇtiful worldˇ
3840 "});
3841 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3842 cx.assert_editor_state(indoc! {"
3843 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3844 "});
3845
3846 // Test multiple selections on a single line and across multiple lines
3847 cx.set_state(indoc! {"
3848 «Theˇ» quick «brown
3849 foxˇ» jumps «overˇ»
3850 the «lazyˇ» dog
3851 "});
3852 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3853 cx.assert_editor_state(indoc! {"
3854 «THEˇ» quick «BROWN
3855 FOXˇ» jumps «OVERˇ»
3856 the «LAZYˇ» dog
3857 "});
3858
3859 // Test case where text length grows
3860 cx.set_state(indoc! {"
3861 «tschüߡ»
3862 "});
3863 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3864 cx.assert_editor_state(indoc! {"
3865 «TSCHÜSSˇ»
3866 "});
3867
3868 // Test to make sure we don't crash when text shrinks
3869 cx.set_state(indoc! {"
3870 aaa_bbbˇ
3871 "});
3872 cx.update_editor(|e, window, cx| {
3873 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3874 });
3875 cx.assert_editor_state(indoc! {"
3876 «aaaBbbˇ»
3877 "});
3878
3879 // Test to make sure we all aware of the fact that each word can grow and shrink
3880 // Final selections should be aware of this fact
3881 cx.set_state(indoc! {"
3882 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3883 "});
3884 cx.update_editor(|e, window, cx| {
3885 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3886 });
3887 cx.assert_editor_state(indoc! {"
3888 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3889 "});
3890
3891 cx.set_state(indoc! {"
3892 «hElLo, WoRld!ˇ»
3893 "});
3894 cx.update_editor(|e, window, cx| {
3895 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3896 });
3897 cx.assert_editor_state(indoc! {"
3898 «HeLlO, wOrLD!ˇ»
3899 "});
3900}
3901
3902#[gpui::test]
3903fn test_duplicate_line(cx: &mut TestAppContext) {
3904 init_test(cx, |_| {});
3905
3906 let editor = cx.add_window(|window, cx| {
3907 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3908 build_editor(buffer, window, cx)
3909 });
3910 _ = editor.update(cx, |editor, window, cx| {
3911 editor.change_selections(None, window, cx, |s| {
3912 s.select_display_ranges([
3913 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3914 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3915 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3916 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3917 ])
3918 });
3919 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3920 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3921 assert_eq!(
3922 editor.selections.display_ranges(cx),
3923 vec![
3924 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3925 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3926 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3927 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3928 ]
3929 );
3930 });
3931
3932 let editor = cx.add_window(|window, cx| {
3933 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3934 build_editor(buffer, window, cx)
3935 });
3936 _ = editor.update(cx, |editor, window, cx| {
3937 editor.change_selections(None, window, cx, |s| {
3938 s.select_display_ranges([
3939 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3940 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3941 ])
3942 });
3943 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3944 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3945 assert_eq!(
3946 editor.selections.display_ranges(cx),
3947 vec![
3948 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3949 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3950 ]
3951 );
3952 });
3953
3954 // With `move_upwards` the selections stay in place, except for
3955 // the lines inserted above them
3956 let editor = cx.add_window(|window, cx| {
3957 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3958 build_editor(buffer, window, cx)
3959 });
3960 _ = editor.update(cx, |editor, window, cx| {
3961 editor.change_selections(None, window, cx, |s| {
3962 s.select_display_ranges([
3963 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3964 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3965 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3966 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3967 ])
3968 });
3969 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3970 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3971 assert_eq!(
3972 editor.selections.display_ranges(cx),
3973 vec![
3974 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3975 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3976 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3977 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3978 ]
3979 );
3980 });
3981
3982 let editor = cx.add_window(|window, cx| {
3983 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3984 build_editor(buffer, window, cx)
3985 });
3986 _ = editor.update(cx, |editor, window, cx| {
3987 editor.change_selections(None, window, cx, |s| {
3988 s.select_display_ranges([
3989 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3990 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3991 ])
3992 });
3993 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3994 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3995 assert_eq!(
3996 editor.selections.display_ranges(cx),
3997 vec![
3998 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3999 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4000 ]
4001 );
4002 });
4003
4004 let editor = cx.add_window(|window, cx| {
4005 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4006 build_editor(buffer, window, cx)
4007 });
4008 _ = editor.update(cx, |editor, window, cx| {
4009 editor.change_selections(None, window, cx, |s| {
4010 s.select_display_ranges([
4011 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4012 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4013 ])
4014 });
4015 editor.duplicate_selection(&DuplicateSelection, window, cx);
4016 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4017 assert_eq!(
4018 editor.selections.display_ranges(cx),
4019 vec![
4020 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4021 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4022 ]
4023 );
4024 });
4025}
4026
4027#[gpui::test]
4028fn test_move_line_up_down(cx: &mut TestAppContext) {
4029 init_test(cx, |_| {});
4030
4031 let editor = cx.add_window(|window, cx| {
4032 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4033 build_editor(buffer, window, cx)
4034 });
4035 _ = editor.update(cx, |editor, window, cx| {
4036 editor.fold_creases(
4037 vec![
4038 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4039 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4040 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4041 ],
4042 true,
4043 window,
4044 cx,
4045 );
4046 editor.change_selections(None, window, cx, |s| {
4047 s.select_display_ranges([
4048 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4049 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4050 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4051 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4052 ])
4053 });
4054 assert_eq!(
4055 editor.display_text(cx),
4056 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4057 );
4058
4059 editor.move_line_up(&MoveLineUp, window, cx);
4060 assert_eq!(
4061 editor.display_text(cx),
4062 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4063 );
4064 assert_eq!(
4065 editor.selections.display_ranges(cx),
4066 vec![
4067 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4068 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4069 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4070 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4071 ]
4072 );
4073 });
4074
4075 _ = editor.update(cx, |editor, window, cx| {
4076 editor.move_line_down(&MoveLineDown, window, cx);
4077 assert_eq!(
4078 editor.display_text(cx),
4079 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4080 );
4081 assert_eq!(
4082 editor.selections.display_ranges(cx),
4083 vec![
4084 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4085 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4086 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4087 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4088 ]
4089 );
4090 });
4091
4092 _ = editor.update(cx, |editor, window, cx| {
4093 editor.move_line_down(&MoveLineDown, window, cx);
4094 assert_eq!(
4095 editor.display_text(cx),
4096 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4097 );
4098 assert_eq!(
4099 editor.selections.display_ranges(cx),
4100 vec![
4101 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4102 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4103 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4104 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4105 ]
4106 );
4107 });
4108
4109 _ = editor.update(cx, |editor, window, cx| {
4110 editor.move_line_up(&MoveLineUp, window, cx);
4111 assert_eq!(
4112 editor.display_text(cx),
4113 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4114 );
4115 assert_eq!(
4116 editor.selections.display_ranges(cx),
4117 vec![
4118 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4119 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4120 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4121 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4122 ]
4123 );
4124 });
4125}
4126
4127#[gpui::test]
4128fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4129 init_test(cx, |_| {});
4130
4131 let editor = cx.add_window(|window, cx| {
4132 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4133 build_editor(buffer, window, cx)
4134 });
4135 _ = editor.update(cx, |editor, window, cx| {
4136 let snapshot = editor.buffer.read(cx).snapshot(cx);
4137 editor.insert_blocks(
4138 [BlockProperties {
4139 style: BlockStyle::Fixed,
4140 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4141 height: 1,
4142 render: Arc::new(|_| div().into_any()),
4143 priority: 0,
4144 }],
4145 Some(Autoscroll::fit()),
4146 cx,
4147 );
4148 editor.change_selections(None, window, cx, |s| {
4149 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4150 });
4151 editor.move_line_down(&MoveLineDown, window, cx);
4152 });
4153}
4154
4155#[gpui::test]
4156async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4157 init_test(cx, |_| {});
4158
4159 let mut cx = EditorTestContext::new(cx).await;
4160 cx.set_state(
4161 &"
4162 ˇzero
4163 one
4164 two
4165 three
4166 four
4167 five
4168 "
4169 .unindent(),
4170 );
4171
4172 // Create a four-line block that replaces three lines of text.
4173 cx.update_editor(|editor, window, cx| {
4174 let snapshot = editor.snapshot(window, cx);
4175 let snapshot = &snapshot.buffer_snapshot;
4176 let placement = BlockPlacement::Replace(
4177 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4178 );
4179 editor.insert_blocks(
4180 [BlockProperties {
4181 placement,
4182 height: 4,
4183 style: BlockStyle::Sticky,
4184 render: Arc::new(|_| gpui::div().into_any_element()),
4185 priority: 0,
4186 }],
4187 None,
4188 cx,
4189 );
4190 });
4191
4192 // Move down so that the cursor touches the block.
4193 cx.update_editor(|editor, window, cx| {
4194 editor.move_down(&Default::default(), window, cx);
4195 });
4196 cx.assert_editor_state(
4197 &"
4198 zero
4199 «one
4200 two
4201 threeˇ»
4202 four
4203 five
4204 "
4205 .unindent(),
4206 );
4207
4208 // Move down past the block.
4209 cx.update_editor(|editor, window, cx| {
4210 editor.move_down(&Default::default(), window, cx);
4211 });
4212 cx.assert_editor_state(
4213 &"
4214 zero
4215 one
4216 two
4217 three
4218 ˇfour
4219 five
4220 "
4221 .unindent(),
4222 );
4223}
4224
4225#[gpui::test]
4226fn test_transpose(cx: &mut TestAppContext) {
4227 init_test(cx, |_| {});
4228
4229 _ = cx.add_window(|window, cx| {
4230 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4231 editor.set_style(EditorStyle::default(), window, cx);
4232 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4233 editor.transpose(&Default::default(), window, cx);
4234 assert_eq!(editor.text(cx), "bac");
4235 assert_eq!(editor.selections.ranges(cx), [2..2]);
4236
4237 editor.transpose(&Default::default(), window, cx);
4238 assert_eq!(editor.text(cx), "bca");
4239 assert_eq!(editor.selections.ranges(cx), [3..3]);
4240
4241 editor.transpose(&Default::default(), window, cx);
4242 assert_eq!(editor.text(cx), "bac");
4243 assert_eq!(editor.selections.ranges(cx), [3..3]);
4244
4245 editor
4246 });
4247
4248 _ = cx.add_window(|window, cx| {
4249 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4250 editor.set_style(EditorStyle::default(), window, cx);
4251 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4252 editor.transpose(&Default::default(), window, cx);
4253 assert_eq!(editor.text(cx), "acb\nde");
4254 assert_eq!(editor.selections.ranges(cx), [3..3]);
4255
4256 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4257 editor.transpose(&Default::default(), window, cx);
4258 assert_eq!(editor.text(cx), "acbd\ne");
4259 assert_eq!(editor.selections.ranges(cx), [5..5]);
4260
4261 editor.transpose(&Default::default(), window, cx);
4262 assert_eq!(editor.text(cx), "acbde\n");
4263 assert_eq!(editor.selections.ranges(cx), [6..6]);
4264
4265 editor.transpose(&Default::default(), window, cx);
4266 assert_eq!(editor.text(cx), "acbd\ne");
4267 assert_eq!(editor.selections.ranges(cx), [6..6]);
4268
4269 editor
4270 });
4271
4272 _ = cx.add_window(|window, cx| {
4273 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4274 editor.set_style(EditorStyle::default(), window, cx);
4275 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4276 editor.transpose(&Default::default(), window, cx);
4277 assert_eq!(editor.text(cx), "bacd\ne");
4278 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4279
4280 editor.transpose(&Default::default(), window, cx);
4281 assert_eq!(editor.text(cx), "bcade\n");
4282 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4283
4284 editor.transpose(&Default::default(), window, cx);
4285 assert_eq!(editor.text(cx), "bcda\ne");
4286 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4287
4288 editor.transpose(&Default::default(), window, cx);
4289 assert_eq!(editor.text(cx), "bcade\n");
4290 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4291
4292 editor.transpose(&Default::default(), window, cx);
4293 assert_eq!(editor.text(cx), "bcaed\n");
4294 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4295
4296 editor
4297 });
4298
4299 _ = cx.add_window(|window, cx| {
4300 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4301 editor.set_style(EditorStyle::default(), window, cx);
4302 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4303 editor.transpose(&Default::default(), window, cx);
4304 assert_eq!(editor.text(cx), "🏀🍐✋");
4305 assert_eq!(editor.selections.ranges(cx), [8..8]);
4306
4307 editor.transpose(&Default::default(), window, cx);
4308 assert_eq!(editor.text(cx), "🏀✋🍐");
4309 assert_eq!(editor.selections.ranges(cx), [11..11]);
4310
4311 editor.transpose(&Default::default(), window, cx);
4312 assert_eq!(editor.text(cx), "🏀🍐✋");
4313 assert_eq!(editor.selections.ranges(cx), [11..11]);
4314
4315 editor
4316 });
4317}
4318
4319#[gpui::test]
4320async fn test_rewrap(cx: &mut TestAppContext) {
4321 init_test(cx, |settings| {
4322 settings.languages.extend([
4323 (
4324 "Markdown".into(),
4325 LanguageSettingsContent {
4326 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4327 ..Default::default()
4328 },
4329 ),
4330 (
4331 "Plain Text".into(),
4332 LanguageSettingsContent {
4333 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4334 ..Default::default()
4335 },
4336 ),
4337 ])
4338 });
4339
4340 let mut cx = EditorTestContext::new(cx).await;
4341
4342 let language_with_c_comments = Arc::new(Language::new(
4343 LanguageConfig {
4344 line_comments: vec!["// ".into()],
4345 ..LanguageConfig::default()
4346 },
4347 None,
4348 ));
4349 let language_with_pound_comments = Arc::new(Language::new(
4350 LanguageConfig {
4351 line_comments: vec!["# ".into()],
4352 ..LanguageConfig::default()
4353 },
4354 None,
4355 ));
4356 let markdown_language = Arc::new(Language::new(
4357 LanguageConfig {
4358 name: "Markdown".into(),
4359 ..LanguageConfig::default()
4360 },
4361 None,
4362 ));
4363 let language_with_doc_comments = Arc::new(Language::new(
4364 LanguageConfig {
4365 line_comments: vec!["// ".into(), "/// ".into()],
4366 ..LanguageConfig::default()
4367 },
4368 Some(tree_sitter_rust::LANGUAGE.into()),
4369 ));
4370
4371 let plaintext_language = Arc::new(Language::new(
4372 LanguageConfig {
4373 name: "Plain Text".into(),
4374 ..LanguageConfig::default()
4375 },
4376 None,
4377 ));
4378
4379 assert_rewrap(
4380 indoc! {"
4381 // ˇ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.
4382 "},
4383 indoc! {"
4384 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4385 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4386 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4387 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4388 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4389 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4390 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4391 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4392 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4393 // porttitor id. Aliquam id accumsan eros.
4394 "},
4395 language_with_c_comments.clone(),
4396 &mut cx,
4397 );
4398
4399 // Test that rewrapping works inside of a selection
4400 assert_rewrap(
4401 indoc! {"
4402 «// 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.ˇ»
4403 "},
4404 indoc! {"
4405 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4406 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4407 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4408 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4409 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4410 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4411 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4412 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4413 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4414 // porttitor id. Aliquam id accumsan eros.ˇ»
4415 "},
4416 language_with_c_comments.clone(),
4417 &mut cx,
4418 );
4419
4420 // Test that cursors that expand to the same region are collapsed.
4421 assert_rewrap(
4422 indoc! {"
4423 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4424 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4425 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4426 // ˇ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.
4427 "},
4428 indoc! {"
4429 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4430 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4431 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4432 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4433 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4434 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4435 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4436 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4437 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4438 // porttitor id. Aliquam id accumsan eros.
4439 "},
4440 language_with_c_comments.clone(),
4441 &mut cx,
4442 );
4443
4444 // Test that non-contiguous selections are treated separately.
4445 assert_rewrap(
4446 indoc! {"
4447 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4448 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4449 //
4450 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4451 // ˇ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.
4452 "},
4453 indoc! {"
4454 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4455 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4456 // auctor, eu lacinia sapien scelerisque.
4457 //
4458 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4459 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4460 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4461 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4462 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4463 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4464 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4465 "},
4466 language_with_c_comments.clone(),
4467 &mut cx,
4468 );
4469
4470 // Test that different comment prefixes are supported.
4471 assert_rewrap(
4472 indoc! {"
4473 # ˇ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.
4474 "},
4475 indoc! {"
4476 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4477 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4478 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4479 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4480 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4481 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4482 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4483 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4484 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4485 # accumsan eros.
4486 "},
4487 language_with_pound_comments.clone(),
4488 &mut cx,
4489 );
4490
4491 // Test that rewrapping is ignored outside of comments in most languages.
4492 assert_rewrap(
4493 indoc! {"
4494 /// Adds two numbers.
4495 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4496 fn add(a: u32, b: u32) -> u32 {
4497 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ˇ
4498 }
4499 "},
4500 indoc! {"
4501 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4502 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4503 fn add(a: u32, b: u32) -> u32 {
4504 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ˇ
4505 }
4506 "},
4507 language_with_doc_comments.clone(),
4508 &mut cx,
4509 );
4510
4511 // Test that rewrapping works in Markdown and Plain Text languages.
4512 assert_rewrap(
4513 indoc! {"
4514 # Hello
4515
4516 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.
4517 "},
4518 indoc! {"
4519 # Hello
4520
4521 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4522 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4523 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4524 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4525 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4526 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4527 Integer sit amet scelerisque nisi.
4528 "},
4529 markdown_language,
4530 &mut cx,
4531 );
4532
4533 assert_rewrap(
4534 indoc! {"
4535 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.
4536 "},
4537 indoc! {"
4538 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4539 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4540 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4541 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4542 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4543 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4544 Integer sit amet scelerisque nisi.
4545 "},
4546 plaintext_language,
4547 &mut cx,
4548 );
4549
4550 // Test rewrapping unaligned comments in a selection.
4551 assert_rewrap(
4552 indoc! {"
4553 fn foo() {
4554 if true {
4555 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4556 // Praesent semper egestas tellus id dignissim.ˇ»
4557 do_something();
4558 } else {
4559 //
4560 }
4561 }
4562 "},
4563 indoc! {"
4564 fn foo() {
4565 if true {
4566 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4567 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4568 // egestas tellus id dignissim.ˇ»
4569 do_something();
4570 } else {
4571 //
4572 }
4573 }
4574 "},
4575 language_with_doc_comments.clone(),
4576 &mut cx,
4577 );
4578
4579 assert_rewrap(
4580 indoc! {"
4581 fn foo() {
4582 if true {
4583 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4584 // Praesent semper egestas tellus id dignissim.»
4585 do_something();
4586 } else {
4587 //
4588 }
4589
4590 }
4591 "},
4592 indoc! {"
4593 fn foo() {
4594 if true {
4595 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4596 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4597 // egestas tellus id dignissim.»
4598 do_something();
4599 } else {
4600 //
4601 }
4602
4603 }
4604 "},
4605 language_with_doc_comments.clone(),
4606 &mut cx,
4607 );
4608
4609 #[track_caller]
4610 fn assert_rewrap(
4611 unwrapped_text: &str,
4612 wrapped_text: &str,
4613 language: Arc<Language>,
4614 cx: &mut EditorTestContext,
4615 ) {
4616 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4617 cx.set_state(unwrapped_text);
4618 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4619 cx.assert_editor_state(wrapped_text);
4620 }
4621}
4622
4623#[gpui::test]
4624async fn test_clipboard(cx: &mut TestAppContext) {
4625 init_test(cx, |_| {});
4626
4627 let mut cx = EditorTestContext::new(cx).await;
4628
4629 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4630 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4631 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4632
4633 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4634 cx.set_state("two ˇfour ˇsix ˇ");
4635 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4636 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4637
4638 // Paste again but with only two cursors. Since the number of cursors doesn't
4639 // match the number of slices in the clipboard, the entire clipboard text
4640 // is pasted at each cursor.
4641 cx.set_state("ˇtwo one✅ four three six five ˇ");
4642 cx.update_editor(|e, window, cx| {
4643 e.handle_input("( ", window, cx);
4644 e.paste(&Paste, window, cx);
4645 e.handle_input(") ", window, cx);
4646 });
4647 cx.assert_editor_state(
4648 &([
4649 "( one✅ ",
4650 "three ",
4651 "five ) ˇtwo one✅ four three six five ( one✅ ",
4652 "three ",
4653 "five ) ˇ",
4654 ]
4655 .join("\n")),
4656 );
4657
4658 // Cut with three selections, one of which is full-line.
4659 cx.set_state(indoc! {"
4660 1«2ˇ»3
4661 4ˇ567
4662 «8ˇ»9"});
4663 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4664 cx.assert_editor_state(indoc! {"
4665 1ˇ3
4666 ˇ9"});
4667
4668 // Paste with three selections, noticing how the copied selection that was full-line
4669 // gets inserted before the second cursor.
4670 cx.set_state(indoc! {"
4671 1ˇ3
4672 9ˇ
4673 «oˇ»ne"});
4674 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4675 cx.assert_editor_state(indoc! {"
4676 12ˇ3
4677 4567
4678 9ˇ
4679 8ˇne"});
4680
4681 // Copy with a single cursor only, which writes the whole line into the clipboard.
4682 cx.set_state(indoc! {"
4683 The quick brown
4684 fox juˇmps over
4685 the lazy dog"});
4686 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4687 assert_eq!(
4688 cx.read_from_clipboard()
4689 .and_then(|item| item.text().as_deref().map(str::to_string)),
4690 Some("fox jumps over\n".to_string())
4691 );
4692
4693 // Paste with three selections, noticing how the copied full-line selection is inserted
4694 // before the empty selections but replaces the selection that is non-empty.
4695 cx.set_state(indoc! {"
4696 Tˇhe quick brown
4697 «foˇ»x jumps over
4698 tˇhe lazy dog"});
4699 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4700 cx.assert_editor_state(indoc! {"
4701 fox jumps over
4702 Tˇhe quick brown
4703 fox jumps over
4704 ˇx jumps over
4705 fox jumps over
4706 tˇhe lazy dog"});
4707}
4708
4709#[gpui::test]
4710async fn test_paste_multiline(cx: &mut TestAppContext) {
4711 init_test(cx, |_| {});
4712
4713 let mut cx = EditorTestContext::new(cx).await;
4714 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4715
4716 // Cut an indented block, without the leading whitespace.
4717 cx.set_state(indoc! {"
4718 const a: B = (
4719 c(),
4720 «d(
4721 e,
4722 f
4723 )ˇ»
4724 );
4725 "});
4726 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4727 cx.assert_editor_state(indoc! {"
4728 const a: B = (
4729 c(),
4730 ˇ
4731 );
4732 "});
4733
4734 // Paste it at the same position.
4735 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4736 cx.assert_editor_state(indoc! {"
4737 const a: B = (
4738 c(),
4739 d(
4740 e,
4741 f
4742 )ˇ
4743 );
4744 "});
4745
4746 // Paste it at a line with a lower indent level.
4747 cx.set_state(indoc! {"
4748 ˇ
4749 const a: B = (
4750 c(),
4751 );
4752 "});
4753 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4754 cx.assert_editor_state(indoc! {"
4755 d(
4756 e,
4757 f
4758 )ˇ
4759 const a: B = (
4760 c(),
4761 );
4762 "});
4763
4764 // Cut an indented block, with the leading whitespace.
4765 cx.set_state(indoc! {"
4766 const a: B = (
4767 c(),
4768 « d(
4769 e,
4770 f
4771 )
4772 ˇ»);
4773 "});
4774 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4775 cx.assert_editor_state(indoc! {"
4776 const a: B = (
4777 c(),
4778 ˇ);
4779 "});
4780
4781 // Paste it at the same position.
4782 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4783 cx.assert_editor_state(indoc! {"
4784 const a: B = (
4785 c(),
4786 d(
4787 e,
4788 f
4789 )
4790 ˇ);
4791 "});
4792
4793 // Paste it at a line with a higher indent level.
4794 cx.set_state(indoc! {"
4795 const a: B = (
4796 c(),
4797 d(
4798 e,
4799 fˇ
4800 )
4801 );
4802 "});
4803 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4804 cx.assert_editor_state(indoc! {"
4805 const a: B = (
4806 c(),
4807 d(
4808 e,
4809 f d(
4810 e,
4811 f
4812 )
4813 ˇ
4814 )
4815 );
4816 "});
4817}
4818
4819#[gpui::test]
4820async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4821 init_test(cx, |_| {});
4822
4823 cx.write_to_clipboard(ClipboardItem::new_string(
4824 " d(\n e\n );\n".into(),
4825 ));
4826
4827 let mut cx = EditorTestContext::new(cx).await;
4828 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4829
4830 cx.set_state(indoc! {"
4831 fn a() {
4832 b();
4833 if c() {
4834 ˇ
4835 }
4836 }
4837 "});
4838
4839 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4840 cx.assert_editor_state(indoc! {"
4841 fn a() {
4842 b();
4843 if c() {
4844 d(
4845 e
4846 );
4847 ˇ
4848 }
4849 }
4850 "});
4851
4852 cx.set_state(indoc! {"
4853 fn a() {
4854 b();
4855 ˇ
4856 }
4857 "});
4858
4859 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4860 cx.assert_editor_state(indoc! {"
4861 fn a() {
4862 b();
4863 d(
4864 e
4865 );
4866 ˇ
4867 }
4868 "});
4869}
4870
4871#[gpui::test]
4872fn test_select_all(cx: &mut TestAppContext) {
4873 init_test(cx, |_| {});
4874
4875 let editor = cx.add_window(|window, cx| {
4876 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4877 build_editor(buffer, window, cx)
4878 });
4879 _ = editor.update(cx, |editor, window, cx| {
4880 editor.select_all(&SelectAll, window, cx);
4881 assert_eq!(
4882 editor.selections.display_ranges(cx),
4883 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4884 );
4885 });
4886}
4887
4888#[gpui::test]
4889fn test_select_line(cx: &mut TestAppContext) {
4890 init_test(cx, |_| {});
4891
4892 let editor = cx.add_window(|window, cx| {
4893 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4894 build_editor(buffer, window, cx)
4895 });
4896 _ = editor.update(cx, |editor, window, cx| {
4897 editor.change_selections(None, window, cx, |s| {
4898 s.select_display_ranges([
4899 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4900 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4901 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4902 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4903 ])
4904 });
4905 editor.select_line(&SelectLine, window, cx);
4906 assert_eq!(
4907 editor.selections.display_ranges(cx),
4908 vec![
4909 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4910 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4911 ]
4912 );
4913 });
4914
4915 _ = editor.update(cx, |editor, window, cx| {
4916 editor.select_line(&SelectLine, window, cx);
4917 assert_eq!(
4918 editor.selections.display_ranges(cx),
4919 vec![
4920 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4921 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4922 ]
4923 );
4924 });
4925
4926 _ = editor.update(cx, |editor, window, cx| {
4927 editor.select_line(&SelectLine, window, cx);
4928 assert_eq!(
4929 editor.selections.display_ranges(cx),
4930 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4931 );
4932 });
4933}
4934
4935#[gpui::test]
4936async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4937 init_test(cx, |_| {});
4938 let mut cx = EditorTestContext::new(cx).await;
4939
4940 #[track_caller]
4941 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
4942 cx.set_state(initial_state);
4943 cx.update_editor(|e, window, cx| {
4944 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
4945 });
4946 cx.assert_editor_state(expected_state);
4947 }
4948
4949 // Selection starts and ends at the middle of lines, left-to-right
4950 test(
4951 &mut cx,
4952 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
4953 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4954 );
4955 // Same thing, right-to-left
4956 test(
4957 &mut cx,
4958 "aa\nb«b\ncc\ndd\neˇ»e\nff",
4959 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4960 );
4961
4962 // Whole buffer, left-to-right, last line *doesn't* end with newline
4963 test(
4964 &mut cx,
4965 "«ˇaa\nbb\ncc\ndd\nee\nff»",
4966 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4967 );
4968 // Same thing, right-to-left
4969 test(
4970 &mut cx,
4971 "«aa\nbb\ncc\ndd\nee\nffˇ»",
4972 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4973 );
4974
4975 // Whole buffer, left-to-right, last line ends with newline
4976 test(
4977 &mut cx,
4978 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
4979 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4980 );
4981 // Same thing, right-to-left
4982 test(
4983 &mut cx,
4984 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
4985 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4986 );
4987
4988 // Starts at the end of a line, ends at the start of another
4989 test(
4990 &mut cx,
4991 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
4992 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
4993 );
4994}
4995
4996#[gpui::test]
4997async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
4998 init_test(cx, |_| {});
4999
5000 let editor = cx.add_window(|window, cx| {
5001 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5002 build_editor(buffer, window, cx)
5003 });
5004
5005 // setup
5006 _ = editor.update(cx, |editor, window, cx| {
5007 editor.fold_creases(
5008 vec![
5009 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5010 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5011 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5012 ],
5013 true,
5014 window,
5015 cx,
5016 );
5017 assert_eq!(
5018 editor.display_text(cx),
5019 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5020 );
5021 });
5022
5023 _ = editor.update(cx, |editor, window, cx| {
5024 editor.change_selections(None, window, cx, |s| {
5025 s.select_display_ranges([
5026 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5027 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5028 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5029 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5030 ])
5031 });
5032 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5033 assert_eq!(
5034 editor.display_text(cx),
5035 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5036 );
5037 });
5038 EditorTestContext::for_editor(editor, cx)
5039 .await
5040 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5041
5042 _ = editor.update(cx, |editor, window, cx| {
5043 editor.change_selections(None, window, cx, |s| {
5044 s.select_display_ranges([
5045 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5046 ])
5047 });
5048 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5049 assert_eq!(
5050 editor.display_text(cx),
5051 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5052 );
5053 assert_eq!(
5054 editor.selections.display_ranges(cx),
5055 [
5056 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5057 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5058 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5059 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5060 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5061 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5062 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5063 ]
5064 );
5065 });
5066 EditorTestContext::for_editor(editor, cx)
5067 .await
5068 .assert_editor_state(
5069 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5070 );
5071}
5072
5073#[gpui::test]
5074async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5075 init_test(cx, |_| {});
5076
5077 let mut cx = EditorTestContext::new(cx).await;
5078
5079 cx.set_state(indoc!(
5080 r#"abc
5081 defˇghi
5082
5083 jk
5084 nlmo
5085 "#
5086 ));
5087
5088 cx.update_editor(|editor, window, cx| {
5089 editor.add_selection_above(&Default::default(), window, cx);
5090 });
5091
5092 cx.assert_editor_state(indoc!(
5093 r#"abcˇ
5094 defˇghi
5095
5096 jk
5097 nlmo
5098 "#
5099 ));
5100
5101 cx.update_editor(|editor, window, cx| {
5102 editor.add_selection_above(&Default::default(), window, cx);
5103 });
5104
5105 cx.assert_editor_state(indoc!(
5106 r#"abcˇ
5107 defˇghi
5108
5109 jk
5110 nlmo
5111 "#
5112 ));
5113
5114 cx.update_editor(|editor, window, cx| {
5115 editor.add_selection_below(&Default::default(), window, cx);
5116 });
5117
5118 cx.assert_editor_state(indoc!(
5119 r#"abc
5120 defˇghi
5121
5122 jk
5123 nlmo
5124 "#
5125 ));
5126
5127 cx.update_editor(|editor, window, cx| {
5128 editor.undo_selection(&Default::default(), window, cx);
5129 });
5130
5131 cx.assert_editor_state(indoc!(
5132 r#"abcˇ
5133 defˇghi
5134
5135 jk
5136 nlmo
5137 "#
5138 ));
5139
5140 cx.update_editor(|editor, window, cx| {
5141 editor.redo_selection(&Default::default(), window, cx);
5142 });
5143
5144 cx.assert_editor_state(indoc!(
5145 r#"abc
5146 defˇghi
5147
5148 jk
5149 nlmo
5150 "#
5151 ));
5152
5153 cx.update_editor(|editor, window, cx| {
5154 editor.add_selection_below(&Default::default(), window, cx);
5155 });
5156
5157 cx.assert_editor_state(indoc!(
5158 r#"abc
5159 defˇghi
5160
5161 jk
5162 nlmˇo
5163 "#
5164 ));
5165
5166 cx.update_editor(|editor, window, cx| {
5167 editor.add_selection_below(&Default::default(), window, cx);
5168 });
5169
5170 cx.assert_editor_state(indoc!(
5171 r#"abc
5172 defˇghi
5173
5174 jk
5175 nlmˇo
5176 "#
5177 ));
5178
5179 // change selections
5180 cx.set_state(indoc!(
5181 r#"abc
5182 def«ˇg»hi
5183
5184 jk
5185 nlmo
5186 "#
5187 ));
5188
5189 cx.update_editor(|editor, window, cx| {
5190 editor.add_selection_below(&Default::default(), window, cx);
5191 });
5192
5193 cx.assert_editor_state(indoc!(
5194 r#"abc
5195 def«ˇg»hi
5196
5197 jk
5198 nlm«ˇo»
5199 "#
5200 ));
5201
5202 cx.update_editor(|editor, window, cx| {
5203 editor.add_selection_below(&Default::default(), window, cx);
5204 });
5205
5206 cx.assert_editor_state(indoc!(
5207 r#"abc
5208 def«ˇg»hi
5209
5210 jk
5211 nlm«ˇo»
5212 "#
5213 ));
5214
5215 cx.update_editor(|editor, window, cx| {
5216 editor.add_selection_above(&Default::default(), window, cx);
5217 });
5218
5219 cx.assert_editor_state(indoc!(
5220 r#"abc
5221 def«ˇg»hi
5222
5223 jk
5224 nlmo
5225 "#
5226 ));
5227
5228 cx.update_editor(|editor, window, cx| {
5229 editor.add_selection_above(&Default::default(), window, cx);
5230 });
5231
5232 cx.assert_editor_state(indoc!(
5233 r#"abc
5234 def«ˇg»hi
5235
5236 jk
5237 nlmo
5238 "#
5239 ));
5240
5241 // Change selections again
5242 cx.set_state(indoc!(
5243 r#"a«bc
5244 defgˇ»hi
5245
5246 jk
5247 nlmo
5248 "#
5249 ));
5250
5251 cx.update_editor(|editor, window, cx| {
5252 editor.add_selection_below(&Default::default(), window, cx);
5253 });
5254
5255 cx.assert_editor_state(indoc!(
5256 r#"a«bcˇ»
5257 d«efgˇ»hi
5258
5259 j«kˇ»
5260 nlmo
5261 "#
5262 ));
5263
5264 cx.update_editor(|editor, window, cx| {
5265 editor.add_selection_below(&Default::default(), window, cx);
5266 });
5267 cx.assert_editor_state(indoc!(
5268 r#"a«bcˇ»
5269 d«efgˇ»hi
5270
5271 j«kˇ»
5272 n«lmoˇ»
5273 "#
5274 ));
5275 cx.update_editor(|editor, window, cx| {
5276 editor.add_selection_above(&Default::default(), window, cx);
5277 });
5278
5279 cx.assert_editor_state(indoc!(
5280 r#"a«bcˇ»
5281 d«efgˇ»hi
5282
5283 j«kˇ»
5284 nlmo
5285 "#
5286 ));
5287
5288 // Change selections again
5289 cx.set_state(indoc!(
5290 r#"abc
5291 d«ˇefghi
5292
5293 jk
5294 nlm»o
5295 "#
5296 ));
5297
5298 cx.update_editor(|editor, window, cx| {
5299 editor.add_selection_above(&Default::default(), window, cx);
5300 });
5301
5302 cx.assert_editor_state(indoc!(
5303 r#"a«ˇbc»
5304 d«ˇef»ghi
5305
5306 j«ˇk»
5307 n«ˇlm»o
5308 "#
5309 ));
5310
5311 cx.update_editor(|editor, window, cx| {
5312 editor.add_selection_below(&Default::default(), window, cx);
5313 });
5314
5315 cx.assert_editor_state(indoc!(
5316 r#"abc
5317 d«ˇef»ghi
5318
5319 j«ˇk»
5320 n«ˇlm»o
5321 "#
5322 ));
5323}
5324
5325#[gpui::test]
5326async fn test_select_next(cx: &mut TestAppContext) {
5327 init_test(cx, |_| {});
5328
5329 let mut cx = EditorTestContext::new(cx).await;
5330 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5331
5332 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5333 .unwrap();
5334 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5335
5336 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5337 .unwrap();
5338 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5339
5340 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5341 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5342
5343 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5344 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5345
5346 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5347 .unwrap();
5348 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5349
5350 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5351 .unwrap();
5352 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5353}
5354
5355#[gpui::test]
5356async fn test_select_all_matches(cx: &mut TestAppContext) {
5357 init_test(cx, |_| {});
5358
5359 let mut cx = EditorTestContext::new(cx).await;
5360
5361 // Test caret-only selections
5362 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5363 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5364 .unwrap();
5365 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5366
5367 // Test left-to-right selections
5368 cx.set_state("abc\n«abcˇ»\nabc");
5369 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5370 .unwrap();
5371 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5372
5373 // Test right-to-left selections
5374 cx.set_state("abc\n«ˇabc»\nabc");
5375 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5376 .unwrap();
5377 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5378
5379 // Test selecting whitespace with caret selection
5380 cx.set_state("abc\nˇ abc\nabc");
5381 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5382 .unwrap();
5383 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5384
5385 // Test selecting whitespace with left-to-right selection
5386 cx.set_state("abc\n«ˇ »abc\nabc");
5387 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5388 .unwrap();
5389 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5390
5391 // Test no matches with right-to-left selection
5392 cx.set_state("abc\n« ˇ»abc\nabc");
5393 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5394 .unwrap();
5395 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5396}
5397
5398#[gpui::test]
5399async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5400 init_test(cx, |_| {});
5401
5402 let mut cx = EditorTestContext::new(cx).await;
5403 cx.set_state(
5404 r#"let foo = 2;
5405lˇet foo = 2;
5406let fooˇ = 2;
5407let foo = 2;
5408let foo = ˇ2;"#,
5409 );
5410
5411 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5412 .unwrap();
5413 cx.assert_editor_state(
5414 r#"let foo = 2;
5415«letˇ» foo = 2;
5416let «fooˇ» = 2;
5417let foo = 2;
5418let foo = «2ˇ»;"#,
5419 );
5420
5421 // noop for multiple selections with different contents
5422 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5423 .unwrap();
5424 cx.assert_editor_state(
5425 r#"let foo = 2;
5426«letˇ» foo = 2;
5427let «fooˇ» = 2;
5428let foo = 2;
5429let foo = «2ˇ»;"#,
5430 );
5431}
5432
5433#[gpui::test]
5434async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5435 init_test(cx, |_| {});
5436
5437 let mut cx =
5438 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5439
5440 cx.assert_editor_state(indoc! {"
5441 ˇbbb
5442 ccc
5443
5444 bbb
5445 ccc
5446 "});
5447 cx.dispatch_action(SelectPrevious::default());
5448 cx.assert_editor_state(indoc! {"
5449 «bbbˇ»
5450 ccc
5451
5452 bbb
5453 ccc
5454 "});
5455 cx.dispatch_action(SelectPrevious::default());
5456 cx.assert_editor_state(indoc! {"
5457 «bbbˇ»
5458 ccc
5459
5460 «bbbˇ»
5461 ccc
5462 "});
5463}
5464
5465#[gpui::test]
5466async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5467 init_test(cx, |_| {});
5468
5469 let mut cx = EditorTestContext::new(cx).await;
5470 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5471
5472 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5473 .unwrap();
5474 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5475
5476 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5477 .unwrap();
5478 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5479
5480 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5481 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5482
5483 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5484 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5485
5486 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5487 .unwrap();
5488 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5489
5490 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5491 .unwrap();
5492 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5493
5494 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5495 .unwrap();
5496 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5497}
5498
5499#[gpui::test]
5500async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5501 init_test(cx, |_| {});
5502
5503 let mut cx = EditorTestContext::new(cx).await;
5504 cx.set_state("aˇ");
5505
5506 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5507 .unwrap();
5508 cx.assert_editor_state("«aˇ»");
5509 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5510 .unwrap();
5511 cx.assert_editor_state("«aˇ»");
5512}
5513
5514#[gpui::test]
5515async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5516 init_test(cx, |_| {});
5517
5518 let mut cx = EditorTestContext::new(cx).await;
5519 cx.set_state(
5520 r#"let foo = 2;
5521lˇet foo = 2;
5522let fooˇ = 2;
5523let foo = 2;
5524let foo = ˇ2;"#,
5525 );
5526
5527 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5528 .unwrap();
5529 cx.assert_editor_state(
5530 r#"let foo = 2;
5531«letˇ» foo = 2;
5532let «fooˇ» = 2;
5533let foo = 2;
5534let foo = «2ˇ»;"#,
5535 );
5536
5537 // noop for multiple selections with different contents
5538 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5539 .unwrap();
5540 cx.assert_editor_state(
5541 r#"let foo = 2;
5542«letˇ» foo = 2;
5543let «fooˇ» = 2;
5544let foo = 2;
5545let foo = «2ˇ»;"#,
5546 );
5547}
5548
5549#[gpui::test]
5550async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5551 init_test(cx, |_| {});
5552
5553 let mut cx = EditorTestContext::new(cx).await;
5554 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5555
5556 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5557 .unwrap();
5558 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5559
5560 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5561 .unwrap();
5562 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5563
5564 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5565 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5566
5567 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5568 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5569
5570 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5571 .unwrap();
5572 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5573
5574 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5575 .unwrap();
5576 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5577}
5578
5579#[gpui::test]
5580async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5581 init_test(cx, |_| {});
5582
5583 let language = Arc::new(Language::new(
5584 LanguageConfig::default(),
5585 Some(tree_sitter_rust::LANGUAGE.into()),
5586 ));
5587
5588 let text = r#"
5589 use mod1::mod2::{mod3, mod4};
5590
5591 fn fn_1(param1: bool, param2: &str) {
5592 let var1 = "text";
5593 }
5594 "#
5595 .unindent();
5596
5597 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5598 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5599 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5600
5601 editor
5602 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5603 .await;
5604
5605 editor.update_in(cx, |editor, window, cx| {
5606 editor.change_selections(None, window, cx, |s| {
5607 s.select_display_ranges([
5608 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5609 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5610 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5611 ]);
5612 });
5613 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5614 });
5615 editor.update(cx, |editor, cx| {
5616 assert_text_with_selections(
5617 editor,
5618 indoc! {r#"
5619 use mod1::mod2::{mod3, «mod4ˇ»};
5620
5621 fn fn_1«ˇ(param1: bool, param2: &str)» {
5622 let var1 = "«textˇ»";
5623 }
5624 "#},
5625 cx,
5626 );
5627 });
5628
5629 editor.update_in(cx, |editor, window, cx| {
5630 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5631 });
5632 editor.update(cx, |editor, cx| {
5633 assert_text_with_selections(
5634 editor,
5635 indoc! {r#"
5636 use mod1::mod2::«{mod3, mod4}ˇ»;
5637
5638 «ˇfn fn_1(param1: bool, param2: &str) {
5639 let var1 = "text";
5640 }»
5641 "#},
5642 cx,
5643 );
5644 });
5645
5646 editor.update_in(cx, |editor, window, cx| {
5647 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5648 });
5649 assert_eq!(
5650 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5651 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5652 );
5653
5654 // Trying to expand the selected syntax node one more time has no effect.
5655 editor.update_in(cx, |editor, window, cx| {
5656 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5657 });
5658 assert_eq!(
5659 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5660 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5661 );
5662
5663 editor.update_in(cx, |editor, window, cx| {
5664 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5665 });
5666 editor.update(cx, |editor, cx| {
5667 assert_text_with_selections(
5668 editor,
5669 indoc! {r#"
5670 use mod1::mod2::«{mod3, mod4}ˇ»;
5671
5672 «ˇfn fn_1(param1: bool, param2: &str) {
5673 let var1 = "text";
5674 }»
5675 "#},
5676 cx,
5677 );
5678 });
5679
5680 editor.update_in(cx, |editor, window, cx| {
5681 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5682 });
5683 editor.update(cx, |editor, cx| {
5684 assert_text_with_selections(
5685 editor,
5686 indoc! {r#"
5687 use mod1::mod2::{mod3, «mod4ˇ»};
5688
5689 fn fn_1«ˇ(param1: bool, param2: &str)» {
5690 let var1 = "«textˇ»";
5691 }
5692 "#},
5693 cx,
5694 );
5695 });
5696
5697 editor.update_in(cx, |editor, window, cx| {
5698 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5699 });
5700 editor.update(cx, |editor, cx| {
5701 assert_text_with_selections(
5702 editor,
5703 indoc! {r#"
5704 use mod1::mod2::{mod3, mo«ˇ»d4};
5705
5706 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5707 let var1 = "te«ˇ»xt";
5708 }
5709 "#},
5710 cx,
5711 );
5712 });
5713
5714 // Trying to shrink the selected syntax node one more time has no effect.
5715 editor.update_in(cx, |editor, window, cx| {
5716 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5717 });
5718 editor.update_in(cx, |editor, _, cx| {
5719 assert_text_with_selections(
5720 editor,
5721 indoc! {r#"
5722 use mod1::mod2::{mod3, mo«ˇ»d4};
5723
5724 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5725 let var1 = "te«ˇ»xt";
5726 }
5727 "#},
5728 cx,
5729 );
5730 });
5731
5732 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5733 // a fold.
5734 editor.update_in(cx, |editor, window, cx| {
5735 editor.fold_creases(
5736 vec![
5737 Crease::simple(
5738 Point::new(0, 21)..Point::new(0, 24),
5739 FoldPlaceholder::test(),
5740 ),
5741 Crease::simple(
5742 Point::new(3, 20)..Point::new(3, 22),
5743 FoldPlaceholder::test(),
5744 ),
5745 ],
5746 true,
5747 window,
5748 cx,
5749 );
5750 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5751 });
5752 editor.update(cx, |editor, cx| {
5753 assert_text_with_selections(
5754 editor,
5755 indoc! {r#"
5756 use mod1::mod2::«{mod3, mod4}ˇ»;
5757
5758 fn fn_1«ˇ(param1: bool, param2: &str)» {
5759 «let var1 = "text";ˇ»
5760 }
5761 "#},
5762 cx,
5763 );
5764 });
5765}
5766
5767#[gpui::test]
5768async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5769 init_test(cx, |_| {});
5770
5771 let base_text = r#"
5772 impl A {
5773 // this is an uncommitted comment
5774
5775 fn b() {
5776 c();
5777 }
5778
5779 // this is another uncommitted comment
5780
5781 fn d() {
5782 // e
5783 // f
5784 }
5785 }
5786
5787 fn g() {
5788 // h
5789 }
5790 "#
5791 .unindent();
5792
5793 let text = r#"
5794 ˇimpl A {
5795
5796 fn b() {
5797 c();
5798 }
5799
5800 fn d() {
5801 // e
5802 // f
5803 }
5804 }
5805
5806 fn g() {
5807 // h
5808 }
5809 "#
5810 .unindent();
5811
5812 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5813 cx.set_state(&text);
5814 cx.set_diff_base(&base_text);
5815 cx.update_editor(|editor, window, cx| {
5816 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5817 });
5818
5819 cx.assert_state_with_diff(
5820 "
5821 ˇimpl A {
5822 - // this is an uncommitted comment
5823
5824 fn b() {
5825 c();
5826 }
5827
5828 - // this is another uncommitted comment
5829 -
5830 fn d() {
5831 // e
5832 // f
5833 }
5834 }
5835
5836 fn g() {
5837 // h
5838 }
5839 "
5840 .unindent(),
5841 );
5842
5843 let expected_display_text = "
5844 impl A {
5845 // this is an uncommitted comment
5846
5847 fn b() {
5848 ⋯
5849 }
5850
5851 // this is another uncommitted comment
5852
5853 fn d() {
5854 ⋯
5855 }
5856 }
5857
5858 fn g() {
5859 ⋯
5860 }
5861 "
5862 .unindent();
5863
5864 cx.update_editor(|editor, window, cx| {
5865 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5866 assert_eq!(editor.display_text(cx), expected_display_text);
5867 });
5868}
5869
5870#[gpui::test]
5871async fn test_autoindent(cx: &mut TestAppContext) {
5872 init_test(cx, |_| {});
5873
5874 let language = Arc::new(
5875 Language::new(
5876 LanguageConfig {
5877 brackets: BracketPairConfig {
5878 pairs: vec![
5879 BracketPair {
5880 start: "{".to_string(),
5881 end: "}".to_string(),
5882 close: false,
5883 surround: false,
5884 newline: true,
5885 },
5886 BracketPair {
5887 start: "(".to_string(),
5888 end: ")".to_string(),
5889 close: false,
5890 surround: false,
5891 newline: true,
5892 },
5893 ],
5894 ..Default::default()
5895 },
5896 ..Default::default()
5897 },
5898 Some(tree_sitter_rust::LANGUAGE.into()),
5899 )
5900 .with_indents_query(
5901 r#"
5902 (_ "(" ")" @end) @indent
5903 (_ "{" "}" @end) @indent
5904 "#,
5905 )
5906 .unwrap(),
5907 );
5908
5909 let text = "fn a() {}";
5910
5911 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5912 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5913 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5914 editor
5915 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5916 .await;
5917
5918 editor.update_in(cx, |editor, window, cx| {
5919 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5920 editor.newline(&Newline, window, cx);
5921 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5922 assert_eq!(
5923 editor.selections.ranges(cx),
5924 &[
5925 Point::new(1, 4)..Point::new(1, 4),
5926 Point::new(3, 4)..Point::new(3, 4),
5927 Point::new(5, 0)..Point::new(5, 0)
5928 ]
5929 );
5930 });
5931}
5932
5933#[gpui::test]
5934async fn test_autoindent_selections(cx: &mut TestAppContext) {
5935 init_test(cx, |_| {});
5936
5937 {
5938 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5939 cx.set_state(indoc! {"
5940 impl A {
5941
5942 fn b() {}
5943
5944 «fn c() {
5945
5946 }ˇ»
5947 }
5948 "});
5949
5950 cx.update_editor(|editor, window, cx| {
5951 editor.autoindent(&Default::default(), window, cx);
5952 });
5953
5954 cx.assert_editor_state(indoc! {"
5955 impl A {
5956
5957 fn b() {}
5958
5959 «fn c() {
5960
5961 }ˇ»
5962 }
5963 "});
5964 }
5965
5966 {
5967 let mut cx = EditorTestContext::new_multibuffer(
5968 cx,
5969 [indoc! { "
5970 impl A {
5971 «
5972 // a
5973 fn b(){}
5974 »
5975 «
5976 }
5977 fn c(){}
5978 »
5979 "}],
5980 );
5981
5982 let buffer = cx.update_editor(|editor, _, cx| {
5983 let buffer = editor.buffer().update(cx, |buffer, _| {
5984 buffer.all_buffers().iter().next().unwrap().clone()
5985 });
5986 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5987 buffer
5988 });
5989
5990 cx.run_until_parked();
5991 cx.update_editor(|editor, window, cx| {
5992 editor.select_all(&Default::default(), window, cx);
5993 editor.autoindent(&Default::default(), window, cx)
5994 });
5995 cx.run_until_parked();
5996
5997 cx.update(|_, cx| {
5998 pretty_assertions::assert_eq!(
5999 buffer.read(cx).text(),
6000 indoc! { "
6001 impl A {
6002
6003 // a
6004 fn b(){}
6005
6006
6007 }
6008 fn c(){}
6009
6010 " }
6011 )
6012 });
6013 }
6014}
6015
6016#[gpui::test]
6017async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6018 init_test(cx, |_| {});
6019
6020 let mut cx = EditorTestContext::new(cx).await;
6021
6022 let language = Arc::new(Language::new(
6023 LanguageConfig {
6024 brackets: BracketPairConfig {
6025 pairs: vec![
6026 BracketPair {
6027 start: "{".to_string(),
6028 end: "}".to_string(),
6029 close: true,
6030 surround: true,
6031 newline: true,
6032 },
6033 BracketPair {
6034 start: "(".to_string(),
6035 end: ")".to_string(),
6036 close: true,
6037 surround: true,
6038 newline: true,
6039 },
6040 BracketPair {
6041 start: "/*".to_string(),
6042 end: " */".to_string(),
6043 close: true,
6044 surround: true,
6045 newline: true,
6046 },
6047 BracketPair {
6048 start: "[".to_string(),
6049 end: "]".to_string(),
6050 close: false,
6051 surround: false,
6052 newline: true,
6053 },
6054 BracketPair {
6055 start: "\"".to_string(),
6056 end: "\"".to_string(),
6057 close: true,
6058 surround: true,
6059 newline: false,
6060 },
6061 BracketPair {
6062 start: "<".to_string(),
6063 end: ">".to_string(),
6064 close: false,
6065 surround: true,
6066 newline: true,
6067 },
6068 ],
6069 ..Default::default()
6070 },
6071 autoclose_before: "})]".to_string(),
6072 ..Default::default()
6073 },
6074 Some(tree_sitter_rust::LANGUAGE.into()),
6075 ));
6076
6077 cx.language_registry().add(language.clone());
6078 cx.update_buffer(|buffer, cx| {
6079 buffer.set_language(Some(language), cx);
6080 });
6081
6082 cx.set_state(
6083 &r#"
6084 🏀ˇ
6085 εˇ
6086 ❤️ˇ
6087 "#
6088 .unindent(),
6089 );
6090
6091 // autoclose multiple nested brackets at multiple cursors
6092 cx.update_editor(|editor, window, cx| {
6093 editor.handle_input("{", window, cx);
6094 editor.handle_input("{", window, cx);
6095 editor.handle_input("{", window, cx);
6096 });
6097 cx.assert_editor_state(
6098 &"
6099 🏀{{{ˇ}}}
6100 ε{{{ˇ}}}
6101 ❤️{{{ˇ}}}
6102 "
6103 .unindent(),
6104 );
6105
6106 // insert a different closing bracket
6107 cx.update_editor(|editor, window, cx| {
6108 editor.handle_input(")", window, cx);
6109 });
6110 cx.assert_editor_state(
6111 &"
6112 🏀{{{)ˇ}}}
6113 ε{{{)ˇ}}}
6114 ❤️{{{)ˇ}}}
6115 "
6116 .unindent(),
6117 );
6118
6119 // skip over the auto-closed brackets when typing a closing bracket
6120 cx.update_editor(|editor, window, cx| {
6121 editor.move_right(&MoveRight, window, cx);
6122 editor.handle_input("}", window, cx);
6123 editor.handle_input("}", window, cx);
6124 editor.handle_input("}", window, cx);
6125 });
6126 cx.assert_editor_state(
6127 &"
6128 🏀{{{)}}}}ˇ
6129 ε{{{)}}}}ˇ
6130 ❤️{{{)}}}}ˇ
6131 "
6132 .unindent(),
6133 );
6134
6135 // autoclose multi-character pairs
6136 cx.set_state(
6137 &"
6138 ˇ
6139 ˇ
6140 "
6141 .unindent(),
6142 );
6143 cx.update_editor(|editor, window, cx| {
6144 editor.handle_input("/", window, cx);
6145 editor.handle_input("*", window, cx);
6146 });
6147 cx.assert_editor_state(
6148 &"
6149 /*ˇ */
6150 /*ˇ */
6151 "
6152 .unindent(),
6153 );
6154
6155 // one cursor autocloses a multi-character pair, one cursor
6156 // does not autoclose.
6157 cx.set_state(
6158 &"
6159 /ˇ
6160 ˇ
6161 "
6162 .unindent(),
6163 );
6164 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6165 cx.assert_editor_state(
6166 &"
6167 /*ˇ */
6168 *ˇ
6169 "
6170 .unindent(),
6171 );
6172
6173 // Don't autoclose if the next character isn't whitespace and isn't
6174 // listed in the language's "autoclose_before" section.
6175 cx.set_state("ˇa b");
6176 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6177 cx.assert_editor_state("{ˇa b");
6178
6179 // Don't autoclose if `close` is false for the bracket pair
6180 cx.set_state("ˇ");
6181 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6182 cx.assert_editor_state("[ˇ");
6183
6184 // Surround with brackets if text is selected
6185 cx.set_state("«aˇ» b");
6186 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6187 cx.assert_editor_state("{«aˇ»} b");
6188
6189 // Autclose pair where the start and end characters are the same
6190 cx.set_state("aˇ");
6191 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6192 cx.assert_editor_state("a\"ˇ\"");
6193 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6194 cx.assert_editor_state("a\"\"ˇ");
6195
6196 // Don't autoclose pair if autoclose is disabled
6197 cx.set_state("ˇ");
6198 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6199 cx.assert_editor_state("<ˇ");
6200
6201 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6202 cx.set_state("«aˇ» b");
6203 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6204 cx.assert_editor_state("<«aˇ»> b");
6205}
6206
6207#[gpui::test]
6208async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6209 init_test(cx, |settings| {
6210 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6211 });
6212
6213 let mut cx = EditorTestContext::new(cx).await;
6214
6215 let language = Arc::new(Language::new(
6216 LanguageConfig {
6217 brackets: BracketPairConfig {
6218 pairs: vec![
6219 BracketPair {
6220 start: "{".to_string(),
6221 end: "}".to_string(),
6222 close: true,
6223 surround: true,
6224 newline: true,
6225 },
6226 BracketPair {
6227 start: "(".to_string(),
6228 end: ")".to_string(),
6229 close: true,
6230 surround: true,
6231 newline: true,
6232 },
6233 BracketPair {
6234 start: "[".to_string(),
6235 end: "]".to_string(),
6236 close: false,
6237 surround: false,
6238 newline: true,
6239 },
6240 ],
6241 ..Default::default()
6242 },
6243 autoclose_before: "})]".to_string(),
6244 ..Default::default()
6245 },
6246 Some(tree_sitter_rust::LANGUAGE.into()),
6247 ));
6248
6249 cx.language_registry().add(language.clone());
6250 cx.update_buffer(|buffer, cx| {
6251 buffer.set_language(Some(language), cx);
6252 });
6253
6254 cx.set_state(
6255 &"
6256 ˇ
6257 ˇ
6258 ˇ
6259 "
6260 .unindent(),
6261 );
6262
6263 // ensure only matching closing brackets are skipped over
6264 cx.update_editor(|editor, window, cx| {
6265 editor.handle_input("}", window, cx);
6266 editor.move_left(&MoveLeft, window, cx);
6267 editor.handle_input(")", window, cx);
6268 editor.move_left(&MoveLeft, window, cx);
6269 });
6270 cx.assert_editor_state(
6271 &"
6272 ˇ)}
6273 ˇ)}
6274 ˇ)}
6275 "
6276 .unindent(),
6277 );
6278
6279 // skip-over closing brackets at multiple cursors
6280 cx.update_editor(|editor, window, cx| {
6281 editor.handle_input(")", window, cx);
6282 editor.handle_input("}", window, cx);
6283 });
6284 cx.assert_editor_state(
6285 &"
6286 )}ˇ
6287 )}ˇ
6288 )}ˇ
6289 "
6290 .unindent(),
6291 );
6292
6293 // ignore non-close brackets
6294 cx.update_editor(|editor, window, cx| {
6295 editor.handle_input("]", window, cx);
6296 editor.move_left(&MoveLeft, window, cx);
6297 editor.handle_input("]", window, cx);
6298 });
6299 cx.assert_editor_state(
6300 &"
6301 )}]ˇ]
6302 )}]ˇ]
6303 )}]ˇ]
6304 "
6305 .unindent(),
6306 );
6307}
6308
6309#[gpui::test]
6310async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6311 init_test(cx, |_| {});
6312
6313 let mut cx = EditorTestContext::new(cx).await;
6314
6315 let html_language = Arc::new(
6316 Language::new(
6317 LanguageConfig {
6318 name: "HTML".into(),
6319 brackets: BracketPairConfig {
6320 pairs: vec![
6321 BracketPair {
6322 start: "<".into(),
6323 end: ">".into(),
6324 close: true,
6325 ..Default::default()
6326 },
6327 BracketPair {
6328 start: "{".into(),
6329 end: "}".into(),
6330 close: true,
6331 ..Default::default()
6332 },
6333 BracketPair {
6334 start: "(".into(),
6335 end: ")".into(),
6336 close: true,
6337 ..Default::default()
6338 },
6339 ],
6340 ..Default::default()
6341 },
6342 autoclose_before: "})]>".into(),
6343 ..Default::default()
6344 },
6345 Some(tree_sitter_html::LANGUAGE.into()),
6346 )
6347 .with_injection_query(
6348 r#"
6349 (script_element
6350 (raw_text) @injection.content
6351 (#set! injection.language "javascript"))
6352 "#,
6353 )
6354 .unwrap(),
6355 );
6356
6357 let javascript_language = Arc::new(Language::new(
6358 LanguageConfig {
6359 name: "JavaScript".into(),
6360 brackets: BracketPairConfig {
6361 pairs: vec![
6362 BracketPair {
6363 start: "/*".into(),
6364 end: " */".into(),
6365 close: true,
6366 ..Default::default()
6367 },
6368 BracketPair {
6369 start: "{".into(),
6370 end: "}".into(),
6371 close: true,
6372 ..Default::default()
6373 },
6374 BracketPair {
6375 start: "(".into(),
6376 end: ")".into(),
6377 close: true,
6378 ..Default::default()
6379 },
6380 ],
6381 ..Default::default()
6382 },
6383 autoclose_before: "})]>".into(),
6384 ..Default::default()
6385 },
6386 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6387 ));
6388
6389 cx.language_registry().add(html_language.clone());
6390 cx.language_registry().add(javascript_language.clone());
6391
6392 cx.update_buffer(|buffer, cx| {
6393 buffer.set_language(Some(html_language), cx);
6394 });
6395
6396 cx.set_state(
6397 &r#"
6398 <body>ˇ
6399 <script>
6400 var x = 1;ˇ
6401 </script>
6402 </body>ˇ
6403 "#
6404 .unindent(),
6405 );
6406
6407 // Precondition: different languages are active at different locations.
6408 cx.update_editor(|editor, window, cx| {
6409 let snapshot = editor.snapshot(window, cx);
6410 let cursors = editor.selections.ranges::<usize>(cx);
6411 let languages = cursors
6412 .iter()
6413 .map(|c| snapshot.language_at(c.start).unwrap().name())
6414 .collect::<Vec<_>>();
6415 assert_eq!(
6416 languages,
6417 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6418 );
6419 });
6420
6421 // Angle brackets autoclose in HTML, but not JavaScript.
6422 cx.update_editor(|editor, window, cx| {
6423 editor.handle_input("<", window, cx);
6424 editor.handle_input("a", window, cx);
6425 });
6426 cx.assert_editor_state(
6427 &r#"
6428 <body><aˇ>
6429 <script>
6430 var x = 1;<aˇ
6431 </script>
6432 </body><aˇ>
6433 "#
6434 .unindent(),
6435 );
6436
6437 // Curly braces and parens autoclose in both HTML and JavaScript.
6438 cx.update_editor(|editor, window, cx| {
6439 editor.handle_input(" b=", window, cx);
6440 editor.handle_input("{", window, cx);
6441 editor.handle_input("c", window, cx);
6442 editor.handle_input("(", window, cx);
6443 });
6444 cx.assert_editor_state(
6445 &r#"
6446 <body><a b={c(ˇ)}>
6447 <script>
6448 var x = 1;<a b={c(ˇ)}
6449 </script>
6450 </body><a b={c(ˇ)}>
6451 "#
6452 .unindent(),
6453 );
6454
6455 // Brackets that were already autoclosed are skipped.
6456 cx.update_editor(|editor, window, cx| {
6457 editor.handle_input(")", window, cx);
6458 editor.handle_input("d", window, cx);
6459 editor.handle_input("}", window, cx);
6460 });
6461 cx.assert_editor_state(
6462 &r#"
6463 <body><a b={c()d}ˇ>
6464 <script>
6465 var x = 1;<a b={c()d}ˇ
6466 </script>
6467 </body><a b={c()d}ˇ>
6468 "#
6469 .unindent(),
6470 );
6471 cx.update_editor(|editor, window, cx| {
6472 editor.handle_input(">", window, cx);
6473 });
6474 cx.assert_editor_state(
6475 &r#"
6476 <body><a b={c()d}>ˇ
6477 <script>
6478 var x = 1;<a b={c()d}>ˇ
6479 </script>
6480 </body><a b={c()d}>ˇ
6481 "#
6482 .unindent(),
6483 );
6484
6485 // Reset
6486 cx.set_state(
6487 &r#"
6488 <body>ˇ
6489 <script>
6490 var x = 1;ˇ
6491 </script>
6492 </body>ˇ
6493 "#
6494 .unindent(),
6495 );
6496
6497 cx.update_editor(|editor, window, cx| {
6498 editor.handle_input("<", window, cx);
6499 });
6500 cx.assert_editor_state(
6501 &r#"
6502 <body><ˇ>
6503 <script>
6504 var x = 1;<ˇ
6505 </script>
6506 </body><ˇ>
6507 "#
6508 .unindent(),
6509 );
6510
6511 // When backspacing, the closing angle brackets are removed.
6512 cx.update_editor(|editor, window, cx| {
6513 editor.backspace(&Backspace, window, cx);
6514 });
6515 cx.assert_editor_state(
6516 &r#"
6517 <body>ˇ
6518 <script>
6519 var x = 1;ˇ
6520 </script>
6521 </body>ˇ
6522 "#
6523 .unindent(),
6524 );
6525
6526 // Block comments autoclose in JavaScript, but not HTML.
6527 cx.update_editor(|editor, window, cx| {
6528 editor.handle_input("/", window, cx);
6529 editor.handle_input("*", window, cx);
6530 });
6531 cx.assert_editor_state(
6532 &r#"
6533 <body>/*ˇ
6534 <script>
6535 var x = 1;/*ˇ */
6536 </script>
6537 </body>/*ˇ
6538 "#
6539 .unindent(),
6540 );
6541}
6542
6543#[gpui::test]
6544async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6545 init_test(cx, |_| {});
6546
6547 let mut cx = EditorTestContext::new(cx).await;
6548
6549 let rust_language = Arc::new(
6550 Language::new(
6551 LanguageConfig {
6552 name: "Rust".into(),
6553 brackets: serde_json::from_value(json!([
6554 { "start": "{", "end": "}", "close": true, "newline": true },
6555 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6556 ]))
6557 .unwrap(),
6558 autoclose_before: "})]>".into(),
6559 ..Default::default()
6560 },
6561 Some(tree_sitter_rust::LANGUAGE.into()),
6562 )
6563 .with_override_query("(string_literal) @string")
6564 .unwrap(),
6565 );
6566
6567 cx.language_registry().add(rust_language.clone());
6568 cx.update_buffer(|buffer, cx| {
6569 buffer.set_language(Some(rust_language), cx);
6570 });
6571
6572 cx.set_state(
6573 &r#"
6574 let x = ˇ
6575 "#
6576 .unindent(),
6577 );
6578
6579 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6580 cx.update_editor(|editor, window, cx| {
6581 editor.handle_input("\"", window, cx);
6582 });
6583 cx.assert_editor_state(
6584 &r#"
6585 let x = "ˇ"
6586 "#
6587 .unindent(),
6588 );
6589
6590 // Inserting another quotation mark. The cursor moves across the existing
6591 // automatically-inserted quotation mark.
6592 cx.update_editor(|editor, window, cx| {
6593 editor.handle_input("\"", window, cx);
6594 });
6595 cx.assert_editor_state(
6596 &r#"
6597 let x = ""ˇ
6598 "#
6599 .unindent(),
6600 );
6601
6602 // Reset
6603 cx.set_state(
6604 &r#"
6605 let x = ˇ
6606 "#
6607 .unindent(),
6608 );
6609
6610 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6611 cx.update_editor(|editor, window, cx| {
6612 editor.handle_input("\"", window, cx);
6613 editor.handle_input(" ", window, cx);
6614 editor.move_left(&Default::default(), window, cx);
6615 editor.handle_input("\\", window, cx);
6616 editor.handle_input("\"", window, cx);
6617 });
6618 cx.assert_editor_state(
6619 &r#"
6620 let x = "\"ˇ "
6621 "#
6622 .unindent(),
6623 );
6624
6625 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6626 // mark. Nothing is inserted.
6627 cx.update_editor(|editor, window, cx| {
6628 editor.move_right(&Default::default(), window, cx);
6629 editor.handle_input("\"", window, cx);
6630 });
6631 cx.assert_editor_state(
6632 &r#"
6633 let x = "\" "ˇ
6634 "#
6635 .unindent(),
6636 );
6637}
6638
6639#[gpui::test]
6640async fn test_surround_with_pair(cx: &mut TestAppContext) {
6641 init_test(cx, |_| {});
6642
6643 let language = Arc::new(Language::new(
6644 LanguageConfig {
6645 brackets: BracketPairConfig {
6646 pairs: vec![
6647 BracketPair {
6648 start: "{".to_string(),
6649 end: "}".to_string(),
6650 close: true,
6651 surround: true,
6652 newline: true,
6653 },
6654 BracketPair {
6655 start: "/* ".to_string(),
6656 end: "*/".to_string(),
6657 close: true,
6658 surround: true,
6659 ..Default::default()
6660 },
6661 ],
6662 ..Default::default()
6663 },
6664 ..Default::default()
6665 },
6666 Some(tree_sitter_rust::LANGUAGE.into()),
6667 ));
6668
6669 let text = r#"
6670 a
6671 b
6672 c
6673 "#
6674 .unindent();
6675
6676 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6678 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6679 editor
6680 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6681 .await;
6682
6683 editor.update_in(cx, |editor, window, cx| {
6684 editor.change_selections(None, window, cx, |s| {
6685 s.select_display_ranges([
6686 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6687 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6688 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6689 ])
6690 });
6691
6692 editor.handle_input("{", window, cx);
6693 editor.handle_input("{", window, cx);
6694 editor.handle_input("{", window, cx);
6695 assert_eq!(
6696 editor.text(cx),
6697 "
6698 {{{a}}}
6699 {{{b}}}
6700 {{{c}}}
6701 "
6702 .unindent()
6703 );
6704 assert_eq!(
6705 editor.selections.display_ranges(cx),
6706 [
6707 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6708 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6709 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6710 ]
6711 );
6712
6713 editor.undo(&Undo, window, cx);
6714 editor.undo(&Undo, window, cx);
6715 editor.undo(&Undo, window, cx);
6716 assert_eq!(
6717 editor.text(cx),
6718 "
6719 a
6720 b
6721 c
6722 "
6723 .unindent()
6724 );
6725 assert_eq!(
6726 editor.selections.display_ranges(cx),
6727 [
6728 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6729 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6730 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6731 ]
6732 );
6733
6734 // Ensure inserting the first character of a multi-byte bracket pair
6735 // doesn't surround the selections with the bracket.
6736 editor.handle_input("/", window, cx);
6737 assert_eq!(
6738 editor.text(cx),
6739 "
6740 /
6741 /
6742 /
6743 "
6744 .unindent()
6745 );
6746 assert_eq!(
6747 editor.selections.display_ranges(cx),
6748 [
6749 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6750 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6751 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6752 ]
6753 );
6754
6755 editor.undo(&Undo, window, cx);
6756 assert_eq!(
6757 editor.text(cx),
6758 "
6759 a
6760 b
6761 c
6762 "
6763 .unindent()
6764 );
6765 assert_eq!(
6766 editor.selections.display_ranges(cx),
6767 [
6768 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6769 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6770 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6771 ]
6772 );
6773
6774 // Ensure inserting the last character of a multi-byte bracket pair
6775 // doesn't surround the selections with the bracket.
6776 editor.handle_input("*", window, cx);
6777 assert_eq!(
6778 editor.text(cx),
6779 "
6780 *
6781 *
6782 *
6783 "
6784 .unindent()
6785 );
6786 assert_eq!(
6787 editor.selections.display_ranges(cx),
6788 [
6789 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6790 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6791 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6792 ]
6793 );
6794 });
6795}
6796
6797#[gpui::test]
6798async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6799 init_test(cx, |_| {});
6800
6801 let language = Arc::new(Language::new(
6802 LanguageConfig {
6803 brackets: BracketPairConfig {
6804 pairs: vec![BracketPair {
6805 start: "{".to_string(),
6806 end: "}".to_string(),
6807 close: true,
6808 surround: true,
6809 newline: true,
6810 }],
6811 ..Default::default()
6812 },
6813 autoclose_before: "}".to_string(),
6814 ..Default::default()
6815 },
6816 Some(tree_sitter_rust::LANGUAGE.into()),
6817 ));
6818
6819 let text = r#"
6820 a
6821 b
6822 c
6823 "#
6824 .unindent();
6825
6826 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6827 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6828 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6829 editor
6830 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6831 .await;
6832
6833 editor.update_in(cx, |editor, window, cx| {
6834 editor.change_selections(None, window, cx, |s| {
6835 s.select_ranges([
6836 Point::new(0, 1)..Point::new(0, 1),
6837 Point::new(1, 1)..Point::new(1, 1),
6838 Point::new(2, 1)..Point::new(2, 1),
6839 ])
6840 });
6841
6842 editor.handle_input("{", window, cx);
6843 editor.handle_input("{", window, cx);
6844 editor.handle_input("_", window, cx);
6845 assert_eq!(
6846 editor.text(cx),
6847 "
6848 a{{_}}
6849 b{{_}}
6850 c{{_}}
6851 "
6852 .unindent()
6853 );
6854 assert_eq!(
6855 editor.selections.ranges::<Point>(cx),
6856 [
6857 Point::new(0, 4)..Point::new(0, 4),
6858 Point::new(1, 4)..Point::new(1, 4),
6859 Point::new(2, 4)..Point::new(2, 4)
6860 ]
6861 );
6862
6863 editor.backspace(&Default::default(), window, cx);
6864 editor.backspace(&Default::default(), window, cx);
6865 assert_eq!(
6866 editor.text(cx),
6867 "
6868 a{}
6869 b{}
6870 c{}
6871 "
6872 .unindent()
6873 );
6874 assert_eq!(
6875 editor.selections.ranges::<Point>(cx),
6876 [
6877 Point::new(0, 2)..Point::new(0, 2),
6878 Point::new(1, 2)..Point::new(1, 2),
6879 Point::new(2, 2)..Point::new(2, 2)
6880 ]
6881 );
6882
6883 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6884 assert_eq!(
6885 editor.text(cx),
6886 "
6887 a
6888 b
6889 c
6890 "
6891 .unindent()
6892 );
6893 assert_eq!(
6894 editor.selections.ranges::<Point>(cx),
6895 [
6896 Point::new(0, 1)..Point::new(0, 1),
6897 Point::new(1, 1)..Point::new(1, 1),
6898 Point::new(2, 1)..Point::new(2, 1)
6899 ]
6900 );
6901 });
6902}
6903
6904#[gpui::test]
6905async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
6906 init_test(cx, |settings| {
6907 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6908 });
6909
6910 let mut cx = EditorTestContext::new(cx).await;
6911
6912 let language = Arc::new(Language::new(
6913 LanguageConfig {
6914 brackets: BracketPairConfig {
6915 pairs: vec![
6916 BracketPair {
6917 start: "{".to_string(),
6918 end: "}".to_string(),
6919 close: true,
6920 surround: true,
6921 newline: true,
6922 },
6923 BracketPair {
6924 start: "(".to_string(),
6925 end: ")".to_string(),
6926 close: true,
6927 surround: true,
6928 newline: true,
6929 },
6930 BracketPair {
6931 start: "[".to_string(),
6932 end: "]".to_string(),
6933 close: false,
6934 surround: true,
6935 newline: true,
6936 },
6937 ],
6938 ..Default::default()
6939 },
6940 autoclose_before: "})]".to_string(),
6941 ..Default::default()
6942 },
6943 Some(tree_sitter_rust::LANGUAGE.into()),
6944 ));
6945
6946 cx.language_registry().add(language.clone());
6947 cx.update_buffer(|buffer, cx| {
6948 buffer.set_language(Some(language), cx);
6949 });
6950
6951 cx.set_state(
6952 &"
6953 {(ˇ)}
6954 [[ˇ]]
6955 {(ˇ)}
6956 "
6957 .unindent(),
6958 );
6959
6960 cx.update_editor(|editor, window, cx| {
6961 editor.backspace(&Default::default(), window, cx);
6962 editor.backspace(&Default::default(), window, cx);
6963 });
6964
6965 cx.assert_editor_state(
6966 &"
6967 ˇ
6968 ˇ]]
6969 ˇ
6970 "
6971 .unindent(),
6972 );
6973
6974 cx.update_editor(|editor, window, cx| {
6975 editor.handle_input("{", window, cx);
6976 editor.handle_input("{", window, cx);
6977 editor.move_right(&MoveRight, window, cx);
6978 editor.move_right(&MoveRight, window, cx);
6979 editor.move_left(&MoveLeft, window, cx);
6980 editor.move_left(&MoveLeft, window, cx);
6981 editor.backspace(&Default::default(), window, cx);
6982 });
6983
6984 cx.assert_editor_state(
6985 &"
6986 {ˇ}
6987 {ˇ}]]
6988 {ˇ}
6989 "
6990 .unindent(),
6991 );
6992
6993 cx.update_editor(|editor, window, cx| {
6994 editor.backspace(&Default::default(), window, cx);
6995 });
6996
6997 cx.assert_editor_state(
6998 &"
6999 ˇ
7000 ˇ]]
7001 ˇ
7002 "
7003 .unindent(),
7004 );
7005}
7006
7007#[gpui::test]
7008async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7009 init_test(cx, |_| {});
7010
7011 let language = Arc::new(Language::new(
7012 LanguageConfig::default(),
7013 Some(tree_sitter_rust::LANGUAGE.into()),
7014 ));
7015
7016 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7017 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7018 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7019 editor
7020 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7021 .await;
7022
7023 editor.update_in(cx, |editor, window, cx| {
7024 editor.set_auto_replace_emoji_shortcode(true);
7025
7026 editor.handle_input("Hello ", window, cx);
7027 editor.handle_input(":wave", window, cx);
7028 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7029
7030 editor.handle_input(":", window, cx);
7031 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7032
7033 editor.handle_input(" :smile", window, cx);
7034 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7035
7036 editor.handle_input(":", window, cx);
7037 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7038
7039 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7040 editor.handle_input(":wave", window, cx);
7041 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7042
7043 editor.handle_input(":", window, cx);
7044 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7045
7046 editor.handle_input(":1", window, cx);
7047 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7048
7049 editor.handle_input(":", window, cx);
7050 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7051
7052 // Ensure shortcode does not get replaced when it is part of a word
7053 editor.handle_input(" Test:wave", window, cx);
7054 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7055
7056 editor.handle_input(":", window, cx);
7057 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7058
7059 editor.set_auto_replace_emoji_shortcode(false);
7060
7061 // Ensure shortcode does not get replaced when auto replace is off
7062 editor.handle_input(" :wave", window, cx);
7063 assert_eq!(
7064 editor.text(cx),
7065 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7066 );
7067
7068 editor.handle_input(":", window, cx);
7069 assert_eq!(
7070 editor.text(cx),
7071 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7072 );
7073 });
7074}
7075
7076#[gpui::test]
7077async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7078 init_test(cx, |_| {});
7079
7080 let (text, insertion_ranges) = marked_text_ranges(
7081 indoc! {"
7082 ˇ
7083 "},
7084 false,
7085 );
7086
7087 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7088 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7089
7090 _ = editor.update_in(cx, |editor, window, cx| {
7091 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7092
7093 editor
7094 .insert_snippet(&insertion_ranges, snippet, window, cx)
7095 .unwrap();
7096
7097 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7098 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7099 assert_eq!(editor.text(cx), expected_text);
7100 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7101 }
7102
7103 assert(
7104 editor,
7105 cx,
7106 indoc! {"
7107 type «» =•
7108 "},
7109 );
7110
7111 assert!(editor.context_menu_visible(), "There should be a matches");
7112 });
7113}
7114
7115#[gpui::test]
7116async fn test_snippets(cx: &mut TestAppContext) {
7117 init_test(cx, |_| {});
7118
7119 let (text, insertion_ranges) = marked_text_ranges(
7120 indoc! {"
7121 a.ˇ b
7122 a.ˇ b
7123 a.ˇ b
7124 "},
7125 false,
7126 );
7127
7128 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7129 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7130
7131 editor.update_in(cx, |editor, window, cx| {
7132 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7133
7134 editor
7135 .insert_snippet(&insertion_ranges, snippet, window, cx)
7136 .unwrap();
7137
7138 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7139 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7140 assert_eq!(editor.text(cx), expected_text);
7141 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7142 }
7143
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
7154 // Can't move earlier than the first tab stop
7155 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7156 assert(
7157 editor,
7158 cx,
7159 indoc! {"
7160 a.f(«one», two, «three») b
7161 a.f(«one», two, «three») b
7162 a.f(«one», two, «three») b
7163 "},
7164 );
7165
7166 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7167 assert(
7168 editor,
7169 cx,
7170 indoc! {"
7171 a.f(one, «two», three) b
7172 a.f(one, «two», three) b
7173 a.f(one, «two», three) b
7174 "},
7175 );
7176
7177 editor.move_to_prev_snippet_tabstop(window, cx);
7178 assert(
7179 editor,
7180 cx,
7181 indoc! {"
7182 a.f(«one», two, «three») b
7183 a.f(«one», two, «three») b
7184 a.f(«one», two, «three») b
7185 "},
7186 );
7187
7188 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7189 assert(
7190 editor,
7191 cx,
7192 indoc! {"
7193 a.f(one, «two», three) b
7194 a.f(one, «two», three) b
7195 a.f(one, «two», three) b
7196 "},
7197 );
7198 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7199 assert(
7200 editor,
7201 cx,
7202 indoc! {"
7203 a.f(one, two, three)ˇ b
7204 a.f(one, two, three)ˇ b
7205 a.f(one, two, three)ˇ b
7206 "},
7207 );
7208
7209 // As soon as the last tab stop is reached, snippet state is gone
7210 editor.move_to_prev_snippet_tabstop(window, cx);
7211 assert(
7212 editor,
7213 cx,
7214 indoc! {"
7215 a.f(one, two, three)ˇ b
7216 a.f(one, two, three)ˇ b
7217 a.f(one, two, three)ˇ b
7218 "},
7219 );
7220 });
7221}
7222
7223#[gpui::test]
7224async fn test_document_format_during_save(cx: &mut TestAppContext) {
7225 init_test(cx, |_| {});
7226
7227 let fs = FakeFs::new(cx.executor());
7228 fs.insert_file(path!("/file.rs"), Default::default()).await;
7229
7230 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7231
7232 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7233 language_registry.add(rust_lang());
7234 let mut fake_servers = language_registry.register_fake_lsp(
7235 "Rust",
7236 FakeLspAdapter {
7237 capabilities: lsp::ServerCapabilities {
7238 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7239 ..Default::default()
7240 },
7241 ..Default::default()
7242 },
7243 );
7244
7245 let buffer = project
7246 .update(cx, |project, cx| {
7247 project.open_local_buffer(path!("/file.rs"), cx)
7248 })
7249 .await
7250 .unwrap();
7251
7252 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7253 let (editor, cx) = cx.add_window_view(|window, cx| {
7254 build_editor_with_project(project.clone(), buffer, window, cx)
7255 });
7256 editor.update_in(cx, |editor, window, cx| {
7257 editor.set_text("one\ntwo\nthree\n", window, cx)
7258 });
7259 assert!(cx.read(|cx| editor.is_dirty(cx)));
7260
7261 cx.executor().start_waiting();
7262 let fake_server = fake_servers.next().await.unwrap();
7263
7264 let save = editor
7265 .update_in(cx, |editor, window, cx| {
7266 editor.save(true, project.clone(), window, cx)
7267 })
7268 .unwrap();
7269 fake_server
7270 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7271 assert_eq!(
7272 params.text_document.uri,
7273 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7274 );
7275 assert_eq!(params.options.tab_size, 4);
7276 Ok(Some(vec![lsp::TextEdit::new(
7277 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7278 ", ".to_string(),
7279 )]))
7280 })
7281 .next()
7282 .await;
7283 cx.executor().start_waiting();
7284 save.await;
7285
7286 assert_eq!(
7287 editor.update(cx, |editor, cx| editor.text(cx)),
7288 "one, two\nthree\n"
7289 );
7290 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7291
7292 editor.update_in(cx, |editor, window, cx| {
7293 editor.set_text("one\ntwo\nthree\n", window, cx)
7294 });
7295 assert!(cx.read(|cx| editor.is_dirty(cx)));
7296
7297 // Ensure we can still save even if formatting hangs.
7298 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7299 assert_eq!(
7300 params.text_document.uri,
7301 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7302 );
7303 futures::future::pending::<()>().await;
7304 unreachable!()
7305 });
7306 let save = editor
7307 .update_in(cx, |editor, window, cx| {
7308 editor.save(true, project.clone(), window, cx)
7309 })
7310 .unwrap();
7311 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7312 cx.executor().start_waiting();
7313 save.await;
7314 assert_eq!(
7315 editor.update(cx, |editor, cx| editor.text(cx)),
7316 "one\ntwo\nthree\n"
7317 );
7318 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7319
7320 // For non-dirty buffer, no formatting request should be sent
7321 let save = editor
7322 .update_in(cx, |editor, window, cx| {
7323 editor.save(true, project.clone(), window, cx)
7324 })
7325 .unwrap();
7326 let _pending_format_request = fake_server
7327 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7328 panic!("Should not be invoked on non-dirty buffer");
7329 })
7330 .next();
7331 cx.executor().start_waiting();
7332 save.await;
7333
7334 // Set rust language override and assert overridden tabsize is sent to language server
7335 update_test_language_settings(cx, |settings| {
7336 settings.languages.insert(
7337 "Rust".into(),
7338 LanguageSettingsContent {
7339 tab_size: NonZeroU32::new(8),
7340 ..Default::default()
7341 },
7342 );
7343 });
7344
7345 editor.update_in(cx, |editor, window, cx| {
7346 editor.set_text("somehting_new\n", window, cx)
7347 });
7348 assert!(cx.read(|cx| editor.is_dirty(cx)));
7349 let save = editor
7350 .update_in(cx, |editor, window, cx| {
7351 editor.save(true, project.clone(), window, cx)
7352 })
7353 .unwrap();
7354 fake_server
7355 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7356 assert_eq!(
7357 params.text_document.uri,
7358 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7359 );
7360 assert_eq!(params.options.tab_size, 8);
7361 Ok(Some(vec![]))
7362 })
7363 .next()
7364 .await;
7365 cx.executor().start_waiting();
7366 save.await;
7367}
7368
7369#[gpui::test]
7370async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7371 init_test(cx, |_| {});
7372
7373 let cols = 4;
7374 let rows = 10;
7375 let sample_text_1 = sample_text(rows, cols, 'a');
7376 assert_eq!(
7377 sample_text_1,
7378 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7379 );
7380 let sample_text_2 = sample_text(rows, cols, 'l');
7381 assert_eq!(
7382 sample_text_2,
7383 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7384 );
7385 let sample_text_3 = sample_text(rows, cols, 'v');
7386 assert_eq!(
7387 sample_text_3,
7388 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7389 );
7390
7391 let fs = FakeFs::new(cx.executor());
7392 fs.insert_tree(
7393 path!("/a"),
7394 json!({
7395 "main.rs": sample_text_1,
7396 "other.rs": sample_text_2,
7397 "lib.rs": sample_text_3,
7398 }),
7399 )
7400 .await;
7401
7402 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7403 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7404 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7405
7406 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7407 language_registry.add(rust_lang());
7408 let mut fake_servers = language_registry.register_fake_lsp(
7409 "Rust",
7410 FakeLspAdapter {
7411 capabilities: lsp::ServerCapabilities {
7412 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7413 ..Default::default()
7414 },
7415 ..Default::default()
7416 },
7417 );
7418
7419 let worktree = project.update(cx, |project, cx| {
7420 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7421 assert_eq!(worktrees.len(), 1);
7422 worktrees.pop().unwrap()
7423 });
7424 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7425
7426 let buffer_1 = project
7427 .update(cx, |project, cx| {
7428 project.open_buffer((worktree_id, "main.rs"), cx)
7429 })
7430 .await
7431 .unwrap();
7432 let buffer_2 = project
7433 .update(cx, |project, cx| {
7434 project.open_buffer((worktree_id, "other.rs"), cx)
7435 })
7436 .await
7437 .unwrap();
7438 let buffer_3 = project
7439 .update(cx, |project, cx| {
7440 project.open_buffer((worktree_id, "lib.rs"), cx)
7441 })
7442 .await
7443 .unwrap();
7444
7445 let multi_buffer = cx.new(|cx| {
7446 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7447 multi_buffer.push_excerpts(
7448 buffer_1.clone(),
7449 [
7450 ExcerptRange {
7451 context: Point::new(0, 0)..Point::new(3, 0),
7452 primary: None,
7453 },
7454 ExcerptRange {
7455 context: Point::new(5, 0)..Point::new(7, 0),
7456 primary: None,
7457 },
7458 ExcerptRange {
7459 context: Point::new(9, 0)..Point::new(10, 4),
7460 primary: None,
7461 },
7462 ],
7463 cx,
7464 );
7465 multi_buffer.push_excerpts(
7466 buffer_2.clone(),
7467 [
7468 ExcerptRange {
7469 context: Point::new(0, 0)..Point::new(3, 0),
7470 primary: None,
7471 },
7472 ExcerptRange {
7473 context: Point::new(5, 0)..Point::new(7, 0),
7474 primary: None,
7475 },
7476 ExcerptRange {
7477 context: Point::new(9, 0)..Point::new(10, 4),
7478 primary: None,
7479 },
7480 ],
7481 cx,
7482 );
7483 multi_buffer.push_excerpts(
7484 buffer_3.clone(),
7485 [
7486 ExcerptRange {
7487 context: Point::new(0, 0)..Point::new(3, 0),
7488 primary: None,
7489 },
7490 ExcerptRange {
7491 context: Point::new(5, 0)..Point::new(7, 0),
7492 primary: None,
7493 },
7494 ExcerptRange {
7495 context: Point::new(9, 0)..Point::new(10, 4),
7496 primary: None,
7497 },
7498 ],
7499 cx,
7500 );
7501 multi_buffer
7502 });
7503 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7504 Editor::new(
7505 EditorMode::Full,
7506 multi_buffer,
7507 Some(project.clone()),
7508 true,
7509 window,
7510 cx,
7511 )
7512 });
7513
7514 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7515 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7516 s.select_ranges(Some(1..2))
7517 });
7518 editor.insert("|one|two|three|", window, cx);
7519 });
7520 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7521 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7522 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7523 s.select_ranges(Some(60..70))
7524 });
7525 editor.insert("|four|five|six|", window, cx);
7526 });
7527 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7528
7529 // First two buffers should be edited, but not the third one.
7530 assert_eq!(
7531 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7532 "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}",
7533 );
7534 buffer_1.update(cx, |buffer, _| {
7535 assert!(buffer.is_dirty());
7536 assert_eq!(
7537 buffer.text(),
7538 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7539 )
7540 });
7541 buffer_2.update(cx, |buffer, _| {
7542 assert!(buffer.is_dirty());
7543 assert_eq!(
7544 buffer.text(),
7545 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7546 )
7547 });
7548 buffer_3.update(cx, |buffer, _| {
7549 assert!(!buffer.is_dirty());
7550 assert_eq!(buffer.text(), sample_text_3,)
7551 });
7552 cx.executor().run_until_parked();
7553
7554 cx.executor().start_waiting();
7555 let save = multi_buffer_editor
7556 .update_in(cx, |editor, window, cx| {
7557 editor.save(true, project.clone(), window, cx)
7558 })
7559 .unwrap();
7560
7561 let fake_server = fake_servers.next().await.unwrap();
7562 fake_server
7563 .server
7564 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7565 Ok(Some(vec![lsp::TextEdit::new(
7566 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7567 format!("[{} formatted]", params.text_document.uri),
7568 )]))
7569 })
7570 .detach();
7571 save.await;
7572
7573 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7574 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7575 assert_eq!(
7576 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7577 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}"),
7578 );
7579 buffer_1.update(cx, |buffer, _| {
7580 assert!(!buffer.is_dirty());
7581 assert_eq!(
7582 buffer.text(),
7583 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7584 )
7585 });
7586 buffer_2.update(cx, |buffer, _| {
7587 assert!(!buffer.is_dirty());
7588 assert_eq!(
7589 buffer.text(),
7590 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7591 )
7592 });
7593 buffer_3.update(cx, |buffer, _| {
7594 assert!(!buffer.is_dirty());
7595 assert_eq!(buffer.text(), sample_text_3,)
7596 });
7597}
7598
7599#[gpui::test]
7600async fn test_range_format_during_save(cx: &mut TestAppContext) {
7601 init_test(cx, |_| {});
7602
7603 let fs = FakeFs::new(cx.executor());
7604 fs.insert_file(path!("/file.rs"), Default::default()).await;
7605
7606 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7607
7608 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7609 language_registry.add(rust_lang());
7610 let mut fake_servers = language_registry.register_fake_lsp(
7611 "Rust",
7612 FakeLspAdapter {
7613 capabilities: lsp::ServerCapabilities {
7614 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7615 ..Default::default()
7616 },
7617 ..Default::default()
7618 },
7619 );
7620
7621 let buffer = project
7622 .update(cx, |project, cx| {
7623 project.open_local_buffer(path!("/file.rs"), cx)
7624 })
7625 .await
7626 .unwrap();
7627
7628 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7629 let (editor, cx) = cx.add_window_view(|window, cx| {
7630 build_editor_with_project(project.clone(), buffer, window, cx)
7631 });
7632 editor.update_in(cx, |editor, window, cx| {
7633 editor.set_text("one\ntwo\nthree\n", window, cx)
7634 });
7635 assert!(cx.read(|cx| editor.is_dirty(cx)));
7636
7637 cx.executor().start_waiting();
7638 let fake_server = fake_servers.next().await.unwrap();
7639
7640 let save = editor
7641 .update_in(cx, |editor, window, cx| {
7642 editor.save(true, project.clone(), window, cx)
7643 })
7644 .unwrap();
7645 fake_server
7646 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7647 assert_eq!(
7648 params.text_document.uri,
7649 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7650 );
7651 assert_eq!(params.options.tab_size, 4);
7652 Ok(Some(vec![lsp::TextEdit::new(
7653 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7654 ", ".to_string(),
7655 )]))
7656 })
7657 .next()
7658 .await;
7659 cx.executor().start_waiting();
7660 save.await;
7661 assert_eq!(
7662 editor.update(cx, |editor, cx| editor.text(cx)),
7663 "one, two\nthree\n"
7664 );
7665 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7666
7667 editor.update_in(cx, |editor, window, cx| {
7668 editor.set_text("one\ntwo\nthree\n", window, cx)
7669 });
7670 assert!(cx.read(|cx| editor.is_dirty(cx)));
7671
7672 // Ensure we can still save even if formatting hangs.
7673 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7674 move |params, _| async move {
7675 assert_eq!(
7676 params.text_document.uri,
7677 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7678 );
7679 futures::future::pending::<()>().await;
7680 unreachable!()
7681 },
7682 );
7683 let save = editor
7684 .update_in(cx, |editor, window, cx| {
7685 editor.save(true, project.clone(), window, cx)
7686 })
7687 .unwrap();
7688 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7689 cx.executor().start_waiting();
7690 save.await;
7691 assert_eq!(
7692 editor.update(cx, |editor, cx| editor.text(cx)),
7693 "one\ntwo\nthree\n"
7694 );
7695 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7696
7697 // For non-dirty buffer, no formatting request should be sent
7698 let save = editor
7699 .update_in(cx, |editor, window, cx| {
7700 editor.save(true, project.clone(), window, cx)
7701 })
7702 .unwrap();
7703 let _pending_format_request = fake_server
7704 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7705 panic!("Should not be invoked on non-dirty buffer");
7706 })
7707 .next();
7708 cx.executor().start_waiting();
7709 save.await;
7710
7711 // Set Rust language override and assert overridden tabsize is sent to language server
7712 update_test_language_settings(cx, |settings| {
7713 settings.languages.insert(
7714 "Rust".into(),
7715 LanguageSettingsContent {
7716 tab_size: NonZeroU32::new(8),
7717 ..Default::default()
7718 },
7719 );
7720 });
7721
7722 editor.update_in(cx, |editor, window, cx| {
7723 editor.set_text("somehting_new\n", window, cx)
7724 });
7725 assert!(cx.read(|cx| editor.is_dirty(cx)));
7726 let save = editor
7727 .update_in(cx, |editor, window, cx| {
7728 editor.save(true, project.clone(), window, cx)
7729 })
7730 .unwrap();
7731 fake_server
7732 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7733 assert_eq!(
7734 params.text_document.uri,
7735 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7736 );
7737 assert_eq!(params.options.tab_size, 8);
7738 Ok(Some(vec![]))
7739 })
7740 .next()
7741 .await;
7742 cx.executor().start_waiting();
7743 save.await;
7744}
7745
7746#[gpui::test]
7747async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7748 init_test(cx, |settings| {
7749 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7750 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7751 ))
7752 });
7753
7754 let fs = FakeFs::new(cx.executor());
7755 fs.insert_file(path!("/file.rs"), Default::default()).await;
7756
7757 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7758
7759 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7760 language_registry.add(Arc::new(Language::new(
7761 LanguageConfig {
7762 name: "Rust".into(),
7763 matcher: LanguageMatcher {
7764 path_suffixes: vec!["rs".to_string()],
7765 ..Default::default()
7766 },
7767 ..LanguageConfig::default()
7768 },
7769 Some(tree_sitter_rust::LANGUAGE.into()),
7770 )));
7771 update_test_language_settings(cx, |settings| {
7772 // Enable Prettier formatting for the same buffer, and ensure
7773 // LSP is called instead of Prettier.
7774 settings.defaults.prettier = Some(PrettierSettings {
7775 allowed: true,
7776 ..PrettierSettings::default()
7777 });
7778 });
7779 let mut fake_servers = language_registry.register_fake_lsp(
7780 "Rust",
7781 FakeLspAdapter {
7782 capabilities: lsp::ServerCapabilities {
7783 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7784 ..Default::default()
7785 },
7786 ..Default::default()
7787 },
7788 );
7789
7790 let buffer = project
7791 .update(cx, |project, cx| {
7792 project.open_local_buffer(path!("/file.rs"), cx)
7793 })
7794 .await
7795 .unwrap();
7796
7797 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7798 let (editor, cx) = cx.add_window_view(|window, cx| {
7799 build_editor_with_project(project.clone(), buffer, window, cx)
7800 });
7801 editor.update_in(cx, |editor, window, cx| {
7802 editor.set_text("one\ntwo\nthree\n", window, cx)
7803 });
7804
7805 cx.executor().start_waiting();
7806 let fake_server = fake_servers.next().await.unwrap();
7807
7808 let format = editor
7809 .update_in(cx, |editor, window, cx| {
7810 editor.perform_format(
7811 project.clone(),
7812 FormatTrigger::Manual,
7813 FormatTarget::Buffers,
7814 window,
7815 cx,
7816 )
7817 })
7818 .unwrap();
7819 fake_server
7820 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7821 assert_eq!(
7822 params.text_document.uri,
7823 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7824 );
7825 assert_eq!(params.options.tab_size, 4);
7826 Ok(Some(vec![lsp::TextEdit::new(
7827 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7828 ", ".to_string(),
7829 )]))
7830 })
7831 .next()
7832 .await;
7833 cx.executor().start_waiting();
7834 format.await;
7835 assert_eq!(
7836 editor.update(cx, |editor, cx| editor.text(cx)),
7837 "one, two\nthree\n"
7838 );
7839
7840 editor.update_in(cx, |editor, window, cx| {
7841 editor.set_text("one\ntwo\nthree\n", window, cx)
7842 });
7843 // Ensure we don't lock if formatting hangs.
7844 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7845 assert_eq!(
7846 params.text_document.uri,
7847 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7848 );
7849 futures::future::pending::<()>().await;
7850 unreachable!()
7851 });
7852 let format = editor
7853 .update_in(cx, |editor, window, cx| {
7854 editor.perform_format(
7855 project,
7856 FormatTrigger::Manual,
7857 FormatTarget::Buffers,
7858 window,
7859 cx,
7860 )
7861 })
7862 .unwrap();
7863 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7864 cx.executor().start_waiting();
7865 format.await;
7866 assert_eq!(
7867 editor.update(cx, |editor, cx| editor.text(cx)),
7868 "one\ntwo\nthree\n"
7869 );
7870}
7871
7872#[gpui::test]
7873async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
7874 init_test(cx, |_| {});
7875
7876 let mut cx = EditorLspTestContext::new_rust(
7877 lsp::ServerCapabilities {
7878 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7879 ..Default::default()
7880 },
7881 cx,
7882 )
7883 .await;
7884
7885 cx.set_state(indoc! {"
7886 one.twoˇ
7887 "});
7888
7889 // The format request takes a long time. When it completes, it inserts
7890 // a newline and an indent before the `.`
7891 cx.lsp
7892 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7893 let executor = cx.background_executor().clone();
7894 async move {
7895 executor.timer(Duration::from_millis(100)).await;
7896 Ok(Some(vec![lsp::TextEdit {
7897 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7898 new_text: "\n ".into(),
7899 }]))
7900 }
7901 });
7902
7903 // Submit a format request.
7904 let format_1 = cx
7905 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7906 .unwrap();
7907 cx.executor().run_until_parked();
7908
7909 // Submit a second format request.
7910 let format_2 = cx
7911 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7912 .unwrap();
7913 cx.executor().run_until_parked();
7914
7915 // Wait for both format requests to complete
7916 cx.executor().advance_clock(Duration::from_millis(200));
7917 cx.executor().start_waiting();
7918 format_1.await.unwrap();
7919 cx.executor().start_waiting();
7920 format_2.await.unwrap();
7921
7922 // The formatting edits only happens once.
7923 cx.assert_editor_state(indoc! {"
7924 one
7925 .twoˇ
7926 "});
7927}
7928
7929#[gpui::test]
7930async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
7931 init_test(cx, |settings| {
7932 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7933 });
7934
7935 let mut cx = EditorLspTestContext::new_rust(
7936 lsp::ServerCapabilities {
7937 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7938 ..Default::default()
7939 },
7940 cx,
7941 )
7942 .await;
7943
7944 // Set up a buffer white some trailing whitespace and no trailing newline.
7945 cx.set_state(
7946 &[
7947 "one ", //
7948 "twoˇ", //
7949 "three ", //
7950 "four", //
7951 ]
7952 .join("\n"),
7953 );
7954
7955 // Submit a format request.
7956 let format = cx
7957 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7958 .unwrap();
7959
7960 // Record which buffer changes have been sent to the language server
7961 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7962 cx.lsp
7963 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7964 let buffer_changes = buffer_changes.clone();
7965 move |params, _| {
7966 buffer_changes.lock().extend(
7967 params
7968 .content_changes
7969 .into_iter()
7970 .map(|e| (e.range.unwrap(), e.text)),
7971 );
7972 }
7973 });
7974
7975 // Handle formatting requests to the language server.
7976 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7977 let buffer_changes = buffer_changes.clone();
7978 move |_, _| {
7979 // When formatting is requested, trailing whitespace has already been stripped,
7980 // and the trailing newline has already been added.
7981 assert_eq!(
7982 &buffer_changes.lock()[1..],
7983 &[
7984 (
7985 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7986 "".into()
7987 ),
7988 (
7989 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7990 "".into()
7991 ),
7992 (
7993 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7994 "\n".into()
7995 ),
7996 ]
7997 );
7998
7999 // Insert blank lines between each line of the buffer.
8000 async move {
8001 Ok(Some(vec![
8002 lsp::TextEdit {
8003 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8004 new_text: "\n".into(),
8005 },
8006 lsp::TextEdit {
8007 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8008 new_text: "\n".into(),
8009 },
8010 ]))
8011 }
8012 }
8013 });
8014
8015 // After formatting the buffer, the trailing whitespace is stripped,
8016 // a newline is appended, and the edits provided by the language server
8017 // have been applied.
8018 format.await.unwrap();
8019 cx.assert_editor_state(
8020 &[
8021 "one", //
8022 "", //
8023 "twoˇ", //
8024 "", //
8025 "three", //
8026 "four", //
8027 "", //
8028 ]
8029 .join("\n"),
8030 );
8031
8032 // Undoing the formatting undoes the trailing whitespace removal, the
8033 // trailing newline, and the LSP edits.
8034 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8035 cx.assert_editor_state(
8036 &[
8037 "one ", //
8038 "twoˇ", //
8039 "three ", //
8040 "four", //
8041 ]
8042 .join("\n"),
8043 );
8044}
8045
8046#[gpui::test]
8047async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8048 cx: &mut TestAppContext,
8049) {
8050 init_test(cx, |_| {});
8051
8052 cx.update(|cx| {
8053 cx.update_global::<SettingsStore, _>(|settings, cx| {
8054 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8055 settings.auto_signature_help = Some(true);
8056 });
8057 });
8058 });
8059
8060 let mut cx = EditorLspTestContext::new_rust(
8061 lsp::ServerCapabilities {
8062 signature_help_provider: Some(lsp::SignatureHelpOptions {
8063 ..Default::default()
8064 }),
8065 ..Default::default()
8066 },
8067 cx,
8068 )
8069 .await;
8070
8071 let language = Language::new(
8072 LanguageConfig {
8073 name: "Rust".into(),
8074 brackets: BracketPairConfig {
8075 pairs: vec![
8076 BracketPair {
8077 start: "{".to_string(),
8078 end: "}".to_string(),
8079 close: true,
8080 surround: true,
8081 newline: true,
8082 },
8083 BracketPair {
8084 start: "(".to_string(),
8085 end: ")".to_string(),
8086 close: true,
8087 surround: true,
8088 newline: true,
8089 },
8090 BracketPair {
8091 start: "/*".to_string(),
8092 end: " */".to_string(),
8093 close: true,
8094 surround: true,
8095 newline: true,
8096 },
8097 BracketPair {
8098 start: "[".to_string(),
8099 end: "]".to_string(),
8100 close: false,
8101 surround: false,
8102 newline: true,
8103 },
8104 BracketPair {
8105 start: "\"".to_string(),
8106 end: "\"".to_string(),
8107 close: true,
8108 surround: true,
8109 newline: false,
8110 },
8111 BracketPair {
8112 start: "<".to_string(),
8113 end: ">".to_string(),
8114 close: false,
8115 surround: true,
8116 newline: true,
8117 },
8118 ],
8119 ..Default::default()
8120 },
8121 autoclose_before: "})]".to_string(),
8122 ..Default::default()
8123 },
8124 Some(tree_sitter_rust::LANGUAGE.into()),
8125 );
8126 let language = Arc::new(language);
8127
8128 cx.language_registry().add(language.clone());
8129 cx.update_buffer(|buffer, cx| {
8130 buffer.set_language(Some(language), cx);
8131 });
8132
8133 cx.set_state(
8134 &r#"
8135 fn main() {
8136 sampleˇ
8137 }
8138 "#
8139 .unindent(),
8140 );
8141
8142 cx.update_editor(|editor, window, cx| {
8143 editor.handle_input("(", window, cx);
8144 });
8145 cx.assert_editor_state(
8146 &"
8147 fn main() {
8148 sample(ˇ)
8149 }
8150 "
8151 .unindent(),
8152 );
8153
8154 let mocked_response = lsp::SignatureHelp {
8155 signatures: vec![lsp::SignatureInformation {
8156 label: "fn sample(param1: u8, param2: u8)".to_string(),
8157 documentation: None,
8158 parameters: Some(vec![
8159 lsp::ParameterInformation {
8160 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8161 documentation: None,
8162 },
8163 lsp::ParameterInformation {
8164 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8165 documentation: None,
8166 },
8167 ]),
8168 active_parameter: None,
8169 }],
8170 active_signature: Some(0),
8171 active_parameter: Some(0),
8172 };
8173 handle_signature_help_request(&mut cx, mocked_response).await;
8174
8175 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8176 .await;
8177
8178 cx.editor(|editor, _, _| {
8179 let signature_help_state = editor.signature_help_state.popover().cloned();
8180 assert_eq!(
8181 signature_help_state.unwrap().label,
8182 "param1: u8, param2: u8"
8183 );
8184 });
8185}
8186
8187#[gpui::test]
8188async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8189 init_test(cx, |_| {});
8190
8191 cx.update(|cx| {
8192 cx.update_global::<SettingsStore, _>(|settings, cx| {
8193 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8194 settings.auto_signature_help = Some(false);
8195 settings.show_signature_help_after_edits = Some(false);
8196 });
8197 });
8198 });
8199
8200 let mut cx = EditorLspTestContext::new_rust(
8201 lsp::ServerCapabilities {
8202 signature_help_provider: Some(lsp::SignatureHelpOptions {
8203 ..Default::default()
8204 }),
8205 ..Default::default()
8206 },
8207 cx,
8208 )
8209 .await;
8210
8211 let language = Language::new(
8212 LanguageConfig {
8213 name: "Rust".into(),
8214 brackets: BracketPairConfig {
8215 pairs: vec![
8216 BracketPair {
8217 start: "{".to_string(),
8218 end: "}".to_string(),
8219 close: true,
8220 surround: true,
8221 newline: true,
8222 },
8223 BracketPair {
8224 start: "(".to_string(),
8225 end: ")".to_string(),
8226 close: true,
8227 surround: true,
8228 newline: true,
8229 },
8230 BracketPair {
8231 start: "/*".to_string(),
8232 end: " */".to_string(),
8233 close: true,
8234 surround: true,
8235 newline: true,
8236 },
8237 BracketPair {
8238 start: "[".to_string(),
8239 end: "]".to_string(),
8240 close: false,
8241 surround: false,
8242 newline: true,
8243 },
8244 BracketPair {
8245 start: "\"".to_string(),
8246 end: "\"".to_string(),
8247 close: true,
8248 surround: true,
8249 newline: false,
8250 },
8251 BracketPair {
8252 start: "<".to_string(),
8253 end: ">".to_string(),
8254 close: false,
8255 surround: true,
8256 newline: true,
8257 },
8258 ],
8259 ..Default::default()
8260 },
8261 autoclose_before: "})]".to_string(),
8262 ..Default::default()
8263 },
8264 Some(tree_sitter_rust::LANGUAGE.into()),
8265 );
8266 let language = Arc::new(language);
8267
8268 cx.language_registry().add(language.clone());
8269 cx.update_buffer(|buffer, cx| {
8270 buffer.set_language(Some(language), cx);
8271 });
8272
8273 // Ensure that signature_help is not called when no signature help is enabled.
8274 cx.set_state(
8275 &r#"
8276 fn main() {
8277 sampleˇ
8278 }
8279 "#
8280 .unindent(),
8281 );
8282 cx.update_editor(|editor, window, cx| {
8283 editor.handle_input("(", window, cx);
8284 });
8285 cx.assert_editor_state(
8286 &"
8287 fn main() {
8288 sample(ˇ)
8289 }
8290 "
8291 .unindent(),
8292 );
8293 cx.editor(|editor, _, _| {
8294 assert!(editor.signature_help_state.task().is_none());
8295 });
8296
8297 let mocked_response = lsp::SignatureHelp {
8298 signatures: vec![lsp::SignatureInformation {
8299 label: "fn sample(param1: u8, param2: u8)".to_string(),
8300 documentation: None,
8301 parameters: Some(vec![
8302 lsp::ParameterInformation {
8303 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8304 documentation: None,
8305 },
8306 lsp::ParameterInformation {
8307 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8308 documentation: None,
8309 },
8310 ]),
8311 active_parameter: None,
8312 }],
8313 active_signature: Some(0),
8314 active_parameter: Some(0),
8315 };
8316
8317 // Ensure that signature_help is called when enabled afte edits
8318 cx.update(|_, cx| {
8319 cx.update_global::<SettingsStore, _>(|settings, cx| {
8320 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8321 settings.auto_signature_help = Some(false);
8322 settings.show_signature_help_after_edits = Some(true);
8323 });
8324 });
8325 });
8326 cx.set_state(
8327 &r#"
8328 fn main() {
8329 sampleˇ
8330 }
8331 "#
8332 .unindent(),
8333 );
8334 cx.update_editor(|editor, window, cx| {
8335 editor.handle_input("(", window, cx);
8336 });
8337 cx.assert_editor_state(
8338 &"
8339 fn main() {
8340 sample(ˇ)
8341 }
8342 "
8343 .unindent(),
8344 );
8345 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8346 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8347 .await;
8348 cx.update_editor(|editor, _, _| {
8349 let signature_help_state = editor.signature_help_state.popover().cloned();
8350 assert!(signature_help_state.is_some());
8351 assert_eq!(
8352 signature_help_state.unwrap().label,
8353 "param1: u8, param2: u8"
8354 );
8355 editor.signature_help_state = SignatureHelpState::default();
8356 });
8357
8358 // Ensure that signature_help is called when auto signature help override is enabled
8359 cx.update(|_, cx| {
8360 cx.update_global::<SettingsStore, _>(|settings, cx| {
8361 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8362 settings.auto_signature_help = Some(true);
8363 settings.show_signature_help_after_edits = Some(false);
8364 });
8365 });
8366 });
8367 cx.set_state(
8368 &r#"
8369 fn main() {
8370 sampleˇ
8371 }
8372 "#
8373 .unindent(),
8374 );
8375 cx.update_editor(|editor, window, cx| {
8376 editor.handle_input("(", window, cx);
8377 });
8378 cx.assert_editor_state(
8379 &"
8380 fn main() {
8381 sample(ˇ)
8382 }
8383 "
8384 .unindent(),
8385 );
8386 handle_signature_help_request(&mut cx, mocked_response).await;
8387 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8388 .await;
8389 cx.editor(|editor, _, _| {
8390 let signature_help_state = editor.signature_help_state.popover().cloned();
8391 assert!(signature_help_state.is_some());
8392 assert_eq!(
8393 signature_help_state.unwrap().label,
8394 "param1: u8, param2: u8"
8395 );
8396 });
8397}
8398
8399#[gpui::test]
8400async fn test_signature_help(cx: &mut TestAppContext) {
8401 init_test(cx, |_| {});
8402 cx.update(|cx| {
8403 cx.update_global::<SettingsStore, _>(|settings, cx| {
8404 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8405 settings.auto_signature_help = Some(true);
8406 });
8407 });
8408 });
8409
8410 let mut cx = EditorLspTestContext::new_rust(
8411 lsp::ServerCapabilities {
8412 signature_help_provider: Some(lsp::SignatureHelpOptions {
8413 ..Default::default()
8414 }),
8415 ..Default::default()
8416 },
8417 cx,
8418 )
8419 .await;
8420
8421 // A test that directly calls `show_signature_help`
8422 cx.update_editor(|editor, window, cx| {
8423 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8424 });
8425
8426 let mocked_response = lsp::SignatureHelp {
8427 signatures: vec![lsp::SignatureInformation {
8428 label: "fn sample(param1: u8, param2: u8)".to_string(),
8429 documentation: None,
8430 parameters: Some(vec![
8431 lsp::ParameterInformation {
8432 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8433 documentation: None,
8434 },
8435 lsp::ParameterInformation {
8436 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8437 documentation: None,
8438 },
8439 ]),
8440 active_parameter: None,
8441 }],
8442 active_signature: Some(0),
8443 active_parameter: Some(0),
8444 };
8445 handle_signature_help_request(&mut cx, mocked_response).await;
8446
8447 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8448 .await;
8449
8450 cx.editor(|editor, _, _| {
8451 let signature_help_state = editor.signature_help_state.popover().cloned();
8452 assert!(signature_help_state.is_some());
8453 assert_eq!(
8454 signature_help_state.unwrap().label,
8455 "param1: u8, param2: u8"
8456 );
8457 });
8458
8459 // When exiting outside from inside the brackets, `signature_help` is closed.
8460 cx.set_state(indoc! {"
8461 fn main() {
8462 sample(ˇ);
8463 }
8464
8465 fn sample(param1: u8, param2: u8) {}
8466 "});
8467
8468 cx.update_editor(|editor, window, cx| {
8469 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8470 });
8471
8472 let mocked_response = lsp::SignatureHelp {
8473 signatures: Vec::new(),
8474 active_signature: None,
8475 active_parameter: None,
8476 };
8477 handle_signature_help_request(&mut cx, mocked_response).await;
8478
8479 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8480 .await;
8481
8482 cx.editor(|editor, _, _| {
8483 assert!(!editor.signature_help_state.is_shown());
8484 });
8485
8486 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8487 cx.set_state(indoc! {"
8488 fn main() {
8489 sample(ˇ);
8490 }
8491
8492 fn sample(param1: u8, param2: u8) {}
8493 "});
8494
8495 let mocked_response = lsp::SignatureHelp {
8496 signatures: vec![lsp::SignatureInformation {
8497 label: "fn sample(param1: u8, param2: u8)".to_string(),
8498 documentation: None,
8499 parameters: Some(vec![
8500 lsp::ParameterInformation {
8501 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8502 documentation: None,
8503 },
8504 lsp::ParameterInformation {
8505 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8506 documentation: None,
8507 },
8508 ]),
8509 active_parameter: None,
8510 }],
8511 active_signature: Some(0),
8512 active_parameter: Some(0),
8513 };
8514 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8515 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8516 .await;
8517 cx.editor(|editor, _, _| {
8518 assert!(editor.signature_help_state.is_shown());
8519 });
8520
8521 // Restore the popover with more parameter input
8522 cx.set_state(indoc! {"
8523 fn main() {
8524 sample(param1, param2ˇ);
8525 }
8526
8527 fn sample(param1: u8, param2: u8) {}
8528 "});
8529
8530 let mocked_response = lsp::SignatureHelp {
8531 signatures: vec![lsp::SignatureInformation {
8532 label: "fn sample(param1: u8, param2: u8)".to_string(),
8533 documentation: None,
8534 parameters: Some(vec![
8535 lsp::ParameterInformation {
8536 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8537 documentation: None,
8538 },
8539 lsp::ParameterInformation {
8540 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8541 documentation: None,
8542 },
8543 ]),
8544 active_parameter: None,
8545 }],
8546 active_signature: Some(0),
8547 active_parameter: Some(1),
8548 };
8549 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8550 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8551 .await;
8552
8553 // When selecting a range, the popover is gone.
8554 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8555 cx.update_editor(|editor, window, cx| {
8556 editor.change_selections(None, window, cx, |s| {
8557 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8558 })
8559 });
8560 cx.assert_editor_state(indoc! {"
8561 fn main() {
8562 sample(param1, «ˇparam2»);
8563 }
8564
8565 fn sample(param1: u8, param2: u8) {}
8566 "});
8567 cx.editor(|editor, _, _| {
8568 assert!(!editor.signature_help_state.is_shown());
8569 });
8570
8571 // When unselecting again, the popover is back if within the brackets.
8572 cx.update_editor(|editor, window, cx| {
8573 editor.change_selections(None, window, cx, |s| {
8574 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8575 })
8576 });
8577 cx.assert_editor_state(indoc! {"
8578 fn main() {
8579 sample(param1, ˇparam2);
8580 }
8581
8582 fn sample(param1: u8, param2: u8) {}
8583 "});
8584 handle_signature_help_request(&mut cx, mocked_response).await;
8585 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8586 .await;
8587 cx.editor(|editor, _, _| {
8588 assert!(editor.signature_help_state.is_shown());
8589 });
8590
8591 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8592 cx.update_editor(|editor, window, cx| {
8593 editor.change_selections(None, window, cx, |s| {
8594 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8595 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8596 })
8597 });
8598 cx.assert_editor_state(indoc! {"
8599 fn main() {
8600 sample(param1, ˇparam2);
8601 }
8602
8603 fn sample(param1: u8, param2: u8) {}
8604 "});
8605
8606 let mocked_response = lsp::SignatureHelp {
8607 signatures: vec![lsp::SignatureInformation {
8608 label: "fn sample(param1: u8, param2: u8)".to_string(),
8609 documentation: None,
8610 parameters: Some(vec![
8611 lsp::ParameterInformation {
8612 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8613 documentation: None,
8614 },
8615 lsp::ParameterInformation {
8616 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8617 documentation: None,
8618 },
8619 ]),
8620 active_parameter: None,
8621 }],
8622 active_signature: Some(0),
8623 active_parameter: Some(1),
8624 };
8625 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8626 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8627 .await;
8628 cx.update_editor(|editor, _, cx| {
8629 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8630 });
8631 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8632 .await;
8633 cx.update_editor(|editor, window, cx| {
8634 editor.change_selections(None, window, cx, |s| {
8635 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8636 })
8637 });
8638 cx.assert_editor_state(indoc! {"
8639 fn main() {
8640 sample(param1, «ˇparam2»);
8641 }
8642
8643 fn sample(param1: u8, param2: u8) {}
8644 "});
8645 cx.update_editor(|editor, window, cx| {
8646 editor.change_selections(None, window, cx, |s| {
8647 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8648 })
8649 });
8650 cx.assert_editor_state(indoc! {"
8651 fn main() {
8652 sample(param1, ˇparam2);
8653 }
8654
8655 fn sample(param1: u8, param2: u8) {}
8656 "});
8657 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8658 .await;
8659}
8660
8661#[gpui::test]
8662async fn test_completion(cx: &mut TestAppContext) {
8663 init_test(cx, |_| {});
8664
8665 let mut cx = EditorLspTestContext::new_rust(
8666 lsp::ServerCapabilities {
8667 completion_provider: Some(lsp::CompletionOptions {
8668 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8669 resolve_provider: Some(true),
8670 ..Default::default()
8671 }),
8672 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8673 ..Default::default()
8674 },
8675 cx,
8676 )
8677 .await;
8678 let counter = Arc::new(AtomicUsize::new(0));
8679
8680 cx.set_state(indoc! {"
8681 oneˇ
8682 two
8683 three
8684 "});
8685 cx.simulate_keystroke(".");
8686 handle_completion_request(
8687 &mut cx,
8688 indoc! {"
8689 one.|<>
8690 two
8691 three
8692 "},
8693 vec!["first_completion", "second_completion"],
8694 counter.clone(),
8695 )
8696 .await;
8697 cx.condition(|editor, _| editor.context_menu_visible())
8698 .await;
8699 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8700
8701 let _handler = handle_signature_help_request(
8702 &mut cx,
8703 lsp::SignatureHelp {
8704 signatures: vec![lsp::SignatureInformation {
8705 label: "test signature".to_string(),
8706 documentation: None,
8707 parameters: Some(vec![lsp::ParameterInformation {
8708 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8709 documentation: None,
8710 }]),
8711 active_parameter: None,
8712 }],
8713 active_signature: None,
8714 active_parameter: None,
8715 },
8716 );
8717 cx.update_editor(|editor, window, cx| {
8718 assert!(
8719 !editor.signature_help_state.is_shown(),
8720 "No signature help was called for"
8721 );
8722 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8723 });
8724 cx.run_until_parked();
8725 cx.update_editor(|editor, _, _| {
8726 assert!(
8727 !editor.signature_help_state.is_shown(),
8728 "No signature help should be shown when completions menu is open"
8729 );
8730 });
8731
8732 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8733 editor.context_menu_next(&Default::default(), window, cx);
8734 editor
8735 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8736 .unwrap()
8737 });
8738 cx.assert_editor_state(indoc! {"
8739 one.second_completionˇ
8740 two
8741 three
8742 "});
8743
8744 handle_resolve_completion_request(
8745 &mut cx,
8746 Some(vec![
8747 (
8748 //This overlaps with the primary completion edit which is
8749 //misbehavior from the LSP spec, test that we filter it out
8750 indoc! {"
8751 one.second_ˇcompletion
8752 two
8753 threeˇ
8754 "},
8755 "overlapping additional edit",
8756 ),
8757 (
8758 indoc! {"
8759 one.second_completion
8760 two
8761 threeˇ
8762 "},
8763 "\nadditional edit",
8764 ),
8765 ]),
8766 )
8767 .await;
8768 apply_additional_edits.await.unwrap();
8769 cx.assert_editor_state(indoc! {"
8770 one.second_completionˇ
8771 two
8772 three
8773 additional edit
8774 "});
8775
8776 cx.set_state(indoc! {"
8777 one.second_completion
8778 twoˇ
8779 threeˇ
8780 additional edit
8781 "});
8782 cx.simulate_keystroke(" ");
8783 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8784 cx.simulate_keystroke("s");
8785 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8786
8787 cx.assert_editor_state(indoc! {"
8788 one.second_completion
8789 two sˇ
8790 three sˇ
8791 additional edit
8792 "});
8793 handle_completion_request(
8794 &mut cx,
8795 indoc! {"
8796 one.second_completion
8797 two s
8798 three <s|>
8799 additional edit
8800 "},
8801 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8802 counter.clone(),
8803 )
8804 .await;
8805 cx.condition(|editor, _| editor.context_menu_visible())
8806 .await;
8807 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8808
8809 cx.simulate_keystroke("i");
8810
8811 handle_completion_request(
8812 &mut cx,
8813 indoc! {"
8814 one.second_completion
8815 two si
8816 three <si|>
8817 additional edit
8818 "},
8819 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8820 counter.clone(),
8821 )
8822 .await;
8823 cx.condition(|editor, _| editor.context_menu_visible())
8824 .await;
8825 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8826
8827 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8828 editor
8829 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8830 .unwrap()
8831 });
8832 cx.assert_editor_state(indoc! {"
8833 one.second_completion
8834 two sixth_completionˇ
8835 three sixth_completionˇ
8836 additional edit
8837 "});
8838
8839 apply_additional_edits.await.unwrap();
8840
8841 update_test_language_settings(&mut cx, |settings| {
8842 settings.defaults.show_completions_on_input = Some(false);
8843 });
8844 cx.set_state("editorˇ");
8845 cx.simulate_keystroke(".");
8846 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8847 cx.simulate_keystroke("c");
8848 cx.simulate_keystroke("l");
8849 cx.simulate_keystroke("o");
8850 cx.assert_editor_state("editor.cloˇ");
8851 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8852 cx.update_editor(|editor, window, cx| {
8853 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8854 });
8855 handle_completion_request(
8856 &mut cx,
8857 "editor.<clo|>",
8858 vec!["close", "clobber"],
8859 counter.clone(),
8860 )
8861 .await;
8862 cx.condition(|editor, _| editor.context_menu_visible())
8863 .await;
8864 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8865
8866 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8867 editor
8868 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8869 .unwrap()
8870 });
8871 cx.assert_editor_state("editor.closeˇ");
8872 handle_resolve_completion_request(&mut cx, None).await;
8873 apply_additional_edits.await.unwrap();
8874}
8875
8876#[gpui::test]
8877async fn test_multiline_completion(cx: &mut TestAppContext) {
8878 init_test(cx, |_| {});
8879
8880 let fs = FakeFs::new(cx.executor());
8881 fs.insert_tree(
8882 path!("/a"),
8883 json!({
8884 "main.ts": "a",
8885 }),
8886 )
8887 .await;
8888
8889 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8890 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8891 let typescript_language = Arc::new(Language::new(
8892 LanguageConfig {
8893 name: "TypeScript".into(),
8894 matcher: LanguageMatcher {
8895 path_suffixes: vec!["ts".to_string()],
8896 ..LanguageMatcher::default()
8897 },
8898 line_comments: vec!["// ".into()],
8899 ..LanguageConfig::default()
8900 },
8901 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8902 ));
8903 language_registry.add(typescript_language.clone());
8904 let mut fake_servers = language_registry.register_fake_lsp(
8905 "TypeScript",
8906 FakeLspAdapter {
8907 capabilities: lsp::ServerCapabilities {
8908 completion_provider: Some(lsp::CompletionOptions {
8909 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8910 ..lsp::CompletionOptions::default()
8911 }),
8912 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8913 ..lsp::ServerCapabilities::default()
8914 },
8915 // Emulate vtsls label generation
8916 label_for_completion: Some(Box::new(|item, _| {
8917 let text = if let Some(description) = item
8918 .label_details
8919 .as_ref()
8920 .and_then(|label_details| label_details.description.as_ref())
8921 {
8922 format!("{} {}", item.label, description)
8923 } else if let Some(detail) = &item.detail {
8924 format!("{} {}", item.label, detail)
8925 } else {
8926 item.label.clone()
8927 };
8928 let len = text.len();
8929 Some(language::CodeLabel {
8930 text,
8931 runs: Vec::new(),
8932 filter_range: 0..len,
8933 })
8934 })),
8935 ..FakeLspAdapter::default()
8936 },
8937 );
8938 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8939 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8940 let worktree_id = workspace
8941 .update(cx, |workspace, _window, cx| {
8942 workspace.project().update(cx, |project, cx| {
8943 project.worktrees(cx).next().unwrap().read(cx).id()
8944 })
8945 })
8946 .unwrap();
8947 let _buffer = project
8948 .update(cx, |project, cx| {
8949 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
8950 })
8951 .await
8952 .unwrap();
8953 let editor = workspace
8954 .update(cx, |workspace, window, cx| {
8955 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8956 })
8957 .unwrap()
8958 .await
8959 .unwrap()
8960 .downcast::<Editor>()
8961 .unwrap();
8962 let fake_server = fake_servers.next().await.unwrap();
8963
8964 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8965 let multiline_label_2 = "a\nb\nc\n";
8966 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8967 let multiline_description = "d\ne\nf\n";
8968 let multiline_detail_2 = "g\nh\ni\n";
8969
8970 let mut completion_handle =
8971 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8972 Ok(Some(lsp::CompletionResponse::Array(vec![
8973 lsp::CompletionItem {
8974 label: multiline_label.to_string(),
8975 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8976 range: lsp::Range {
8977 start: lsp::Position {
8978 line: params.text_document_position.position.line,
8979 character: params.text_document_position.position.character,
8980 },
8981 end: lsp::Position {
8982 line: params.text_document_position.position.line,
8983 character: params.text_document_position.position.character,
8984 },
8985 },
8986 new_text: "new_text_1".to_string(),
8987 })),
8988 ..lsp::CompletionItem::default()
8989 },
8990 lsp::CompletionItem {
8991 label: "single line label 1".to_string(),
8992 detail: Some(multiline_detail.to_string()),
8993 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8994 range: lsp::Range {
8995 start: lsp::Position {
8996 line: params.text_document_position.position.line,
8997 character: params.text_document_position.position.character,
8998 },
8999 end: lsp::Position {
9000 line: params.text_document_position.position.line,
9001 character: params.text_document_position.position.character,
9002 },
9003 },
9004 new_text: "new_text_2".to_string(),
9005 })),
9006 ..lsp::CompletionItem::default()
9007 },
9008 lsp::CompletionItem {
9009 label: "single line label 2".to_string(),
9010 label_details: Some(lsp::CompletionItemLabelDetails {
9011 description: Some(multiline_description.to_string()),
9012 detail: None,
9013 }),
9014 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9015 range: lsp::Range {
9016 start: lsp::Position {
9017 line: params.text_document_position.position.line,
9018 character: params.text_document_position.position.character,
9019 },
9020 end: lsp::Position {
9021 line: params.text_document_position.position.line,
9022 character: params.text_document_position.position.character,
9023 },
9024 },
9025 new_text: "new_text_2".to_string(),
9026 })),
9027 ..lsp::CompletionItem::default()
9028 },
9029 lsp::CompletionItem {
9030 label: multiline_label_2.to_string(),
9031 detail: Some(multiline_detail_2.to_string()),
9032 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9033 range: lsp::Range {
9034 start: lsp::Position {
9035 line: params.text_document_position.position.line,
9036 character: params.text_document_position.position.character,
9037 },
9038 end: lsp::Position {
9039 line: params.text_document_position.position.line,
9040 character: params.text_document_position.position.character,
9041 },
9042 },
9043 new_text: "new_text_3".to_string(),
9044 })),
9045 ..lsp::CompletionItem::default()
9046 },
9047 lsp::CompletionItem {
9048 label: "Label with many spaces and \t but without newlines".to_string(),
9049 detail: Some(
9050 "Details with many spaces and \t but without newlines".to_string(),
9051 ),
9052 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9053 range: lsp::Range {
9054 start: lsp::Position {
9055 line: params.text_document_position.position.line,
9056 character: params.text_document_position.position.character,
9057 },
9058 end: lsp::Position {
9059 line: params.text_document_position.position.line,
9060 character: params.text_document_position.position.character,
9061 },
9062 },
9063 new_text: "new_text_4".to_string(),
9064 })),
9065 ..lsp::CompletionItem::default()
9066 },
9067 ])))
9068 });
9069
9070 editor.update_in(cx, |editor, window, cx| {
9071 cx.focus_self(window);
9072 editor.move_to_end(&MoveToEnd, window, cx);
9073 editor.handle_input(".", window, cx);
9074 });
9075 cx.run_until_parked();
9076 completion_handle.next().await.unwrap();
9077
9078 editor.update(cx, |editor, _| {
9079 assert!(editor.context_menu_visible());
9080 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9081 {
9082 let completion_labels = menu
9083 .completions
9084 .borrow()
9085 .iter()
9086 .map(|c| c.label.text.clone())
9087 .collect::<Vec<_>>();
9088 assert_eq!(
9089 completion_labels,
9090 &[
9091 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9092 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9093 "single line label 2 d e f ",
9094 "a b c g h i ",
9095 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9096 ],
9097 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9098 );
9099
9100 for completion in menu
9101 .completions
9102 .borrow()
9103 .iter() {
9104 assert_eq!(
9105 completion.label.filter_range,
9106 0..completion.label.text.len(),
9107 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9108 );
9109 }
9110
9111 } else {
9112 panic!("expected completion menu to be open");
9113 }
9114 });
9115}
9116
9117#[gpui::test]
9118async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9119 init_test(cx, |_| {});
9120 let mut cx = EditorLspTestContext::new_rust(
9121 lsp::ServerCapabilities {
9122 completion_provider: Some(lsp::CompletionOptions {
9123 trigger_characters: Some(vec![".".to_string()]),
9124 ..Default::default()
9125 }),
9126 ..Default::default()
9127 },
9128 cx,
9129 )
9130 .await;
9131 cx.lsp
9132 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9133 Ok(Some(lsp::CompletionResponse::Array(vec![
9134 lsp::CompletionItem {
9135 label: "first".into(),
9136 ..Default::default()
9137 },
9138 lsp::CompletionItem {
9139 label: "last".into(),
9140 ..Default::default()
9141 },
9142 ])))
9143 });
9144 cx.set_state("variableˇ");
9145 cx.simulate_keystroke(".");
9146 cx.executor().run_until_parked();
9147
9148 cx.update_editor(|editor, _, _| {
9149 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9150 {
9151 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9152 } else {
9153 panic!("expected completion menu to be open");
9154 }
9155 });
9156
9157 cx.update_editor(|editor, window, cx| {
9158 editor.move_page_down(&MovePageDown::default(), window, cx);
9159 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9160 {
9161 assert!(
9162 menu.selected_item == 1,
9163 "expected PageDown to select the last item from the context menu"
9164 );
9165 } else {
9166 panic!("expected completion menu to stay open after PageDown");
9167 }
9168 });
9169
9170 cx.update_editor(|editor, window, cx| {
9171 editor.move_page_up(&MovePageUp::default(), window, cx);
9172 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9173 {
9174 assert!(
9175 menu.selected_item == 0,
9176 "expected PageUp to select the first item from the context menu"
9177 );
9178 } else {
9179 panic!("expected completion menu to stay open after PageUp");
9180 }
9181 });
9182}
9183
9184#[gpui::test]
9185async fn test_completion_sort(cx: &mut TestAppContext) {
9186 init_test(cx, |_| {});
9187 let mut cx = EditorLspTestContext::new_rust(
9188 lsp::ServerCapabilities {
9189 completion_provider: Some(lsp::CompletionOptions {
9190 trigger_characters: Some(vec![".".to_string()]),
9191 ..Default::default()
9192 }),
9193 ..Default::default()
9194 },
9195 cx,
9196 )
9197 .await;
9198 cx.lsp
9199 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9200 Ok(Some(lsp::CompletionResponse::Array(vec![
9201 lsp::CompletionItem {
9202 label: "Range".into(),
9203 sort_text: Some("a".into()),
9204 ..Default::default()
9205 },
9206 lsp::CompletionItem {
9207 label: "r".into(),
9208 sort_text: Some("b".into()),
9209 ..Default::default()
9210 },
9211 lsp::CompletionItem {
9212 label: "ret".into(),
9213 sort_text: Some("c".into()),
9214 ..Default::default()
9215 },
9216 lsp::CompletionItem {
9217 label: "return".into(),
9218 sort_text: Some("d".into()),
9219 ..Default::default()
9220 },
9221 lsp::CompletionItem {
9222 label: "slice".into(),
9223 sort_text: Some("d".into()),
9224 ..Default::default()
9225 },
9226 ])))
9227 });
9228 cx.set_state("rˇ");
9229 cx.executor().run_until_parked();
9230 cx.update_editor(|editor, window, cx| {
9231 editor.show_completions(
9232 &ShowCompletions {
9233 trigger: Some("r".into()),
9234 },
9235 window,
9236 cx,
9237 );
9238 });
9239 cx.executor().run_until_parked();
9240
9241 cx.update_editor(|editor, _, _| {
9242 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9243 {
9244 assert_eq!(
9245 completion_menu_entries(&menu),
9246 &["r", "ret", "Range", "return"]
9247 );
9248 } else {
9249 panic!("expected completion menu to be open");
9250 }
9251 });
9252}
9253
9254#[gpui::test]
9255async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9256 init_test(cx, |_| {});
9257
9258 let mut cx = EditorLspTestContext::new_rust(
9259 lsp::ServerCapabilities {
9260 completion_provider: Some(lsp::CompletionOptions {
9261 trigger_characters: Some(vec![".".to_string()]),
9262 resolve_provider: Some(true),
9263 ..Default::default()
9264 }),
9265 ..Default::default()
9266 },
9267 cx,
9268 )
9269 .await;
9270
9271 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9272 cx.simulate_keystroke(".");
9273 let completion_item = lsp::CompletionItem {
9274 label: "Some".into(),
9275 kind: Some(lsp::CompletionItemKind::SNIPPET),
9276 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9277 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9278 kind: lsp::MarkupKind::Markdown,
9279 value: "```rust\nSome(2)\n```".to_string(),
9280 })),
9281 deprecated: Some(false),
9282 sort_text: Some("Some".to_string()),
9283 filter_text: Some("Some".to_string()),
9284 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9285 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9286 range: lsp::Range {
9287 start: lsp::Position {
9288 line: 0,
9289 character: 22,
9290 },
9291 end: lsp::Position {
9292 line: 0,
9293 character: 22,
9294 },
9295 },
9296 new_text: "Some(2)".to_string(),
9297 })),
9298 additional_text_edits: Some(vec![lsp::TextEdit {
9299 range: lsp::Range {
9300 start: lsp::Position {
9301 line: 0,
9302 character: 20,
9303 },
9304 end: lsp::Position {
9305 line: 0,
9306 character: 22,
9307 },
9308 },
9309 new_text: "".to_string(),
9310 }]),
9311 ..Default::default()
9312 };
9313
9314 let closure_completion_item = completion_item.clone();
9315 let counter = Arc::new(AtomicUsize::new(0));
9316 let counter_clone = counter.clone();
9317 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9318 let task_completion_item = closure_completion_item.clone();
9319 counter_clone.fetch_add(1, atomic::Ordering::Release);
9320 async move {
9321 Ok(Some(lsp::CompletionResponse::Array(vec![
9322 task_completion_item,
9323 ])))
9324 }
9325 });
9326
9327 cx.condition(|editor, _| editor.context_menu_visible())
9328 .await;
9329 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9330 assert!(request.next().await.is_some());
9331 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9332
9333 cx.simulate_keystroke("S");
9334 cx.simulate_keystroke("o");
9335 cx.simulate_keystroke("m");
9336 cx.condition(|editor, _| editor.context_menu_visible())
9337 .await;
9338 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9339 assert!(request.next().await.is_some());
9340 assert!(request.next().await.is_some());
9341 assert!(request.next().await.is_some());
9342 request.close();
9343 assert!(request.next().await.is_none());
9344 assert_eq!(
9345 counter.load(atomic::Ordering::Acquire),
9346 4,
9347 "With the completions menu open, only one LSP request should happen per input"
9348 );
9349}
9350
9351#[gpui::test]
9352async fn test_toggle_comment(cx: &mut TestAppContext) {
9353 init_test(cx, |_| {});
9354 let mut cx = EditorTestContext::new(cx).await;
9355 let language = Arc::new(Language::new(
9356 LanguageConfig {
9357 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9358 ..Default::default()
9359 },
9360 Some(tree_sitter_rust::LANGUAGE.into()),
9361 ));
9362 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9363
9364 // If multiple selections intersect a line, the line is only toggled once.
9365 cx.set_state(indoc! {"
9366 fn a() {
9367 «//b();
9368 ˇ»// «c();
9369 //ˇ» d();
9370 }
9371 "});
9372
9373 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9374
9375 cx.assert_editor_state(indoc! {"
9376 fn a() {
9377 «b();
9378 c();
9379 ˇ» d();
9380 }
9381 "});
9382
9383 // The comment prefix is inserted at the same column for every line in a
9384 // selection.
9385 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9386
9387 cx.assert_editor_state(indoc! {"
9388 fn a() {
9389 // «b();
9390 // c();
9391 ˇ»// d();
9392 }
9393 "});
9394
9395 // If a selection ends at the beginning of a line, that line is not toggled.
9396 cx.set_selections_state(indoc! {"
9397 fn a() {
9398 // b();
9399 «// c();
9400 ˇ» // d();
9401 }
9402 "});
9403
9404 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9405
9406 cx.assert_editor_state(indoc! {"
9407 fn a() {
9408 // b();
9409 «c();
9410 ˇ» // d();
9411 }
9412 "});
9413
9414 // If a selection span a single line and is empty, the line is toggled.
9415 cx.set_state(indoc! {"
9416 fn a() {
9417 a();
9418 b();
9419 ˇ
9420 }
9421 "});
9422
9423 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9424
9425 cx.assert_editor_state(indoc! {"
9426 fn a() {
9427 a();
9428 b();
9429 //•ˇ
9430 }
9431 "});
9432
9433 // If a selection span multiple lines, empty lines are not toggled.
9434 cx.set_state(indoc! {"
9435 fn a() {
9436 «a();
9437
9438 c();ˇ»
9439 }
9440 "});
9441
9442 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9443
9444 cx.assert_editor_state(indoc! {"
9445 fn a() {
9446 // «a();
9447
9448 // c();ˇ»
9449 }
9450 "});
9451
9452 // If a selection includes multiple comment prefixes, all lines are uncommented.
9453 cx.set_state(indoc! {"
9454 fn a() {
9455 «// a();
9456 /// b();
9457 //! c();ˇ»
9458 }
9459 "});
9460
9461 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9462
9463 cx.assert_editor_state(indoc! {"
9464 fn a() {
9465 «a();
9466 b();
9467 c();ˇ»
9468 }
9469 "});
9470}
9471
9472#[gpui::test]
9473async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9474 init_test(cx, |_| {});
9475 let mut cx = EditorTestContext::new(cx).await;
9476 let language = Arc::new(Language::new(
9477 LanguageConfig {
9478 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9479 ..Default::default()
9480 },
9481 Some(tree_sitter_rust::LANGUAGE.into()),
9482 ));
9483 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9484
9485 let toggle_comments = &ToggleComments {
9486 advance_downwards: false,
9487 ignore_indent: true,
9488 };
9489
9490 // If multiple selections intersect a line, the line is only toggled once.
9491 cx.set_state(indoc! {"
9492 fn a() {
9493 // «b();
9494 // c();
9495 // ˇ» d();
9496 }
9497 "});
9498
9499 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9500
9501 cx.assert_editor_state(indoc! {"
9502 fn a() {
9503 «b();
9504 c();
9505 ˇ» d();
9506 }
9507 "});
9508
9509 // The comment prefix is inserted at the beginning of each line
9510 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9511
9512 cx.assert_editor_state(indoc! {"
9513 fn a() {
9514 // «b();
9515 // c();
9516 // ˇ» d();
9517 }
9518 "});
9519
9520 // If a selection ends at the beginning of a line, that line is not toggled.
9521 cx.set_selections_state(indoc! {"
9522 fn a() {
9523 // b();
9524 // «c();
9525 ˇ»// d();
9526 }
9527 "});
9528
9529 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9530
9531 cx.assert_editor_state(indoc! {"
9532 fn a() {
9533 // b();
9534 «c();
9535 ˇ»// d();
9536 }
9537 "});
9538
9539 // If a selection span a single line and is empty, the line is toggled.
9540 cx.set_state(indoc! {"
9541 fn a() {
9542 a();
9543 b();
9544 ˇ
9545 }
9546 "});
9547
9548 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9549
9550 cx.assert_editor_state(indoc! {"
9551 fn a() {
9552 a();
9553 b();
9554 //ˇ
9555 }
9556 "});
9557
9558 // If a selection span multiple lines, empty lines are not toggled.
9559 cx.set_state(indoc! {"
9560 fn a() {
9561 «a();
9562
9563 c();ˇ»
9564 }
9565 "});
9566
9567 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9568
9569 cx.assert_editor_state(indoc! {"
9570 fn a() {
9571 // «a();
9572
9573 // c();ˇ»
9574 }
9575 "});
9576
9577 // If a selection includes multiple comment prefixes, all lines are uncommented.
9578 cx.set_state(indoc! {"
9579 fn a() {
9580 // «a();
9581 /// b();
9582 //! c();ˇ»
9583 }
9584 "});
9585
9586 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9587
9588 cx.assert_editor_state(indoc! {"
9589 fn a() {
9590 «a();
9591 b();
9592 c();ˇ»
9593 }
9594 "});
9595}
9596
9597#[gpui::test]
9598async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9599 init_test(cx, |_| {});
9600
9601 let language = Arc::new(Language::new(
9602 LanguageConfig {
9603 line_comments: vec!["// ".into()],
9604 ..Default::default()
9605 },
9606 Some(tree_sitter_rust::LANGUAGE.into()),
9607 ));
9608
9609 let mut cx = EditorTestContext::new(cx).await;
9610
9611 cx.language_registry().add(language.clone());
9612 cx.update_buffer(|buffer, cx| {
9613 buffer.set_language(Some(language), cx);
9614 });
9615
9616 let toggle_comments = &ToggleComments {
9617 advance_downwards: true,
9618 ignore_indent: false,
9619 };
9620
9621 // Single cursor on one line -> advance
9622 // Cursor moves horizontally 3 characters as well on non-blank line
9623 cx.set_state(indoc!(
9624 "fn a() {
9625 ˇdog();
9626 cat();
9627 }"
9628 ));
9629 cx.update_editor(|editor, window, cx| {
9630 editor.toggle_comments(toggle_comments, window, cx);
9631 });
9632 cx.assert_editor_state(indoc!(
9633 "fn a() {
9634 // dog();
9635 catˇ();
9636 }"
9637 ));
9638
9639 // Single selection on one line -> don't advance
9640 cx.set_state(indoc!(
9641 "fn a() {
9642 «dog()ˇ»;
9643 cat();
9644 }"
9645 ));
9646 cx.update_editor(|editor, window, cx| {
9647 editor.toggle_comments(toggle_comments, window, cx);
9648 });
9649 cx.assert_editor_state(indoc!(
9650 "fn a() {
9651 // «dog()ˇ»;
9652 cat();
9653 }"
9654 ));
9655
9656 // Multiple cursors on one line -> advance
9657 cx.set_state(indoc!(
9658 "fn a() {
9659 ˇdˇog();
9660 cat();
9661 }"
9662 ));
9663 cx.update_editor(|editor, window, cx| {
9664 editor.toggle_comments(toggle_comments, window, cx);
9665 });
9666 cx.assert_editor_state(indoc!(
9667 "fn a() {
9668 // dog();
9669 catˇ(ˇ);
9670 }"
9671 ));
9672
9673 // Multiple cursors on one line, with selection -> don't advance
9674 cx.set_state(indoc!(
9675 "fn a() {
9676 ˇdˇog«()ˇ»;
9677 cat();
9678 }"
9679 ));
9680 cx.update_editor(|editor, window, cx| {
9681 editor.toggle_comments(toggle_comments, window, cx);
9682 });
9683 cx.assert_editor_state(indoc!(
9684 "fn a() {
9685 // ˇdˇog«()ˇ»;
9686 cat();
9687 }"
9688 ));
9689
9690 // Single cursor on one line -> advance
9691 // Cursor moves to column 0 on blank line
9692 cx.set_state(indoc!(
9693 "fn a() {
9694 ˇdog();
9695
9696 cat();
9697 }"
9698 ));
9699 cx.update_editor(|editor, window, cx| {
9700 editor.toggle_comments(toggle_comments, window, cx);
9701 });
9702 cx.assert_editor_state(indoc!(
9703 "fn a() {
9704 // dog();
9705 ˇ
9706 cat();
9707 }"
9708 ));
9709
9710 // Single cursor on one line -> advance
9711 // Cursor starts and ends at column 0
9712 cx.set_state(indoc!(
9713 "fn a() {
9714 ˇ dog();
9715 cat();
9716 }"
9717 ));
9718 cx.update_editor(|editor, window, cx| {
9719 editor.toggle_comments(toggle_comments, window, cx);
9720 });
9721 cx.assert_editor_state(indoc!(
9722 "fn a() {
9723 // dog();
9724 ˇ cat();
9725 }"
9726 ));
9727}
9728
9729#[gpui::test]
9730async fn test_toggle_block_comment(cx: &mut TestAppContext) {
9731 init_test(cx, |_| {});
9732
9733 let mut cx = EditorTestContext::new(cx).await;
9734
9735 let html_language = Arc::new(
9736 Language::new(
9737 LanguageConfig {
9738 name: "HTML".into(),
9739 block_comment: Some(("<!-- ".into(), " -->".into())),
9740 ..Default::default()
9741 },
9742 Some(tree_sitter_html::LANGUAGE.into()),
9743 )
9744 .with_injection_query(
9745 r#"
9746 (script_element
9747 (raw_text) @injection.content
9748 (#set! injection.language "javascript"))
9749 "#,
9750 )
9751 .unwrap(),
9752 );
9753
9754 let javascript_language = Arc::new(Language::new(
9755 LanguageConfig {
9756 name: "JavaScript".into(),
9757 line_comments: vec!["// ".into()],
9758 ..Default::default()
9759 },
9760 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9761 ));
9762
9763 cx.language_registry().add(html_language.clone());
9764 cx.language_registry().add(javascript_language.clone());
9765 cx.update_buffer(|buffer, cx| {
9766 buffer.set_language(Some(html_language), cx);
9767 });
9768
9769 // Toggle comments for empty selections
9770 cx.set_state(
9771 &r#"
9772 <p>A</p>ˇ
9773 <p>B</p>ˇ
9774 <p>C</p>ˇ
9775 "#
9776 .unindent(),
9777 );
9778 cx.update_editor(|editor, window, cx| {
9779 editor.toggle_comments(&ToggleComments::default(), window, cx)
9780 });
9781 cx.assert_editor_state(
9782 &r#"
9783 <!-- <p>A</p>ˇ -->
9784 <!-- <p>B</p>ˇ -->
9785 <!-- <p>C</p>ˇ -->
9786 "#
9787 .unindent(),
9788 );
9789 cx.update_editor(|editor, window, cx| {
9790 editor.toggle_comments(&ToggleComments::default(), window, cx)
9791 });
9792 cx.assert_editor_state(
9793 &r#"
9794 <p>A</p>ˇ
9795 <p>B</p>ˇ
9796 <p>C</p>ˇ
9797 "#
9798 .unindent(),
9799 );
9800
9801 // Toggle comments for mixture of empty and non-empty selections, where
9802 // multiple selections occupy a given line.
9803 cx.set_state(
9804 &r#"
9805 <p>A«</p>
9806 <p>ˇ»B</p>ˇ
9807 <p>C«</p>
9808 <p>ˇ»D</p>ˇ
9809 "#
9810 .unindent(),
9811 );
9812
9813 cx.update_editor(|editor, window, cx| {
9814 editor.toggle_comments(&ToggleComments::default(), window, cx)
9815 });
9816 cx.assert_editor_state(
9817 &r#"
9818 <!-- <p>A«</p>
9819 <p>ˇ»B</p>ˇ -->
9820 <!-- <p>C«</p>
9821 <p>ˇ»D</p>ˇ -->
9822 "#
9823 .unindent(),
9824 );
9825 cx.update_editor(|editor, window, cx| {
9826 editor.toggle_comments(&ToggleComments::default(), window, cx)
9827 });
9828 cx.assert_editor_state(
9829 &r#"
9830 <p>A«</p>
9831 <p>ˇ»B</p>ˇ
9832 <p>C«</p>
9833 <p>ˇ»D</p>ˇ
9834 "#
9835 .unindent(),
9836 );
9837
9838 // Toggle comments when different languages are active for different
9839 // selections.
9840 cx.set_state(
9841 &r#"
9842 ˇ<script>
9843 ˇvar x = new Y();
9844 ˇ</script>
9845 "#
9846 .unindent(),
9847 );
9848 cx.executor().run_until_parked();
9849 cx.update_editor(|editor, window, cx| {
9850 editor.toggle_comments(&ToggleComments::default(), window, cx)
9851 });
9852 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9853 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9854 cx.assert_editor_state(
9855 &r#"
9856 <!-- ˇ<script> -->
9857 // ˇvar x = new Y();
9858 <!-- ˇ</script> -->
9859 "#
9860 .unindent(),
9861 );
9862}
9863
9864#[gpui::test]
9865fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9866 init_test(cx, |_| {});
9867
9868 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9869 let multibuffer = cx.new(|cx| {
9870 let mut multibuffer = MultiBuffer::new(ReadWrite);
9871 multibuffer.push_excerpts(
9872 buffer.clone(),
9873 [
9874 ExcerptRange {
9875 context: Point::new(0, 0)..Point::new(0, 4),
9876 primary: None,
9877 },
9878 ExcerptRange {
9879 context: Point::new(1, 0)..Point::new(1, 4),
9880 primary: None,
9881 },
9882 ],
9883 cx,
9884 );
9885 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9886 multibuffer
9887 });
9888
9889 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9890 editor.update_in(cx, |editor, window, cx| {
9891 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9892 editor.change_selections(None, window, cx, |s| {
9893 s.select_ranges([
9894 Point::new(0, 0)..Point::new(0, 0),
9895 Point::new(1, 0)..Point::new(1, 0),
9896 ])
9897 });
9898
9899 editor.handle_input("X", window, cx);
9900 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9901 assert_eq!(
9902 editor.selections.ranges(cx),
9903 [
9904 Point::new(0, 1)..Point::new(0, 1),
9905 Point::new(1, 1)..Point::new(1, 1),
9906 ]
9907 );
9908
9909 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9910 editor.change_selections(None, window, cx, |s| {
9911 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9912 });
9913 editor.backspace(&Default::default(), window, cx);
9914 assert_eq!(editor.text(cx), "Xa\nbbb");
9915 assert_eq!(
9916 editor.selections.ranges(cx),
9917 [Point::new(1, 0)..Point::new(1, 0)]
9918 );
9919
9920 editor.change_selections(None, window, cx, |s| {
9921 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9922 });
9923 editor.backspace(&Default::default(), window, cx);
9924 assert_eq!(editor.text(cx), "X\nbb");
9925 assert_eq!(
9926 editor.selections.ranges(cx),
9927 [Point::new(0, 1)..Point::new(0, 1)]
9928 );
9929 });
9930}
9931
9932#[gpui::test]
9933fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9934 init_test(cx, |_| {});
9935
9936 let markers = vec![('[', ']').into(), ('(', ')').into()];
9937 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9938 indoc! {"
9939 [aaaa
9940 (bbbb]
9941 cccc)",
9942 },
9943 markers.clone(),
9944 );
9945 let excerpt_ranges = markers.into_iter().map(|marker| {
9946 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9947 ExcerptRange {
9948 context,
9949 primary: None,
9950 }
9951 });
9952 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9953 let multibuffer = cx.new(|cx| {
9954 let mut multibuffer = MultiBuffer::new(ReadWrite);
9955 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9956 multibuffer
9957 });
9958
9959 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9960 editor.update_in(cx, |editor, window, cx| {
9961 let (expected_text, selection_ranges) = marked_text_ranges(
9962 indoc! {"
9963 aaaa
9964 bˇbbb
9965 bˇbbˇb
9966 cccc"
9967 },
9968 true,
9969 );
9970 assert_eq!(editor.text(cx), expected_text);
9971 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9972
9973 editor.handle_input("X", window, cx);
9974
9975 let (expected_text, expected_selections) = marked_text_ranges(
9976 indoc! {"
9977 aaaa
9978 bXˇbbXb
9979 bXˇbbXˇb
9980 cccc"
9981 },
9982 false,
9983 );
9984 assert_eq!(editor.text(cx), expected_text);
9985 assert_eq!(editor.selections.ranges(cx), expected_selections);
9986
9987 editor.newline(&Newline, window, cx);
9988 let (expected_text, expected_selections) = marked_text_ranges(
9989 indoc! {"
9990 aaaa
9991 bX
9992 ˇbbX
9993 b
9994 bX
9995 ˇbbX
9996 ˇb
9997 cccc"
9998 },
9999 false,
10000 );
10001 assert_eq!(editor.text(cx), expected_text);
10002 assert_eq!(editor.selections.ranges(cx), expected_selections);
10003 });
10004}
10005
10006#[gpui::test]
10007fn test_refresh_selections(cx: &mut TestAppContext) {
10008 init_test(cx, |_| {});
10009
10010 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10011 let mut excerpt1_id = None;
10012 let multibuffer = cx.new(|cx| {
10013 let mut multibuffer = MultiBuffer::new(ReadWrite);
10014 excerpt1_id = multibuffer
10015 .push_excerpts(
10016 buffer.clone(),
10017 [
10018 ExcerptRange {
10019 context: Point::new(0, 0)..Point::new(1, 4),
10020 primary: None,
10021 },
10022 ExcerptRange {
10023 context: Point::new(1, 0)..Point::new(2, 4),
10024 primary: None,
10025 },
10026 ],
10027 cx,
10028 )
10029 .into_iter()
10030 .next();
10031 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10032 multibuffer
10033 });
10034
10035 let editor = cx.add_window(|window, cx| {
10036 let mut editor = build_editor(multibuffer.clone(), window, cx);
10037 let snapshot = editor.snapshot(window, cx);
10038 editor.change_selections(None, window, cx, |s| {
10039 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10040 });
10041 editor.begin_selection(
10042 Point::new(2, 1).to_display_point(&snapshot),
10043 true,
10044 1,
10045 window,
10046 cx,
10047 );
10048 assert_eq!(
10049 editor.selections.ranges(cx),
10050 [
10051 Point::new(1, 3)..Point::new(1, 3),
10052 Point::new(2, 1)..Point::new(2, 1),
10053 ]
10054 );
10055 editor
10056 });
10057
10058 // Refreshing selections is a no-op when excerpts haven't changed.
10059 _ = editor.update(cx, |editor, window, cx| {
10060 editor.change_selections(None, window, cx, |s| s.refresh());
10061 assert_eq!(
10062 editor.selections.ranges(cx),
10063 [
10064 Point::new(1, 3)..Point::new(1, 3),
10065 Point::new(2, 1)..Point::new(2, 1),
10066 ]
10067 );
10068 });
10069
10070 multibuffer.update(cx, |multibuffer, cx| {
10071 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10072 });
10073 _ = editor.update(cx, |editor, window, cx| {
10074 // Removing an excerpt causes the first selection to become degenerate.
10075 assert_eq!(
10076 editor.selections.ranges(cx),
10077 [
10078 Point::new(0, 0)..Point::new(0, 0),
10079 Point::new(0, 1)..Point::new(0, 1)
10080 ]
10081 );
10082
10083 // Refreshing selections will relocate the first selection to the original buffer
10084 // location.
10085 editor.change_selections(None, window, cx, |s| s.refresh());
10086 assert_eq!(
10087 editor.selections.ranges(cx),
10088 [
10089 Point::new(0, 1)..Point::new(0, 1),
10090 Point::new(0, 3)..Point::new(0, 3)
10091 ]
10092 );
10093 assert!(editor.selections.pending_anchor().is_some());
10094 });
10095}
10096
10097#[gpui::test]
10098fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10099 init_test(cx, |_| {});
10100
10101 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10102 let mut excerpt1_id = None;
10103 let multibuffer = cx.new(|cx| {
10104 let mut multibuffer = MultiBuffer::new(ReadWrite);
10105 excerpt1_id = multibuffer
10106 .push_excerpts(
10107 buffer.clone(),
10108 [
10109 ExcerptRange {
10110 context: Point::new(0, 0)..Point::new(1, 4),
10111 primary: None,
10112 },
10113 ExcerptRange {
10114 context: Point::new(1, 0)..Point::new(2, 4),
10115 primary: None,
10116 },
10117 ],
10118 cx,
10119 )
10120 .into_iter()
10121 .next();
10122 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10123 multibuffer
10124 });
10125
10126 let editor = cx.add_window(|window, cx| {
10127 let mut editor = build_editor(multibuffer.clone(), window, cx);
10128 let snapshot = editor.snapshot(window, cx);
10129 editor.begin_selection(
10130 Point::new(1, 3).to_display_point(&snapshot),
10131 false,
10132 1,
10133 window,
10134 cx,
10135 );
10136 assert_eq!(
10137 editor.selections.ranges(cx),
10138 [Point::new(1, 3)..Point::new(1, 3)]
10139 );
10140 editor
10141 });
10142
10143 multibuffer.update(cx, |multibuffer, cx| {
10144 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10145 });
10146 _ = editor.update(cx, |editor, window, cx| {
10147 assert_eq!(
10148 editor.selections.ranges(cx),
10149 [Point::new(0, 0)..Point::new(0, 0)]
10150 );
10151
10152 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10153 editor.change_selections(None, window, cx, |s| s.refresh());
10154 assert_eq!(
10155 editor.selections.ranges(cx),
10156 [Point::new(0, 3)..Point::new(0, 3)]
10157 );
10158 assert!(editor.selections.pending_anchor().is_some());
10159 });
10160}
10161
10162#[gpui::test]
10163async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10164 init_test(cx, |_| {});
10165
10166 let language = Arc::new(
10167 Language::new(
10168 LanguageConfig {
10169 brackets: BracketPairConfig {
10170 pairs: vec![
10171 BracketPair {
10172 start: "{".to_string(),
10173 end: "}".to_string(),
10174 close: true,
10175 surround: true,
10176 newline: true,
10177 },
10178 BracketPair {
10179 start: "/* ".to_string(),
10180 end: " */".to_string(),
10181 close: true,
10182 surround: true,
10183 newline: true,
10184 },
10185 ],
10186 ..Default::default()
10187 },
10188 ..Default::default()
10189 },
10190 Some(tree_sitter_rust::LANGUAGE.into()),
10191 )
10192 .with_indents_query("")
10193 .unwrap(),
10194 );
10195
10196 let text = concat!(
10197 "{ }\n", //
10198 " x\n", //
10199 " /* */\n", //
10200 "x\n", //
10201 "{{} }\n", //
10202 );
10203
10204 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10205 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10206 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10207 editor
10208 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10209 .await;
10210
10211 editor.update_in(cx, |editor, window, cx| {
10212 editor.change_selections(None, window, cx, |s| {
10213 s.select_display_ranges([
10214 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10215 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10216 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10217 ])
10218 });
10219 editor.newline(&Newline, window, cx);
10220
10221 assert_eq!(
10222 editor.buffer().read(cx).read(cx).text(),
10223 concat!(
10224 "{ \n", // Suppress rustfmt
10225 "\n", //
10226 "}\n", //
10227 " x\n", //
10228 " /* \n", //
10229 " \n", //
10230 " */\n", //
10231 "x\n", //
10232 "{{} \n", //
10233 "}\n", //
10234 )
10235 );
10236 });
10237}
10238
10239#[gpui::test]
10240fn test_highlighted_ranges(cx: &mut TestAppContext) {
10241 init_test(cx, |_| {});
10242
10243 let editor = cx.add_window(|window, cx| {
10244 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10245 build_editor(buffer.clone(), window, cx)
10246 });
10247
10248 _ = editor.update(cx, |editor, window, cx| {
10249 struct Type1;
10250 struct Type2;
10251
10252 let buffer = editor.buffer.read(cx).snapshot(cx);
10253
10254 let anchor_range =
10255 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10256
10257 editor.highlight_background::<Type1>(
10258 &[
10259 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10260 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10261 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10262 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10263 ],
10264 |_| Hsla::red(),
10265 cx,
10266 );
10267 editor.highlight_background::<Type2>(
10268 &[
10269 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10270 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10271 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10272 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10273 ],
10274 |_| Hsla::green(),
10275 cx,
10276 );
10277
10278 let snapshot = editor.snapshot(window, cx);
10279 let mut highlighted_ranges = editor.background_highlights_in_range(
10280 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10281 &snapshot,
10282 cx.theme().colors(),
10283 );
10284 // Enforce a consistent ordering based on color without relying on the ordering of the
10285 // highlight's `TypeId` which is non-executor.
10286 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10287 assert_eq!(
10288 highlighted_ranges,
10289 &[
10290 (
10291 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10292 Hsla::red(),
10293 ),
10294 (
10295 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10296 Hsla::red(),
10297 ),
10298 (
10299 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10300 Hsla::green(),
10301 ),
10302 (
10303 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10304 Hsla::green(),
10305 ),
10306 ]
10307 );
10308 assert_eq!(
10309 editor.background_highlights_in_range(
10310 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10311 &snapshot,
10312 cx.theme().colors(),
10313 ),
10314 &[(
10315 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10316 Hsla::red(),
10317 )]
10318 );
10319 });
10320}
10321
10322#[gpui::test]
10323async fn test_following(cx: &mut TestAppContext) {
10324 init_test(cx, |_| {});
10325
10326 let fs = FakeFs::new(cx.executor());
10327 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10328
10329 let buffer = project.update(cx, |project, cx| {
10330 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10331 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10332 });
10333 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10334 let follower = cx.update(|cx| {
10335 cx.open_window(
10336 WindowOptions {
10337 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10338 gpui::Point::new(px(0.), px(0.)),
10339 gpui::Point::new(px(10.), px(80.)),
10340 ))),
10341 ..Default::default()
10342 },
10343 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10344 )
10345 .unwrap()
10346 });
10347
10348 let is_still_following = Rc::new(RefCell::new(true));
10349 let follower_edit_event_count = Rc::new(RefCell::new(0));
10350 let pending_update = Rc::new(RefCell::new(None));
10351 let leader_entity = leader.root(cx).unwrap();
10352 let follower_entity = follower.root(cx).unwrap();
10353 _ = follower.update(cx, {
10354 let update = pending_update.clone();
10355 let is_still_following = is_still_following.clone();
10356 let follower_edit_event_count = follower_edit_event_count.clone();
10357 |_, window, cx| {
10358 cx.subscribe_in(
10359 &leader_entity,
10360 window,
10361 move |_, leader, event, window, cx| {
10362 leader.read(cx).add_event_to_update_proto(
10363 event,
10364 &mut update.borrow_mut(),
10365 window,
10366 cx,
10367 );
10368 },
10369 )
10370 .detach();
10371
10372 cx.subscribe_in(
10373 &follower_entity,
10374 window,
10375 move |_, _, event: &EditorEvent, _window, _cx| {
10376 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10377 *is_still_following.borrow_mut() = false;
10378 }
10379
10380 if let EditorEvent::BufferEdited = event {
10381 *follower_edit_event_count.borrow_mut() += 1;
10382 }
10383 },
10384 )
10385 .detach();
10386 }
10387 });
10388
10389 // Update the selections only
10390 _ = leader.update(cx, |leader, window, cx| {
10391 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10392 });
10393 follower
10394 .update(cx, |follower, window, cx| {
10395 follower.apply_update_proto(
10396 &project,
10397 pending_update.borrow_mut().take().unwrap(),
10398 window,
10399 cx,
10400 )
10401 })
10402 .unwrap()
10403 .await
10404 .unwrap();
10405 _ = follower.update(cx, |follower, _, cx| {
10406 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10407 });
10408 assert!(*is_still_following.borrow());
10409 assert_eq!(*follower_edit_event_count.borrow(), 0);
10410
10411 // Update the scroll position only
10412 _ = leader.update(cx, |leader, window, cx| {
10413 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10414 });
10415 follower
10416 .update(cx, |follower, window, cx| {
10417 follower.apply_update_proto(
10418 &project,
10419 pending_update.borrow_mut().take().unwrap(),
10420 window,
10421 cx,
10422 )
10423 })
10424 .unwrap()
10425 .await
10426 .unwrap();
10427 assert_eq!(
10428 follower
10429 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10430 .unwrap(),
10431 gpui::Point::new(1.5, 3.5)
10432 );
10433 assert!(*is_still_following.borrow());
10434 assert_eq!(*follower_edit_event_count.borrow(), 0);
10435
10436 // Update the selections and scroll position. The follower's scroll position is updated
10437 // via autoscroll, not via the leader's exact scroll position.
10438 _ = leader.update(cx, |leader, window, cx| {
10439 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10440 leader.request_autoscroll(Autoscroll::newest(), cx);
10441 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10442 });
10443 follower
10444 .update(cx, |follower, window, cx| {
10445 follower.apply_update_proto(
10446 &project,
10447 pending_update.borrow_mut().take().unwrap(),
10448 window,
10449 cx,
10450 )
10451 })
10452 .unwrap()
10453 .await
10454 .unwrap();
10455 _ = follower.update(cx, |follower, _, cx| {
10456 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10457 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10458 });
10459 assert!(*is_still_following.borrow());
10460
10461 // Creating a pending selection that precedes another selection
10462 _ = leader.update(cx, |leader, window, cx| {
10463 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10464 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10465 });
10466 follower
10467 .update(cx, |follower, window, cx| {
10468 follower.apply_update_proto(
10469 &project,
10470 pending_update.borrow_mut().take().unwrap(),
10471 window,
10472 cx,
10473 )
10474 })
10475 .unwrap()
10476 .await
10477 .unwrap();
10478 _ = follower.update(cx, |follower, _, cx| {
10479 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10480 });
10481 assert!(*is_still_following.borrow());
10482
10483 // Extend the pending selection so that it surrounds another selection
10484 _ = leader.update(cx, |leader, window, cx| {
10485 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10486 });
10487 follower
10488 .update(cx, |follower, window, cx| {
10489 follower.apply_update_proto(
10490 &project,
10491 pending_update.borrow_mut().take().unwrap(),
10492 window,
10493 cx,
10494 )
10495 })
10496 .unwrap()
10497 .await
10498 .unwrap();
10499 _ = follower.update(cx, |follower, _, cx| {
10500 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10501 });
10502
10503 // Scrolling locally breaks the follow
10504 _ = follower.update(cx, |follower, window, cx| {
10505 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10506 follower.set_scroll_anchor(
10507 ScrollAnchor {
10508 anchor: top_anchor,
10509 offset: gpui::Point::new(0.0, 0.5),
10510 },
10511 window,
10512 cx,
10513 );
10514 });
10515 assert!(!(*is_still_following.borrow()));
10516}
10517
10518#[gpui::test]
10519async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10520 init_test(cx, |_| {});
10521
10522 let fs = FakeFs::new(cx.executor());
10523 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10524 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10525 let pane = workspace
10526 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10527 .unwrap();
10528
10529 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10530
10531 let leader = pane.update_in(cx, |_, window, cx| {
10532 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10533 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10534 });
10535
10536 // Start following the editor when it has no excerpts.
10537 let mut state_message =
10538 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10539 let workspace_entity = workspace.root(cx).unwrap();
10540 let follower_1 = cx
10541 .update_window(*workspace.deref(), |_, window, cx| {
10542 Editor::from_state_proto(
10543 workspace_entity,
10544 ViewId {
10545 creator: Default::default(),
10546 id: 0,
10547 },
10548 &mut state_message,
10549 window,
10550 cx,
10551 )
10552 })
10553 .unwrap()
10554 .unwrap()
10555 .await
10556 .unwrap();
10557
10558 let update_message = Rc::new(RefCell::new(None));
10559 follower_1.update_in(cx, {
10560 let update = update_message.clone();
10561 |_, window, cx| {
10562 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10563 leader.read(cx).add_event_to_update_proto(
10564 event,
10565 &mut update.borrow_mut(),
10566 window,
10567 cx,
10568 );
10569 })
10570 .detach();
10571 }
10572 });
10573
10574 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10575 (
10576 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10577 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10578 )
10579 });
10580
10581 // Insert some excerpts.
10582 leader.update(cx, |leader, cx| {
10583 leader.buffer.update(cx, |multibuffer, cx| {
10584 let excerpt_ids = multibuffer.push_excerpts(
10585 buffer_1.clone(),
10586 [
10587 ExcerptRange {
10588 context: 1..6,
10589 primary: None,
10590 },
10591 ExcerptRange {
10592 context: 12..15,
10593 primary: None,
10594 },
10595 ExcerptRange {
10596 context: 0..3,
10597 primary: None,
10598 },
10599 ],
10600 cx,
10601 );
10602 multibuffer.insert_excerpts_after(
10603 excerpt_ids[0],
10604 buffer_2.clone(),
10605 [
10606 ExcerptRange {
10607 context: 8..12,
10608 primary: None,
10609 },
10610 ExcerptRange {
10611 context: 0..6,
10612 primary: None,
10613 },
10614 ],
10615 cx,
10616 );
10617 });
10618 });
10619
10620 // Apply the update of adding the excerpts.
10621 follower_1
10622 .update_in(cx, |follower, window, cx| {
10623 follower.apply_update_proto(
10624 &project,
10625 update_message.borrow().clone().unwrap(),
10626 window,
10627 cx,
10628 )
10629 })
10630 .await
10631 .unwrap();
10632 assert_eq!(
10633 follower_1.update(cx, |editor, cx| editor.text(cx)),
10634 leader.update(cx, |editor, cx| editor.text(cx))
10635 );
10636 update_message.borrow_mut().take();
10637
10638 // Start following separately after it already has excerpts.
10639 let mut state_message =
10640 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10641 let workspace_entity = workspace.root(cx).unwrap();
10642 let follower_2 = cx
10643 .update_window(*workspace.deref(), |_, window, cx| {
10644 Editor::from_state_proto(
10645 workspace_entity,
10646 ViewId {
10647 creator: Default::default(),
10648 id: 0,
10649 },
10650 &mut state_message,
10651 window,
10652 cx,
10653 )
10654 })
10655 .unwrap()
10656 .unwrap()
10657 .await
10658 .unwrap();
10659 assert_eq!(
10660 follower_2.update(cx, |editor, cx| editor.text(cx)),
10661 leader.update(cx, |editor, cx| editor.text(cx))
10662 );
10663
10664 // Remove some excerpts.
10665 leader.update(cx, |leader, cx| {
10666 leader.buffer.update(cx, |multibuffer, cx| {
10667 let excerpt_ids = multibuffer.excerpt_ids();
10668 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10669 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10670 });
10671 });
10672
10673 // Apply the update of removing the excerpts.
10674 follower_1
10675 .update_in(cx, |follower, window, cx| {
10676 follower.apply_update_proto(
10677 &project,
10678 update_message.borrow().clone().unwrap(),
10679 window,
10680 cx,
10681 )
10682 })
10683 .await
10684 .unwrap();
10685 follower_2
10686 .update_in(cx, |follower, window, cx| {
10687 follower.apply_update_proto(
10688 &project,
10689 update_message.borrow().clone().unwrap(),
10690 window,
10691 cx,
10692 )
10693 })
10694 .await
10695 .unwrap();
10696 update_message.borrow_mut().take();
10697 assert_eq!(
10698 follower_1.update(cx, |editor, cx| editor.text(cx)),
10699 leader.update(cx, |editor, cx| editor.text(cx))
10700 );
10701}
10702
10703#[gpui::test]
10704async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
10705 init_test(cx, |_| {});
10706
10707 let mut cx = EditorTestContext::new(cx).await;
10708 let lsp_store =
10709 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10710
10711 cx.set_state(indoc! {"
10712 ˇfn func(abc def: i32) -> u32 {
10713 }
10714 "});
10715
10716 cx.update(|_, cx| {
10717 lsp_store.update(cx, |lsp_store, cx| {
10718 lsp_store
10719 .update_diagnostics(
10720 LanguageServerId(0),
10721 lsp::PublishDiagnosticsParams {
10722 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10723 version: None,
10724 diagnostics: vec![
10725 lsp::Diagnostic {
10726 range: lsp::Range::new(
10727 lsp::Position::new(0, 11),
10728 lsp::Position::new(0, 12),
10729 ),
10730 severity: Some(lsp::DiagnosticSeverity::ERROR),
10731 ..Default::default()
10732 },
10733 lsp::Diagnostic {
10734 range: lsp::Range::new(
10735 lsp::Position::new(0, 12),
10736 lsp::Position::new(0, 15),
10737 ),
10738 severity: Some(lsp::DiagnosticSeverity::ERROR),
10739 ..Default::default()
10740 },
10741 lsp::Diagnostic {
10742 range: lsp::Range::new(
10743 lsp::Position::new(0, 25),
10744 lsp::Position::new(0, 28),
10745 ),
10746 severity: Some(lsp::DiagnosticSeverity::ERROR),
10747 ..Default::default()
10748 },
10749 ],
10750 },
10751 &[],
10752 cx,
10753 )
10754 .unwrap()
10755 });
10756 });
10757
10758 executor.run_until_parked();
10759
10760 cx.update_editor(|editor, window, cx| {
10761 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10762 });
10763
10764 cx.assert_editor_state(indoc! {"
10765 fn func(abc def: i32) -> ˇu32 {
10766 }
10767 "});
10768
10769 cx.update_editor(|editor, window, cx| {
10770 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10771 });
10772
10773 cx.assert_editor_state(indoc! {"
10774 fn func(abc ˇdef: i32) -> u32 {
10775 }
10776 "});
10777
10778 cx.update_editor(|editor, window, cx| {
10779 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10780 });
10781
10782 cx.assert_editor_state(indoc! {"
10783 fn func(abcˇ def: i32) -> u32 {
10784 }
10785 "});
10786
10787 cx.update_editor(|editor, window, cx| {
10788 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10789 });
10790
10791 cx.assert_editor_state(indoc! {"
10792 fn func(abc def: i32) -> ˇu32 {
10793 }
10794 "});
10795}
10796
10797#[gpui::test]
10798async fn cycle_through_same_place_diagnostics(
10799 executor: BackgroundExecutor,
10800 cx: &mut TestAppContext,
10801) {
10802 init_test(cx, |_| {});
10803
10804 let mut cx = EditorTestContext::new(cx).await;
10805 let lsp_store =
10806 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10807
10808 cx.set_state(indoc! {"
10809 ˇfn func(abc def: i32) -> u32 {
10810 }
10811 "});
10812
10813 cx.update(|_, cx| {
10814 lsp_store.update(cx, |lsp_store, cx| {
10815 lsp_store
10816 .update_diagnostics(
10817 LanguageServerId(0),
10818 lsp::PublishDiagnosticsParams {
10819 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10820 version: None,
10821 diagnostics: vec![
10822 lsp::Diagnostic {
10823 range: lsp::Range::new(
10824 lsp::Position::new(0, 11),
10825 lsp::Position::new(0, 12),
10826 ),
10827 severity: Some(lsp::DiagnosticSeverity::ERROR),
10828 ..Default::default()
10829 },
10830 lsp::Diagnostic {
10831 range: lsp::Range::new(
10832 lsp::Position::new(0, 12),
10833 lsp::Position::new(0, 15),
10834 ),
10835 severity: Some(lsp::DiagnosticSeverity::ERROR),
10836 ..Default::default()
10837 },
10838 lsp::Diagnostic {
10839 range: lsp::Range::new(
10840 lsp::Position::new(0, 12),
10841 lsp::Position::new(0, 15),
10842 ),
10843 severity: Some(lsp::DiagnosticSeverity::ERROR),
10844 ..Default::default()
10845 },
10846 lsp::Diagnostic {
10847 range: lsp::Range::new(
10848 lsp::Position::new(0, 25),
10849 lsp::Position::new(0, 28),
10850 ),
10851 severity: Some(lsp::DiagnosticSeverity::ERROR),
10852 ..Default::default()
10853 },
10854 ],
10855 },
10856 &[],
10857 cx,
10858 )
10859 .unwrap()
10860 });
10861 });
10862 executor.run_until_parked();
10863
10864 //// Backward
10865
10866 // Fourth diagnostic
10867 cx.update_editor(|editor, window, cx| {
10868 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10869 });
10870 cx.assert_editor_state(indoc! {"
10871 fn func(abc def: i32) -> ˇu32 {
10872 }
10873 "});
10874
10875 // Third diagnostic
10876 cx.update_editor(|editor, window, cx| {
10877 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10878 });
10879 cx.assert_editor_state(indoc! {"
10880 fn func(abc ˇdef: i32) -> u32 {
10881 }
10882 "});
10883
10884 // Second diagnostic, same place
10885 cx.update_editor(|editor, window, cx| {
10886 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10887 });
10888 cx.assert_editor_state(indoc! {"
10889 fn func(abc ˇdef: i32) -> u32 {
10890 }
10891 "});
10892
10893 // First diagnostic
10894 cx.update_editor(|editor, window, cx| {
10895 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10896 });
10897 cx.assert_editor_state(indoc! {"
10898 fn func(abcˇ def: i32) -> u32 {
10899 }
10900 "});
10901
10902 // Wrapped over, fourth diagnostic
10903 cx.update_editor(|editor, window, cx| {
10904 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10905 });
10906 cx.assert_editor_state(indoc! {"
10907 fn func(abc def: i32) -> ˇu32 {
10908 }
10909 "});
10910
10911 cx.update_editor(|editor, window, cx| {
10912 editor.move_to_beginning(&MoveToBeginning, window, cx);
10913 });
10914 cx.assert_editor_state(indoc! {"
10915 ˇfn func(abc def: i32) -> u32 {
10916 }
10917 "});
10918
10919 //// Forward
10920
10921 // First diagnostic
10922 cx.update_editor(|editor, window, cx| {
10923 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10924 });
10925 cx.assert_editor_state(indoc! {"
10926 fn func(abcˇ def: i32) -> u32 {
10927 }
10928 "});
10929
10930 // Second diagnostic
10931 cx.update_editor(|editor, window, cx| {
10932 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10933 });
10934 cx.assert_editor_state(indoc! {"
10935 fn func(abc ˇdef: i32) -> u32 {
10936 }
10937 "});
10938
10939 // Third diagnostic, same place
10940 cx.update_editor(|editor, window, cx| {
10941 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10942 });
10943 cx.assert_editor_state(indoc! {"
10944 fn func(abc ˇdef: i32) -> u32 {
10945 }
10946 "});
10947
10948 // Fourth diagnostic
10949 cx.update_editor(|editor, window, cx| {
10950 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10951 });
10952 cx.assert_editor_state(indoc! {"
10953 fn func(abc def: i32) -> ˇu32 {
10954 }
10955 "});
10956
10957 // Wrapped around, first diagnostic
10958 cx.update_editor(|editor, window, cx| {
10959 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10960 });
10961 cx.assert_editor_state(indoc! {"
10962 fn func(abcˇ def: i32) -> u32 {
10963 }
10964 "});
10965}
10966
10967#[gpui::test]
10968async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10969 init_test(cx, |_| {});
10970
10971 let mut cx = EditorTestContext::new(cx).await;
10972
10973 cx.set_state(indoc! {"
10974 fn func(abˇc def: i32) -> u32 {
10975 }
10976 "});
10977 let lsp_store =
10978 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10979
10980 cx.update(|_, cx| {
10981 lsp_store.update(cx, |lsp_store, cx| {
10982 lsp_store.update_diagnostics(
10983 LanguageServerId(0),
10984 lsp::PublishDiagnosticsParams {
10985 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10986 version: None,
10987 diagnostics: vec![lsp::Diagnostic {
10988 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10989 severity: Some(lsp::DiagnosticSeverity::ERROR),
10990 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10991 ..Default::default()
10992 }],
10993 },
10994 &[],
10995 cx,
10996 )
10997 })
10998 }).unwrap();
10999 cx.run_until_parked();
11000 cx.update_editor(|editor, window, cx| {
11001 hover_popover::hover(editor, &Default::default(), window, cx)
11002 });
11003 cx.run_until_parked();
11004 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11005}
11006
11007#[gpui::test]
11008async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11009 init_test(cx, |_| {});
11010
11011 let mut cx = EditorTestContext::new(cx).await;
11012
11013 let diff_base = r#"
11014 use some::mod;
11015
11016 const A: u32 = 42;
11017
11018 fn main() {
11019 println!("hello");
11020
11021 println!("world");
11022 }
11023 "#
11024 .unindent();
11025
11026 // Edits are modified, removed, modified, added
11027 cx.set_state(
11028 &r#"
11029 use some::modified;
11030
11031 ˇ
11032 fn main() {
11033 println!("hello there");
11034
11035 println!("around the");
11036 println!("world");
11037 }
11038 "#
11039 .unindent(),
11040 );
11041
11042 cx.set_diff_base(&diff_base);
11043 executor.run_until_parked();
11044
11045 cx.update_editor(|editor, window, cx| {
11046 //Wrap around the bottom of the buffer
11047 for _ in 0..3 {
11048 editor.go_to_next_hunk(&GoToHunk, window, cx);
11049 }
11050 });
11051
11052 cx.assert_editor_state(
11053 &r#"
11054 ˇuse some::modified;
11055
11056
11057 fn main() {
11058 println!("hello there");
11059
11060 println!("around the");
11061 println!("world");
11062 }
11063 "#
11064 .unindent(),
11065 );
11066
11067 cx.update_editor(|editor, window, cx| {
11068 //Wrap around the top of the buffer
11069 for _ in 0..2 {
11070 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11071 }
11072 });
11073
11074 cx.assert_editor_state(
11075 &r#"
11076 use some::modified;
11077
11078
11079 fn main() {
11080 ˇ println!("hello there");
11081
11082 println!("around the");
11083 println!("world");
11084 }
11085 "#
11086 .unindent(),
11087 );
11088
11089 cx.update_editor(|editor, window, cx| {
11090 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11091 });
11092
11093 cx.assert_editor_state(
11094 &r#"
11095 use some::modified;
11096
11097 ˇ
11098 fn main() {
11099 println!("hello there");
11100
11101 println!("around the");
11102 println!("world");
11103 }
11104 "#
11105 .unindent(),
11106 );
11107
11108 cx.update_editor(|editor, window, cx| {
11109 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11110 });
11111
11112 cx.assert_editor_state(
11113 &r#"
11114 ˇuse some::modified;
11115
11116
11117 fn main() {
11118 println!("hello there");
11119
11120 println!("around the");
11121 println!("world");
11122 }
11123 "#
11124 .unindent(),
11125 );
11126
11127 cx.update_editor(|editor, window, cx| {
11128 for _ in 0..2 {
11129 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11130 }
11131 });
11132
11133 cx.assert_editor_state(
11134 &r#"
11135 use some::modified;
11136
11137
11138 fn main() {
11139 ˇ println!("hello there");
11140
11141 println!("around the");
11142 println!("world");
11143 }
11144 "#
11145 .unindent(),
11146 );
11147
11148 cx.update_editor(|editor, window, cx| {
11149 editor.fold(&Fold, window, cx);
11150 });
11151
11152 cx.update_editor(|editor, window, cx| {
11153 editor.go_to_next_hunk(&GoToHunk, window, cx);
11154 });
11155
11156 cx.assert_editor_state(
11157 &r#"
11158 ˇuse some::modified;
11159
11160
11161 fn main() {
11162 println!("hello there");
11163
11164 println!("around the");
11165 println!("world");
11166 }
11167 "#
11168 .unindent(),
11169 );
11170}
11171
11172#[test]
11173fn test_split_words() {
11174 fn split(text: &str) -> Vec<&str> {
11175 split_words(text).collect()
11176 }
11177
11178 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11179 assert_eq!(split("hello_world"), &["hello_", "world"]);
11180 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11181 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11182 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11183 assert_eq!(split("helloworld"), &["helloworld"]);
11184
11185 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11186}
11187
11188#[gpui::test]
11189async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11190 init_test(cx, |_| {});
11191
11192 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11193 let mut assert = |before, after| {
11194 let _state_context = cx.set_state(before);
11195 cx.run_until_parked();
11196 cx.update_editor(|editor, window, cx| {
11197 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11198 });
11199 cx.assert_editor_state(after);
11200 };
11201
11202 // Outside bracket jumps to outside of matching bracket
11203 assert("console.logˇ(var);", "console.log(var)ˇ;");
11204 assert("console.log(var)ˇ;", "console.logˇ(var);");
11205
11206 // Inside bracket jumps to inside of matching bracket
11207 assert("console.log(ˇvar);", "console.log(varˇ);");
11208 assert("console.log(varˇ);", "console.log(ˇvar);");
11209
11210 // When outside a bracket and inside, favor jumping to the inside bracket
11211 assert(
11212 "console.log('foo', [1, 2, 3]ˇ);",
11213 "console.log(ˇ'foo', [1, 2, 3]);",
11214 );
11215 assert(
11216 "console.log(ˇ'foo', [1, 2, 3]);",
11217 "console.log('foo', [1, 2, 3]ˇ);",
11218 );
11219
11220 // Bias forward if two options are equally likely
11221 assert(
11222 "let result = curried_fun()ˇ();",
11223 "let result = curried_fun()()ˇ;",
11224 );
11225
11226 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11227 assert(
11228 indoc! {"
11229 function test() {
11230 console.log('test')ˇ
11231 }"},
11232 indoc! {"
11233 function test() {
11234 console.logˇ('test')
11235 }"},
11236 );
11237}
11238
11239#[gpui::test]
11240async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11241 init_test(cx, |_| {});
11242
11243 let fs = FakeFs::new(cx.executor());
11244 fs.insert_tree(
11245 path!("/a"),
11246 json!({
11247 "main.rs": "fn main() { let a = 5; }",
11248 "other.rs": "// Test file",
11249 }),
11250 )
11251 .await;
11252 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11253
11254 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11255 language_registry.add(Arc::new(Language::new(
11256 LanguageConfig {
11257 name: "Rust".into(),
11258 matcher: LanguageMatcher {
11259 path_suffixes: vec!["rs".to_string()],
11260 ..Default::default()
11261 },
11262 brackets: BracketPairConfig {
11263 pairs: vec![BracketPair {
11264 start: "{".to_string(),
11265 end: "}".to_string(),
11266 close: true,
11267 surround: true,
11268 newline: true,
11269 }],
11270 disabled_scopes_by_bracket_ix: Vec::new(),
11271 },
11272 ..Default::default()
11273 },
11274 Some(tree_sitter_rust::LANGUAGE.into()),
11275 )));
11276 let mut fake_servers = language_registry.register_fake_lsp(
11277 "Rust",
11278 FakeLspAdapter {
11279 capabilities: lsp::ServerCapabilities {
11280 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11281 first_trigger_character: "{".to_string(),
11282 more_trigger_character: None,
11283 }),
11284 ..Default::default()
11285 },
11286 ..Default::default()
11287 },
11288 );
11289
11290 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11291
11292 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11293
11294 let worktree_id = workspace
11295 .update(cx, |workspace, _, cx| {
11296 workspace.project().update(cx, |project, cx| {
11297 project.worktrees(cx).next().unwrap().read(cx).id()
11298 })
11299 })
11300 .unwrap();
11301
11302 let buffer = project
11303 .update(cx, |project, cx| {
11304 project.open_local_buffer(path!("/a/main.rs"), cx)
11305 })
11306 .await
11307 .unwrap();
11308 let editor_handle = workspace
11309 .update(cx, |workspace, window, cx| {
11310 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11311 })
11312 .unwrap()
11313 .await
11314 .unwrap()
11315 .downcast::<Editor>()
11316 .unwrap();
11317
11318 cx.executor().start_waiting();
11319 let fake_server = fake_servers.next().await.unwrap();
11320
11321 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11322 assert_eq!(
11323 params.text_document_position.text_document.uri,
11324 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11325 );
11326 assert_eq!(
11327 params.text_document_position.position,
11328 lsp::Position::new(0, 21),
11329 );
11330
11331 Ok(Some(vec![lsp::TextEdit {
11332 new_text: "]".to_string(),
11333 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11334 }]))
11335 });
11336
11337 editor_handle.update_in(cx, |editor, window, cx| {
11338 window.focus(&editor.focus_handle(cx));
11339 editor.change_selections(None, window, cx, |s| {
11340 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11341 });
11342 editor.handle_input("{", window, cx);
11343 });
11344
11345 cx.executor().run_until_parked();
11346
11347 buffer.update(cx, |buffer, _| {
11348 assert_eq!(
11349 buffer.text(),
11350 "fn main() { let a = {5}; }",
11351 "No extra braces from on type formatting should appear in the buffer"
11352 )
11353 });
11354}
11355
11356#[gpui::test]
11357async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11358 init_test(cx, |_| {});
11359
11360 let fs = FakeFs::new(cx.executor());
11361 fs.insert_tree(
11362 path!("/a"),
11363 json!({
11364 "main.rs": "fn main() { let a = 5; }",
11365 "other.rs": "// Test file",
11366 }),
11367 )
11368 .await;
11369
11370 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11371
11372 let server_restarts = Arc::new(AtomicUsize::new(0));
11373 let closure_restarts = Arc::clone(&server_restarts);
11374 let language_server_name = "test language server";
11375 let language_name: LanguageName = "Rust".into();
11376
11377 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11378 language_registry.add(Arc::new(Language::new(
11379 LanguageConfig {
11380 name: language_name.clone(),
11381 matcher: LanguageMatcher {
11382 path_suffixes: vec!["rs".to_string()],
11383 ..Default::default()
11384 },
11385 ..Default::default()
11386 },
11387 Some(tree_sitter_rust::LANGUAGE.into()),
11388 )));
11389 let mut fake_servers = language_registry.register_fake_lsp(
11390 "Rust",
11391 FakeLspAdapter {
11392 name: language_server_name,
11393 initialization_options: Some(json!({
11394 "testOptionValue": true
11395 })),
11396 initializer: Some(Box::new(move |fake_server| {
11397 let task_restarts = Arc::clone(&closure_restarts);
11398 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11399 task_restarts.fetch_add(1, atomic::Ordering::Release);
11400 futures::future::ready(Ok(()))
11401 });
11402 })),
11403 ..Default::default()
11404 },
11405 );
11406
11407 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11408 let _buffer = project
11409 .update(cx, |project, cx| {
11410 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11411 })
11412 .await
11413 .unwrap();
11414 let _fake_server = fake_servers.next().await.unwrap();
11415 update_test_language_settings(cx, |language_settings| {
11416 language_settings.languages.insert(
11417 language_name.clone(),
11418 LanguageSettingsContent {
11419 tab_size: NonZeroU32::new(8),
11420 ..Default::default()
11421 },
11422 );
11423 });
11424 cx.executor().run_until_parked();
11425 assert_eq!(
11426 server_restarts.load(atomic::Ordering::Acquire),
11427 0,
11428 "Should not restart LSP server on an unrelated change"
11429 );
11430
11431 update_test_project_settings(cx, |project_settings| {
11432 project_settings.lsp.insert(
11433 "Some other server name".into(),
11434 LspSettings {
11435 binary: None,
11436 settings: None,
11437 initialization_options: Some(json!({
11438 "some other init value": false
11439 })),
11440 },
11441 );
11442 });
11443 cx.executor().run_until_parked();
11444 assert_eq!(
11445 server_restarts.load(atomic::Ordering::Acquire),
11446 0,
11447 "Should not restart LSP server on an unrelated LSP settings change"
11448 );
11449
11450 update_test_project_settings(cx, |project_settings| {
11451 project_settings.lsp.insert(
11452 language_server_name.into(),
11453 LspSettings {
11454 binary: None,
11455 settings: None,
11456 initialization_options: Some(json!({
11457 "anotherInitValue": false
11458 })),
11459 },
11460 );
11461 });
11462 cx.executor().run_until_parked();
11463 assert_eq!(
11464 server_restarts.load(atomic::Ordering::Acquire),
11465 1,
11466 "Should restart LSP server on a related LSP settings change"
11467 );
11468
11469 update_test_project_settings(cx, |project_settings| {
11470 project_settings.lsp.insert(
11471 language_server_name.into(),
11472 LspSettings {
11473 binary: None,
11474 settings: None,
11475 initialization_options: Some(json!({
11476 "anotherInitValue": false
11477 })),
11478 },
11479 );
11480 });
11481 cx.executor().run_until_parked();
11482 assert_eq!(
11483 server_restarts.load(atomic::Ordering::Acquire),
11484 1,
11485 "Should not restart LSP server on a related LSP settings change that is the same"
11486 );
11487
11488 update_test_project_settings(cx, |project_settings| {
11489 project_settings.lsp.insert(
11490 language_server_name.into(),
11491 LspSettings {
11492 binary: None,
11493 settings: None,
11494 initialization_options: None,
11495 },
11496 );
11497 });
11498 cx.executor().run_until_parked();
11499 assert_eq!(
11500 server_restarts.load(atomic::Ordering::Acquire),
11501 2,
11502 "Should restart LSP server on another related LSP settings change"
11503 );
11504}
11505
11506#[gpui::test]
11507async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11508 init_test(cx, |_| {});
11509
11510 let mut cx = EditorLspTestContext::new_rust(
11511 lsp::ServerCapabilities {
11512 completion_provider: Some(lsp::CompletionOptions {
11513 trigger_characters: Some(vec![".".to_string()]),
11514 resolve_provider: Some(true),
11515 ..Default::default()
11516 }),
11517 ..Default::default()
11518 },
11519 cx,
11520 )
11521 .await;
11522
11523 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11524 cx.simulate_keystroke(".");
11525 let completion_item = lsp::CompletionItem {
11526 label: "some".into(),
11527 kind: Some(lsp::CompletionItemKind::SNIPPET),
11528 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11529 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11530 kind: lsp::MarkupKind::Markdown,
11531 value: "```rust\nSome(2)\n```".to_string(),
11532 })),
11533 deprecated: Some(false),
11534 sort_text: Some("fffffff2".to_string()),
11535 filter_text: Some("some".to_string()),
11536 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11537 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11538 range: lsp::Range {
11539 start: lsp::Position {
11540 line: 0,
11541 character: 22,
11542 },
11543 end: lsp::Position {
11544 line: 0,
11545 character: 22,
11546 },
11547 },
11548 new_text: "Some(2)".to_string(),
11549 })),
11550 additional_text_edits: Some(vec![lsp::TextEdit {
11551 range: lsp::Range {
11552 start: lsp::Position {
11553 line: 0,
11554 character: 20,
11555 },
11556 end: lsp::Position {
11557 line: 0,
11558 character: 22,
11559 },
11560 },
11561 new_text: "".to_string(),
11562 }]),
11563 ..Default::default()
11564 };
11565
11566 let closure_completion_item = completion_item.clone();
11567 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11568 let task_completion_item = closure_completion_item.clone();
11569 async move {
11570 Ok(Some(lsp::CompletionResponse::Array(vec![
11571 task_completion_item,
11572 ])))
11573 }
11574 });
11575
11576 request.next().await;
11577
11578 cx.condition(|editor, _| editor.context_menu_visible())
11579 .await;
11580 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11581 editor
11582 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11583 .unwrap()
11584 });
11585 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11586
11587 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11588 let task_completion_item = completion_item.clone();
11589 async move { Ok(task_completion_item) }
11590 })
11591 .next()
11592 .await
11593 .unwrap();
11594 apply_additional_edits.await.unwrap();
11595 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11596}
11597
11598#[gpui::test]
11599async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11600 init_test(cx, |_| {});
11601
11602 let mut cx = EditorLspTestContext::new_rust(
11603 lsp::ServerCapabilities {
11604 completion_provider: Some(lsp::CompletionOptions {
11605 trigger_characters: Some(vec![".".to_string()]),
11606 resolve_provider: Some(true),
11607 ..Default::default()
11608 }),
11609 ..Default::default()
11610 },
11611 cx,
11612 )
11613 .await;
11614
11615 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11616 cx.simulate_keystroke(".");
11617
11618 let item1 = lsp::CompletionItem {
11619 label: "method id()".to_string(),
11620 filter_text: Some("id".to_string()),
11621 detail: None,
11622 documentation: None,
11623 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11624 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11625 new_text: ".id".to_string(),
11626 })),
11627 ..lsp::CompletionItem::default()
11628 };
11629
11630 let item2 = lsp::CompletionItem {
11631 label: "other".to_string(),
11632 filter_text: Some("other".to_string()),
11633 detail: None,
11634 documentation: None,
11635 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11636 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11637 new_text: ".other".to_string(),
11638 })),
11639 ..lsp::CompletionItem::default()
11640 };
11641
11642 let item1 = item1.clone();
11643 cx.handle_request::<lsp::request::Completion, _, _>({
11644 let item1 = item1.clone();
11645 move |_, _, _| {
11646 let item1 = item1.clone();
11647 let item2 = item2.clone();
11648 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11649 }
11650 })
11651 .next()
11652 .await;
11653
11654 cx.condition(|editor, _| editor.context_menu_visible())
11655 .await;
11656 cx.update_editor(|editor, _, _| {
11657 let context_menu = editor.context_menu.borrow_mut();
11658 let context_menu = context_menu
11659 .as_ref()
11660 .expect("Should have the context menu deployed");
11661 match context_menu {
11662 CodeContextMenu::Completions(completions_menu) => {
11663 let completions = completions_menu.completions.borrow_mut();
11664 assert_eq!(
11665 completions
11666 .iter()
11667 .map(|completion| &completion.label.text)
11668 .collect::<Vec<_>>(),
11669 vec!["method id()", "other"]
11670 )
11671 }
11672 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11673 }
11674 });
11675
11676 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11677 let item1 = item1.clone();
11678 move |_, item_to_resolve, _| {
11679 let item1 = item1.clone();
11680 async move {
11681 if item1 == item_to_resolve {
11682 Ok(lsp::CompletionItem {
11683 label: "method id()".to_string(),
11684 filter_text: Some("id".to_string()),
11685 detail: Some("Now resolved!".to_string()),
11686 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11687 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11688 range: lsp::Range::new(
11689 lsp::Position::new(0, 22),
11690 lsp::Position::new(0, 22),
11691 ),
11692 new_text: ".id".to_string(),
11693 })),
11694 ..lsp::CompletionItem::default()
11695 })
11696 } else {
11697 Ok(item_to_resolve)
11698 }
11699 }
11700 }
11701 })
11702 .next()
11703 .await
11704 .unwrap();
11705 cx.run_until_parked();
11706
11707 cx.update_editor(|editor, window, cx| {
11708 editor.context_menu_next(&Default::default(), window, cx);
11709 });
11710
11711 cx.update_editor(|editor, _, _| {
11712 let context_menu = editor.context_menu.borrow_mut();
11713 let context_menu = context_menu
11714 .as_ref()
11715 .expect("Should have the context menu deployed");
11716 match context_menu {
11717 CodeContextMenu::Completions(completions_menu) => {
11718 let completions = completions_menu.completions.borrow_mut();
11719 assert_eq!(
11720 completions
11721 .iter()
11722 .map(|completion| &completion.label.text)
11723 .collect::<Vec<_>>(),
11724 vec!["method id() Now resolved!", "other"],
11725 "Should update first completion label, but not second as the filter text did not match."
11726 );
11727 }
11728 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11729 }
11730 });
11731}
11732
11733#[gpui::test]
11734async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
11735 init_test(cx, |_| {});
11736
11737 let mut cx = EditorLspTestContext::new_rust(
11738 lsp::ServerCapabilities {
11739 completion_provider: Some(lsp::CompletionOptions {
11740 trigger_characters: Some(vec![".".to_string()]),
11741 resolve_provider: Some(true),
11742 ..Default::default()
11743 }),
11744 ..Default::default()
11745 },
11746 cx,
11747 )
11748 .await;
11749
11750 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11751 cx.simulate_keystroke(".");
11752
11753 let unresolved_item_1 = lsp::CompletionItem {
11754 label: "id".to_string(),
11755 filter_text: Some("id".to_string()),
11756 detail: None,
11757 documentation: None,
11758 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11759 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11760 new_text: ".id".to_string(),
11761 })),
11762 ..lsp::CompletionItem::default()
11763 };
11764 let resolved_item_1 = lsp::CompletionItem {
11765 additional_text_edits: Some(vec![lsp::TextEdit {
11766 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11767 new_text: "!!".to_string(),
11768 }]),
11769 ..unresolved_item_1.clone()
11770 };
11771 let unresolved_item_2 = lsp::CompletionItem {
11772 label: "other".to_string(),
11773 filter_text: Some("other".to_string()),
11774 detail: None,
11775 documentation: None,
11776 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11777 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11778 new_text: ".other".to_string(),
11779 })),
11780 ..lsp::CompletionItem::default()
11781 };
11782 let resolved_item_2 = lsp::CompletionItem {
11783 additional_text_edits: Some(vec![lsp::TextEdit {
11784 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11785 new_text: "??".to_string(),
11786 }]),
11787 ..unresolved_item_2.clone()
11788 };
11789
11790 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11791 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11792 cx.lsp
11793 .server
11794 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11795 let unresolved_item_1 = unresolved_item_1.clone();
11796 let resolved_item_1 = resolved_item_1.clone();
11797 let unresolved_item_2 = unresolved_item_2.clone();
11798 let resolved_item_2 = resolved_item_2.clone();
11799 let resolve_requests_1 = resolve_requests_1.clone();
11800 let resolve_requests_2 = resolve_requests_2.clone();
11801 move |unresolved_request, _| {
11802 let unresolved_item_1 = unresolved_item_1.clone();
11803 let resolved_item_1 = resolved_item_1.clone();
11804 let unresolved_item_2 = unresolved_item_2.clone();
11805 let resolved_item_2 = resolved_item_2.clone();
11806 let resolve_requests_1 = resolve_requests_1.clone();
11807 let resolve_requests_2 = resolve_requests_2.clone();
11808 async move {
11809 if unresolved_request == unresolved_item_1 {
11810 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11811 Ok(resolved_item_1.clone())
11812 } else if unresolved_request == unresolved_item_2 {
11813 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11814 Ok(resolved_item_2.clone())
11815 } else {
11816 panic!("Unexpected completion item {unresolved_request:?}")
11817 }
11818 }
11819 }
11820 })
11821 .detach();
11822
11823 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11824 let unresolved_item_1 = unresolved_item_1.clone();
11825 let unresolved_item_2 = unresolved_item_2.clone();
11826 async move {
11827 Ok(Some(lsp::CompletionResponse::Array(vec![
11828 unresolved_item_1,
11829 unresolved_item_2,
11830 ])))
11831 }
11832 })
11833 .next()
11834 .await;
11835
11836 cx.condition(|editor, _| editor.context_menu_visible())
11837 .await;
11838 cx.update_editor(|editor, _, _| {
11839 let context_menu = editor.context_menu.borrow_mut();
11840 let context_menu = context_menu
11841 .as_ref()
11842 .expect("Should have the context menu deployed");
11843 match context_menu {
11844 CodeContextMenu::Completions(completions_menu) => {
11845 let completions = completions_menu.completions.borrow_mut();
11846 assert_eq!(
11847 completions
11848 .iter()
11849 .map(|completion| &completion.label.text)
11850 .collect::<Vec<_>>(),
11851 vec!["id", "other"]
11852 )
11853 }
11854 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11855 }
11856 });
11857 cx.run_until_parked();
11858
11859 cx.update_editor(|editor, window, cx| {
11860 editor.context_menu_next(&ContextMenuNext, window, cx);
11861 });
11862 cx.run_until_parked();
11863 cx.update_editor(|editor, window, cx| {
11864 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11865 });
11866 cx.run_until_parked();
11867 cx.update_editor(|editor, window, cx| {
11868 editor.context_menu_next(&ContextMenuNext, window, cx);
11869 });
11870 cx.run_until_parked();
11871 cx.update_editor(|editor, window, cx| {
11872 editor
11873 .compose_completion(&ComposeCompletion::default(), window, cx)
11874 .expect("No task returned")
11875 })
11876 .await
11877 .expect("Completion failed");
11878 cx.run_until_parked();
11879
11880 cx.update_editor(|editor, _, cx| {
11881 assert_eq!(
11882 resolve_requests_1.load(atomic::Ordering::Acquire),
11883 1,
11884 "Should always resolve once despite multiple selections"
11885 );
11886 assert_eq!(
11887 resolve_requests_2.load(atomic::Ordering::Acquire),
11888 1,
11889 "Should always resolve once after multiple selections and applying the completion"
11890 );
11891 assert_eq!(
11892 editor.text(cx),
11893 "fn main() { let a = ??.other; }",
11894 "Should use resolved data when applying the completion"
11895 );
11896 });
11897}
11898
11899#[gpui::test]
11900async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
11901 init_test(cx, |_| {});
11902
11903 let item_0 = lsp::CompletionItem {
11904 label: "abs".into(),
11905 insert_text: Some("abs".into()),
11906 data: Some(json!({ "very": "special"})),
11907 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11908 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11909 lsp::InsertReplaceEdit {
11910 new_text: "abs".to_string(),
11911 insert: lsp::Range::default(),
11912 replace: lsp::Range::default(),
11913 },
11914 )),
11915 ..lsp::CompletionItem::default()
11916 };
11917 let items = iter::once(item_0.clone())
11918 .chain((11..51).map(|i| lsp::CompletionItem {
11919 label: format!("item_{}", i),
11920 insert_text: Some(format!("item_{}", i)),
11921 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11922 ..lsp::CompletionItem::default()
11923 }))
11924 .collect::<Vec<_>>();
11925
11926 let default_commit_characters = vec!["?".to_string()];
11927 let default_data = json!({ "default": "data"});
11928 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11929 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11930 let default_edit_range = lsp::Range {
11931 start: lsp::Position {
11932 line: 0,
11933 character: 5,
11934 },
11935 end: lsp::Position {
11936 line: 0,
11937 character: 5,
11938 },
11939 };
11940
11941 let item_0_out = lsp::CompletionItem {
11942 commit_characters: Some(default_commit_characters.clone()),
11943 insert_text_format: Some(default_insert_text_format),
11944 ..item_0
11945 };
11946 let items_out = iter::once(item_0_out)
11947 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11948 commit_characters: Some(default_commit_characters.clone()),
11949 data: Some(default_data.clone()),
11950 insert_text_mode: Some(default_insert_text_mode),
11951 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11952 range: default_edit_range,
11953 new_text: item.label.clone(),
11954 })),
11955 ..item.clone()
11956 }))
11957 .collect::<Vec<lsp::CompletionItem>>();
11958
11959 let mut cx = EditorLspTestContext::new_rust(
11960 lsp::ServerCapabilities {
11961 completion_provider: Some(lsp::CompletionOptions {
11962 trigger_characters: Some(vec![".".to_string()]),
11963 resolve_provider: Some(true),
11964 ..Default::default()
11965 }),
11966 ..Default::default()
11967 },
11968 cx,
11969 )
11970 .await;
11971
11972 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11973 cx.simulate_keystroke(".");
11974
11975 let completion_data = default_data.clone();
11976 let completion_characters = default_commit_characters.clone();
11977 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11978 let default_data = completion_data.clone();
11979 let default_commit_characters = completion_characters.clone();
11980 let items = items.clone();
11981 async move {
11982 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11983 items,
11984 item_defaults: Some(lsp::CompletionListItemDefaults {
11985 data: Some(default_data.clone()),
11986 commit_characters: Some(default_commit_characters.clone()),
11987 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11988 default_edit_range,
11989 )),
11990 insert_text_format: Some(default_insert_text_format),
11991 insert_text_mode: Some(default_insert_text_mode),
11992 }),
11993 ..lsp::CompletionList::default()
11994 })))
11995 }
11996 })
11997 .next()
11998 .await;
11999
12000 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12001 cx.lsp
12002 .server
12003 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12004 let closure_resolved_items = resolved_items.clone();
12005 move |item_to_resolve, _| {
12006 let closure_resolved_items = closure_resolved_items.clone();
12007 async move {
12008 closure_resolved_items.lock().push(item_to_resolve.clone());
12009 Ok(item_to_resolve)
12010 }
12011 }
12012 })
12013 .detach();
12014
12015 cx.condition(|editor, _| editor.context_menu_visible())
12016 .await;
12017 cx.run_until_parked();
12018 cx.update_editor(|editor, _, _| {
12019 let menu = editor.context_menu.borrow_mut();
12020 match menu.as_ref().expect("should have the completions menu") {
12021 CodeContextMenu::Completions(completions_menu) => {
12022 assert_eq!(
12023 completions_menu
12024 .entries
12025 .borrow()
12026 .iter()
12027 .map(|mat| mat.string.clone())
12028 .collect::<Vec<String>>(),
12029 items_out
12030 .iter()
12031 .map(|completion| completion.label.clone())
12032 .collect::<Vec<String>>()
12033 );
12034 }
12035 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12036 }
12037 });
12038 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12039 // with 4 from the end.
12040 assert_eq!(
12041 *resolved_items.lock(),
12042 [
12043 &items_out[0..16],
12044 &items_out[items_out.len() - 4..items_out.len()]
12045 ]
12046 .concat()
12047 .iter()
12048 .cloned()
12049 .collect::<Vec<lsp::CompletionItem>>()
12050 );
12051 resolved_items.lock().clear();
12052
12053 cx.update_editor(|editor, window, cx| {
12054 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12055 });
12056 cx.run_until_parked();
12057 // Completions that have already been resolved are skipped.
12058 assert_eq!(
12059 *resolved_items.lock(),
12060 items_out[items_out.len() - 16..items_out.len() - 4]
12061 .iter()
12062 .cloned()
12063 .collect::<Vec<lsp::CompletionItem>>()
12064 );
12065 resolved_items.lock().clear();
12066}
12067
12068#[gpui::test]
12069async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12070 init_test(cx, |_| {});
12071
12072 let mut cx = EditorLspTestContext::new(
12073 Language::new(
12074 LanguageConfig {
12075 matcher: LanguageMatcher {
12076 path_suffixes: vec!["jsx".into()],
12077 ..Default::default()
12078 },
12079 overrides: [(
12080 "element".into(),
12081 LanguageConfigOverride {
12082 word_characters: Override::Set(['-'].into_iter().collect()),
12083 ..Default::default()
12084 },
12085 )]
12086 .into_iter()
12087 .collect(),
12088 ..Default::default()
12089 },
12090 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12091 )
12092 .with_override_query("(jsx_self_closing_element) @element")
12093 .unwrap(),
12094 lsp::ServerCapabilities {
12095 completion_provider: Some(lsp::CompletionOptions {
12096 trigger_characters: Some(vec![":".to_string()]),
12097 ..Default::default()
12098 }),
12099 ..Default::default()
12100 },
12101 cx,
12102 )
12103 .await;
12104
12105 cx.lsp
12106 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12107 Ok(Some(lsp::CompletionResponse::Array(vec![
12108 lsp::CompletionItem {
12109 label: "bg-blue".into(),
12110 ..Default::default()
12111 },
12112 lsp::CompletionItem {
12113 label: "bg-red".into(),
12114 ..Default::default()
12115 },
12116 lsp::CompletionItem {
12117 label: "bg-yellow".into(),
12118 ..Default::default()
12119 },
12120 ])))
12121 });
12122
12123 cx.set_state(r#"<p class="bgˇ" />"#);
12124
12125 // Trigger completion when typing a dash, because the dash is an extra
12126 // word character in the 'element' scope, which contains the cursor.
12127 cx.simulate_keystroke("-");
12128 cx.executor().run_until_parked();
12129 cx.update_editor(|editor, _, _| {
12130 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12131 {
12132 assert_eq!(
12133 completion_menu_entries(&menu),
12134 &["bg-red", "bg-blue", "bg-yellow"]
12135 );
12136 } else {
12137 panic!("expected completion menu to be open");
12138 }
12139 });
12140
12141 cx.simulate_keystroke("l");
12142 cx.executor().run_until_parked();
12143 cx.update_editor(|editor, _, _| {
12144 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12145 {
12146 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12147 } else {
12148 panic!("expected completion menu to be open");
12149 }
12150 });
12151
12152 // When filtering completions, consider the character after the '-' to
12153 // be the start of a subword.
12154 cx.set_state(r#"<p class="yelˇ" />"#);
12155 cx.simulate_keystroke("l");
12156 cx.executor().run_until_parked();
12157 cx.update_editor(|editor, _, _| {
12158 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12159 {
12160 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12161 } else {
12162 panic!("expected completion menu to be open");
12163 }
12164 });
12165}
12166
12167fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12168 let entries = menu.entries.borrow();
12169 entries.iter().map(|mat| mat.string.clone()).collect()
12170}
12171
12172#[gpui::test]
12173async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12174 init_test(cx, |settings| {
12175 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12176 FormatterList(vec![Formatter::Prettier].into()),
12177 ))
12178 });
12179
12180 let fs = FakeFs::new(cx.executor());
12181 fs.insert_file(path!("/file.ts"), Default::default()).await;
12182
12183 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12184 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12185
12186 language_registry.add(Arc::new(Language::new(
12187 LanguageConfig {
12188 name: "TypeScript".into(),
12189 matcher: LanguageMatcher {
12190 path_suffixes: vec!["ts".to_string()],
12191 ..Default::default()
12192 },
12193 ..Default::default()
12194 },
12195 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12196 )));
12197 update_test_language_settings(cx, |settings| {
12198 settings.defaults.prettier = Some(PrettierSettings {
12199 allowed: true,
12200 ..PrettierSettings::default()
12201 });
12202 });
12203
12204 let test_plugin = "test_plugin";
12205 let _ = language_registry.register_fake_lsp(
12206 "TypeScript",
12207 FakeLspAdapter {
12208 prettier_plugins: vec![test_plugin],
12209 ..Default::default()
12210 },
12211 );
12212
12213 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12214 let buffer = project
12215 .update(cx, |project, cx| {
12216 project.open_local_buffer(path!("/file.ts"), cx)
12217 })
12218 .await
12219 .unwrap();
12220
12221 let buffer_text = "one\ntwo\nthree\n";
12222 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12223 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12224 editor.update_in(cx, |editor, window, cx| {
12225 editor.set_text(buffer_text, window, cx)
12226 });
12227
12228 editor
12229 .update_in(cx, |editor, window, cx| {
12230 editor.perform_format(
12231 project.clone(),
12232 FormatTrigger::Manual,
12233 FormatTarget::Buffers,
12234 window,
12235 cx,
12236 )
12237 })
12238 .unwrap()
12239 .await;
12240 assert_eq!(
12241 editor.update(cx, |editor, cx| editor.text(cx)),
12242 buffer_text.to_string() + prettier_format_suffix,
12243 "Test prettier formatting was not applied to the original buffer text",
12244 );
12245
12246 update_test_language_settings(cx, |settings| {
12247 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12248 });
12249 let format = editor.update_in(cx, |editor, window, cx| {
12250 editor.perform_format(
12251 project.clone(),
12252 FormatTrigger::Manual,
12253 FormatTarget::Buffers,
12254 window,
12255 cx,
12256 )
12257 });
12258 format.await.unwrap();
12259 assert_eq!(
12260 editor.update(cx, |editor, cx| editor.text(cx)),
12261 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12262 "Autoformatting (via test prettier) was not applied to the original buffer text",
12263 );
12264}
12265
12266#[gpui::test]
12267async fn test_addition_reverts(cx: &mut TestAppContext) {
12268 init_test(cx, |_| {});
12269 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12270 let base_text = indoc! {r#"
12271 struct Row;
12272 struct Row1;
12273 struct Row2;
12274
12275 struct Row4;
12276 struct Row5;
12277 struct Row6;
12278
12279 struct Row8;
12280 struct Row9;
12281 struct Row10;"#};
12282
12283 // When addition hunks are not adjacent to carets, no hunk revert is performed
12284 assert_hunk_revert(
12285 indoc! {r#"struct Row;
12286 struct Row1;
12287 struct Row1.1;
12288 struct Row1.2;
12289 struct Row2;ˇ
12290
12291 struct Row4;
12292 struct Row5;
12293 struct Row6;
12294
12295 struct Row8;
12296 ˇstruct Row9;
12297 struct Row9.1;
12298 struct Row9.2;
12299 struct Row9.3;
12300 struct Row10;"#},
12301 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12302 indoc! {r#"struct Row;
12303 struct Row1;
12304 struct Row1.1;
12305 struct Row1.2;
12306 struct Row2;ˇ
12307
12308 struct Row4;
12309 struct Row5;
12310 struct Row6;
12311
12312 struct Row8;
12313 ˇstruct Row9;
12314 struct Row9.1;
12315 struct Row9.2;
12316 struct Row9.3;
12317 struct Row10;"#},
12318 base_text,
12319 &mut cx,
12320 );
12321 // Same for selections
12322 assert_hunk_revert(
12323 indoc! {r#"struct Row;
12324 struct Row1;
12325 struct Row2;
12326 struct Row2.1;
12327 struct Row2.2;
12328 «ˇ
12329 struct Row4;
12330 struct» Row5;
12331 «struct Row6;
12332 ˇ»
12333 struct Row9.1;
12334 struct Row9.2;
12335 struct Row9.3;
12336 struct Row8;
12337 struct Row9;
12338 struct Row10;"#},
12339 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12340 indoc! {r#"struct Row;
12341 struct Row1;
12342 struct Row2;
12343 struct Row2.1;
12344 struct Row2.2;
12345 «ˇ
12346 struct Row4;
12347 struct» Row5;
12348 «struct Row6;
12349 ˇ»
12350 struct Row9.1;
12351 struct Row9.2;
12352 struct Row9.3;
12353 struct Row8;
12354 struct Row9;
12355 struct Row10;"#},
12356 base_text,
12357 &mut cx,
12358 );
12359
12360 // When carets and selections intersect the addition hunks, those are reverted.
12361 // Adjacent carets got merged.
12362 assert_hunk_revert(
12363 indoc! {r#"struct Row;
12364 ˇ// something on the top
12365 struct Row1;
12366 struct Row2;
12367 struct Roˇw3.1;
12368 struct Row2.2;
12369 struct Row2.3;ˇ
12370
12371 struct Row4;
12372 struct ˇRow5.1;
12373 struct Row5.2;
12374 struct «Rowˇ»5.3;
12375 struct Row5;
12376 struct Row6;
12377 ˇ
12378 struct Row9.1;
12379 struct «Rowˇ»9.2;
12380 struct «ˇRow»9.3;
12381 struct Row8;
12382 struct Row9;
12383 «ˇ// something on bottom»
12384 struct Row10;"#},
12385 vec![
12386 DiffHunkStatus::added_none(),
12387 DiffHunkStatus::added_none(),
12388 DiffHunkStatus::added_none(),
12389 DiffHunkStatus::added_none(),
12390 DiffHunkStatus::added_none(),
12391 ],
12392 indoc! {r#"struct Row;
12393 ˇstruct Row1;
12394 struct Row2;
12395 ˇ
12396 struct Row4;
12397 ˇstruct Row5;
12398 struct Row6;
12399 ˇ
12400 ˇstruct Row8;
12401 struct Row9;
12402 ˇstruct Row10;"#},
12403 base_text,
12404 &mut cx,
12405 );
12406}
12407
12408#[gpui::test]
12409async fn test_modification_reverts(cx: &mut TestAppContext) {
12410 init_test(cx, |_| {});
12411 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12412 let base_text = indoc! {r#"
12413 struct Row;
12414 struct Row1;
12415 struct Row2;
12416
12417 struct Row4;
12418 struct Row5;
12419 struct Row6;
12420
12421 struct Row8;
12422 struct Row9;
12423 struct Row10;"#};
12424
12425 // Modification hunks behave the same as the addition ones.
12426 assert_hunk_revert(
12427 indoc! {r#"struct Row;
12428 struct Row1;
12429 struct Row33;
12430 ˇ
12431 struct Row4;
12432 struct Row5;
12433 struct Row6;
12434 ˇ
12435 struct Row99;
12436 struct Row9;
12437 struct Row10;"#},
12438 vec![
12439 DiffHunkStatus::modified_none(),
12440 DiffHunkStatus::modified_none(),
12441 ],
12442 indoc! {r#"struct Row;
12443 struct Row1;
12444 struct Row33;
12445 ˇ
12446 struct Row4;
12447 struct Row5;
12448 struct Row6;
12449 ˇ
12450 struct Row99;
12451 struct Row9;
12452 struct Row10;"#},
12453 base_text,
12454 &mut cx,
12455 );
12456 assert_hunk_revert(
12457 indoc! {r#"struct Row;
12458 struct Row1;
12459 struct Row33;
12460 «ˇ
12461 struct Row4;
12462 struct» Row5;
12463 «struct Row6;
12464 ˇ»
12465 struct Row99;
12466 struct Row9;
12467 struct Row10;"#},
12468 vec![
12469 DiffHunkStatus::modified_none(),
12470 DiffHunkStatus::modified_none(),
12471 ],
12472 indoc! {r#"struct Row;
12473 struct Row1;
12474 struct Row33;
12475 «ˇ
12476 struct Row4;
12477 struct» Row5;
12478 «struct Row6;
12479 ˇ»
12480 struct Row99;
12481 struct Row9;
12482 struct Row10;"#},
12483 base_text,
12484 &mut cx,
12485 );
12486
12487 assert_hunk_revert(
12488 indoc! {r#"ˇstruct Row1.1;
12489 struct Row1;
12490 «ˇstr»uct Row22;
12491
12492 struct ˇRow44;
12493 struct Row5;
12494 struct «Rˇ»ow66;ˇ
12495
12496 «struˇ»ct Row88;
12497 struct Row9;
12498 struct Row1011;ˇ"#},
12499 vec![
12500 DiffHunkStatus::modified_none(),
12501 DiffHunkStatus::modified_none(),
12502 DiffHunkStatus::modified_none(),
12503 DiffHunkStatus::modified_none(),
12504 DiffHunkStatus::modified_none(),
12505 DiffHunkStatus::modified_none(),
12506 ],
12507 indoc! {r#"struct Row;
12508 ˇstruct Row1;
12509 struct Row2;
12510 ˇ
12511 struct Row4;
12512 ˇstruct Row5;
12513 struct Row6;
12514 ˇ
12515 struct Row8;
12516 ˇstruct Row9;
12517 struct Row10;ˇ"#},
12518 base_text,
12519 &mut cx,
12520 );
12521}
12522
12523#[gpui::test]
12524async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12525 init_test(cx, |_| {});
12526 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12527 let base_text = indoc! {r#"
12528 one
12529
12530 two
12531 three
12532 "#};
12533
12534 cx.set_diff_base(base_text);
12535 cx.set_state("\nˇ\n");
12536 cx.executor().run_until_parked();
12537 cx.update_editor(|editor, _window, cx| {
12538 editor.expand_selected_diff_hunks(cx);
12539 });
12540 cx.executor().run_until_parked();
12541 cx.update_editor(|editor, window, cx| {
12542 editor.backspace(&Default::default(), window, cx);
12543 });
12544 cx.run_until_parked();
12545 cx.assert_state_with_diff(
12546 indoc! {r#"
12547
12548 - two
12549 - threeˇ
12550 +
12551 "#}
12552 .to_string(),
12553 );
12554}
12555
12556#[gpui::test]
12557async fn test_deletion_reverts(cx: &mut TestAppContext) {
12558 init_test(cx, |_| {});
12559 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12560 let base_text = indoc! {r#"struct Row;
12561struct Row1;
12562struct Row2;
12563
12564struct Row4;
12565struct Row5;
12566struct Row6;
12567
12568struct Row8;
12569struct Row9;
12570struct Row10;"#};
12571
12572 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12573 assert_hunk_revert(
12574 indoc! {r#"struct Row;
12575 struct Row2;
12576
12577 ˇstruct Row4;
12578 struct Row5;
12579 struct Row6;
12580 ˇ
12581 struct Row8;
12582 struct Row10;"#},
12583 vec![
12584 DiffHunkStatus::deleted_none(),
12585 DiffHunkStatus::deleted_none(),
12586 ],
12587 indoc! {r#"struct Row;
12588 struct Row2;
12589
12590 ˇstruct Row4;
12591 struct Row5;
12592 struct Row6;
12593 ˇ
12594 struct Row8;
12595 struct Row10;"#},
12596 base_text,
12597 &mut cx,
12598 );
12599 assert_hunk_revert(
12600 indoc! {r#"struct Row;
12601 struct Row2;
12602
12603 «ˇstruct Row4;
12604 struct» Row5;
12605 «struct Row6;
12606 ˇ»
12607 struct Row8;
12608 struct Row10;"#},
12609 vec![
12610 DiffHunkStatus::deleted_none(),
12611 DiffHunkStatus::deleted_none(),
12612 ],
12613 indoc! {r#"struct Row;
12614 struct Row2;
12615
12616 «ˇstruct Row4;
12617 struct» Row5;
12618 «struct Row6;
12619 ˇ»
12620 struct Row8;
12621 struct Row10;"#},
12622 base_text,
12623 &mut cx,
12624 );
12625
12626 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12627 assert_hunk_revert(
12628 indoc! {r#"struct Row;
12629 ˇstruct Row2;
12630
12631 struct Row4;
12632 struct Row5;
12633 struct Row6;
12634
12635 struct Row8;ˇ
12636 struct Row10;"#},
12637 vec![
12638 DiffHunkStatus::deleted_none(),
12639 DiffHunkStatus::deleted_none(),
12640 ],
12641 indoc! {r#"struct Row;
12642 struct Row1;
12643 ˇstruct Row2;
12644
12645 struct Row4;
12646 struct Row5;
12647 struct Row6;
12648
12649 struct Row8;ˇ
12650 struct Row9;
12651 struct Row10;"#},
12652 base_text,
12653 &mut cx,
12654 );
12655 assert_hunk_revert(
12656 indoc! {r#"struct Row;
12657 struct Row2«ˇ;
12658 struct Row4;
12659 struct» Row5;
12660 «struct Row6;
12661
12662 struct Row8;ˇ»
12663 struct Row10;"#},
12664 vec![
12665 DiffHunkStatus::deleted_none(),
12666 DiffHunkStatus::deleted_none(),
12667 DiffHunkStatus::deleted_none(),
12668 ],
12669 indoc! {r#"struct Row;
12670 struct Row1;
12671 struct Row2«ˇ;
12672
12673 struct Row4;
12674 struct» Row5;
12675 «struct Row6;
12676
12677 struct Row8;ˇ»
12678 struct Row9;
12679 struct Row10;"#},
12680 base_text,
12681 &mut cx,
12682 );
12683}
12684
12685#[gpui::test]
12686async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
12687 init_test(cx, |_| {});
12688
12689 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12690 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12691 let base_text_3 =
12692 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12693
12694 let text_1 = edit_first_char_of_every_line(base_text_1);
12695 let text_2 = edit_first_char_of_every_line(base_text_2);
12696 let text_3 = edit_first_char_of_every_line(base_text_3);
12697
12698 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12699 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12700 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12701
12702 let multibuffer = cx.new(|cx| {
12703 let mut multibuffer = MultiBuffer::new(ReadWrite);
12704 multibuffer.push_excerpts(
12705 buffer_1.clone(),
12706 [
12707 ExcerptRange {
12708 context: Point::new(0, 0)..Point::new(3, 0),
12709 primary: None,
12710 },
12711 ExcerptRange {
12712 context: Point::new(5, 0)..Point::new(7, 0),
12713 primary: None,
12714 },
12715 ExcerptRange {
12716 context: Point::new(9, 0)..Point::new(10, 4),
12717 primary: None,
12718 },
12719 ],
12720 cx,
12721 );
12722 multibuffer.push_excerpts(
12723 buffer_2.clone(),
12724 [
12725 ExcerptRange {
12726 context: Point::new(0, 0)..Point::new(3, 0),
12727 primary: None,
12728 },
12729 ExcerptRange {
12730 context: Point::new(5, 0)..Point::new(7, 0),
12731 primary: None,
12732 },
12733 ExcerptRange {
12734 context: Point::new(9, 0)..Point::new(10, 4),
12735 primary: None,
12736 },
12737 ],
12738 cx,
12739 );
12740 multibuffer.push_excerpts(
12741 buffer_3.clone(),
12742 [
12743 ExcerptRange {
12744 context: Point::new(0, 0)..Point::new(3, 0),
12745 primary: None,
12746 },
12747 ExcerptRange {
12748 context: Point::new(5, 0)..Point::new(7, 0),
12749 primary: None,
12750 },
12751 ExcerptRange {
12752 context: Point::new(9, 0)..Point::new(10, 4),
12753 primary: None,
12754 },
12755 ],
12756 cx,
12757 );
12758 multibuffer
12759 });
12760
12761 let fs = FakeFs::new(cx.executor());
12762 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12763 let (editor, cx) = cx
12764 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
12765 editor.update_in(cx, |editor, _window, cx| {
12766 for (buffer, diff_base) in [
12767 (buffer_1.clone(), base_text_1),
12768 (buffer_2.clone(), base_text_2),
12769 (buffer_3.clone(), base_text_3),
12770 ] {
12771 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
12772 editor
12773 .buffer
12774 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
12775 }
12776 });
12777 cx.executor().run_until_parked();
12778
12779 editor.update_in(cx, |editor, window, cx| {
12780 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}");
12781 editor.select_all(&SelectAll, window, cx);
12782 editor.git_restore(&Default::default(), window, cx);
12783 });
12784 cx.executor().run_until_parked();
12785
12786 // When all ranges are selected, all buffer hunks are reverted.
12787 editor.update(cx, |editor, cx| {
12788 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");
12789 });
12790 buffer_1.update(cx, |buffer, _| {
12791 assert_eq!(buffer.text(), base_text_1);
12792 });
12793 buffer_2.update(cx, |buffer, _| {
12794 assert_eq!(buffer.text(), base_text_2);
12795 });
12796 buffer_3.update(cx, |buffer, _| {
12797 assert_eq!(buffer.text(), base_text_3);
12798 });
12799
12800 editor.update_in(cx, |editor, window, cx| {
12801 editor.undo(&Default::default(), window, cx);
12802 });
12803
12804 editor.update_in(cx, |editor, window, cx| {
12805 editor.change_selections(None, window, cx, |s| {
12806 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12807 });
12808 editor.git_restore(&Default::default(), window, cx);
12809 });
12810
12811 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12812 // but not affect buffer_2 and its related excerpts.
12813 editor.update(cx, |editor, cx| {
12814 assert_eq!(
12815 editor.text(cx),
12816 "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}"
12817 );
12818 });
12819 buffer_1.update(cx, |buffer, _| {
12820 assert_eq!(buffer.text(), base_text_1);
12821 });
12822 buffer_2.update(cx, |buffer, _| {
12823 assert_eq!(
12824 buffer.text(),
12825 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12826 );
12827 });
12828 buffer_3.update(cx, |buffer, _| {
12829 assert_eq!(
12830 buffer.text(),
12831 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12832 );
12833 });
12834
12835 fn edit_first_char_of_every_line(text: &str) -> String {
12836 text.split('\n')
12837 .map(|line| format!("X{}", &line[1..]))
12838 .collect::<Vec<_>>()
12839 .join("\n")
12840 }
12841}
12842
12843#[gpui::test]
12844async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
12845 init_test(cx, |_| {});
12846
12847 let cols = 4;
12848 let rows = 10;
12849 let sample_text_1 = sample_text(rows, cols, 'a');
12850 assert_eq!(
12851 sample_text_1,
12852 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12853 );
12854 let sample_text_2 = sample_text(rows, cols, 'l');
12855 assert_eq!(
12856 sample_text_2,
12857 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12858 );
12859 let sample_text_3 = sample_text(rows, cols, 'v');
12860 assert_eq!(
12861 sample_text_3,
12862 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12863 );
12864
12865 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12866 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12867 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12868
12869 let multi_buffer = cx.new(|cx| {
12870 let mut multibuffer = MultiBuffer::new(ReadWrite);
12871 multibuffer.push_excerpts(
12872 buffer_1.clone(),
12873 [
12874 ExcerptRange {
12875 context: Point::new(0, 0)..Point::new(3, 0),
12876 primary: None,
12877 },
12878 ExcerptRange {
12879 context: Point::new(5, 0)..Point::new(7, 0),
12880 primary: None,
12881 },
12882 ExcerptRange {
12883 context: Point::new(9, 0)..Point::new(10, 4),
12884 primary: None,
12885 },
12886 ],
12887 cx,
12888 );
12889 multibuffer.push_excerpts(
12890 buffer_2.clone(),
12891 [
12892 ExcerptRange {
12893 context: Point::new(0, 0)..Point::new(3, 0),
12894 primary: None,
12895 },
12896 ExcerptRange {
12897 context: Point::new(5, 0)..Point::new(7, 0),
12898 primary: None,
12899 },
12900 ExcerptRange {
12901 context: Point::new(9, 0)..Point::new(10, 4),
12902 primary: None,
12903 },
12904 ],
12905 cx,
12906 );
12907 multibuffer.push_excerpts(
12908 buffer_3.clone(),
12909 [
12910 ExcerptRange {
12911 context: Point::new(0, 0)..Point::new(3, 0),
12912 primary: None,
12913 },
12914 ExcerptRange {
12915 context: Point::new(5, 0)..Point::new(7, 0),
12916 primary: None,
12917 },
12918 ExcerptRange {
12919 context: Point::new(9, 0)..Point::new(10, 4),
12920 primary: None,
12921 },
12922 ],
12923 cx,
12924 );
12925 multibuffer
12926 });
12927
12928 let fs = FakeFs::new(cx.executor());
12929 fs.insert_tree(
12930 "/a",
12931 json!({
12932 "main.rs": sample_text_1,
12933 "other.rs": sample_text_2,
12934 "lib.rs": sample_text_3,
12935 }),
12936 )
12937 .await;
12938 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12939 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12940 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12941 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12942 Editor::new(
12943 EditorMode::Full,
12944 multi_buffer,
12945 Some(project.clone()),
12946 true,
12947 window,
12948 cx,
12949 )
12950 });
12951 let multibuffer_item_id = workspace
12952 .update(cx, |workspace, window, cx| {
12953 assert!(
12954 workspace.active_item(cx).is_none(),
12955 "active item should be None before the first item is added"
12956 );
12957 workspace.add_item_to_active_pane(
12958 Box::new(multi_buffer_editor.clone()),
12959 None,
12960 true,
12961 window,
12962 cx,
12963 );
12964 let active_item = workspace
12965 .active_item(cx)
12966 .expect("should have an active item after adding the multi buffer");
12967 assert!(
12968 !active_item.is_singleton(cx),
12969 "A multi buffer was expected to active after adding"
12970 );
12971 active_item.item_id()
12972 })
12973 .unwrap();
12974 cx.executor().run_until_parked();
12975
12976 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12977 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12978 s.select_ranges(Some(1..2))
12979 });
12980 editor.open_excerpts(&OpenExcerpts, window, cx);
12981 });
12982 cx.executor().run_until_parked();
12983 let first_item_id = workspace
12984 .update(cx, |workspace, window, cx| {
12985 let active_item = workspace
12986 .active_item(cx)
12987 .expect("should have an active item after navigating into the 1st buffer");
12988 let first_item_id = active_item.item_id();
12989 assert_ne!(
12990 first_item_id, multibuffer_item_id,
12991 "Should navigate into the 1st buffer and activate it"
12992 );
12993 assert!(
12994 active_item.is_singleton(cx),
12995 "New active item should be a singleton buffer"
12996 );
12997 assert_eq!(
12998 active_item
12999 .act_as::<Editor>(cx)
13000 .expect("should have navigated into an editor for the 1st buffer")
13001 .read(cx)
13002 .text(cx),
13003 sample_text_1
13004 );
13005
13006 workspace
13007 .go_back(workspace.active_pane().downgrade(), window, cx)
13008 .detach_and_log_err(cx);
13009
13010 first_item_id
13011 })
13012 .unwrap();
13013 cx.executor().run_until_parked();
13014 workspace
13015 .update(cx, |workspace, _, cx| {
13016 let active_item = workspace
13017 .active_item(cx)
13018 .expect("should have an active item after navigating back");
13019 assert_eq!(
13020 active_item.item_id(),
13021 multibuffer_item_id,
13022 "Should navigate back to the multi buffer"
13023 );
13024 assert!(!active_item.is_singleton(cx));
13025 })
13026 .unwrap();
13027
13028 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13029 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13030 s.select_ranges(Some(39..40))
13031 });
13032 editor.open_excerpts(&OpenExcerpts, window, cx);
13033 });
13034 cx.executor().run_until_parked();
13035 let second_item_id = workspace
13036 .update(cx, |workspace, window, cx| {
13037 let active_item = workspace
13038 .active_item(cx)
13039 .expect("should have an active item after navigating into the 2nd buffer");
13040 let second_item_id = active_item.item_id();
13041 assert_ne!(
13042 second_item_id, multibuffer_item_id,
13043 "Should navigate away from the multibuffer"
13044 );
13045 assert_ne!(
13046 second_item_id, first_item_id,
13047 "Should navigate into the 2nd buffer and activate it"
13048 );
13049 assert!(
13050 active_item.is_singleton(cx),
13051 "New active item should be a singleton buffer"
13052 );
13053 assert_eq!(
13054 active_item
13055 .act_as::<Editor>(cx)
13056 .expect("should have navigated into an editor")
13057 .read(cx)
13058 .text(cx),
13059 sample_text_2
13060 );
13061
13062 workspace
13063 .go_back(workspace.active_pane().downgrade(), window, cx)
13064 .detach_and_log_err(cx);
13065
13066 second_item_id
13067 })
13068 .unwrap();
13069 cx.executor().run_until_parked();
13070 workspace
13071 .update(cx, |workspace, _, cx| {
13072 let active_item = workspace
13073 .active_item(cx)
13074 .expect("should have an active item after navigating back from the 2nd buffer");
13075 assert_eq!(
13076 active_item.item_id(),
13077 multibuffer_item_id,
13078 "Should navigate back from the 2nd buffer to the multi buffer"
13079 );
13080 assert!(!active_item.is_singleton(cx));
13081 })
13082 .unwrap();
13083
13084 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13085 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13086 s.select_ranges(Some(70..70))
13087 });
13088 editor.open_excerpts(&OpenExcerpts, window, cx);
13089 });
13090 cx.executor().run_until_parked();
13091 workspace
13092 .update(cx, |workspace, window, cx| {
13093 let active_item = workspace
13094 .active_item(cx)
13095 .expect("should have an active item after navigating into the 3rd buffer");
13096 let third_item_id = active_item.item_id();
13097 assert_ne!(
13098 third_item_id, multibuffer_item_id,
13099 "Should navigate into the 3rd buffer and activate it"
13100 );
13101 assert_ne!(third_item_id, first_item_id);
13102 assert_ne!(third_item_id, second_item_id);
13103 assert!(
13104 active_item.is_singleton(cx),
13105 "New active item should be a singleton buffer"
13106 );
13107 assert_eq!(
13108 active_item
13109 .act_as::<Editor>(cx)
13110 .expect("should have navigated into an editor")
13111 .read(cx)
13112 .text(cx),
13113 sample_text_3
13114 );
13115
13116 workspace
13117 .go_back(workspace.active_pane().downgrade(), window, cx)
13118 .detach_and_log_err(cx);
13119 })
13120 .unwrap();
13121 cx.executor().run_until_parked();
13122 workspace
13123 .update(cx, |workspace, _, cx| {
13124 let active_item = workspace
13125 .active_item(cx)
13126 .expect("should have an active item after navigating back from the 3rd buffer");
13127 assert_eq!(
13128 active_item.item_id(),
13129 multibuffer_item_id,
13130 "Should navigate back from the 3rd buffer to the multi buffer"
13131 );
13132 assert!(!active_item.is_singleton(cx));
13133 })
13134 .unwrap();
13135}
13136
13137#[gpui::test]
13138async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13139 init_test(cx, |_| {});
13140
13141 let mut cx = EditorTestContext::new(cx).await;
13142
13143 let diff_base = r#"
13144 use some::mod;
13145
13146 const A: u32 = 42;
13147
13148 fn main() {
13149 println!("hello");
13150
13151 println!("world");
13152 }
13153 "#
13154 .unindent();
13155
13156 cx.set_state(
13157 &r#"
13158 use some::modified;
13159
13160 ˇ
13161 fn main() {
13162 println!("hello there");
13163
13164 println!("around the");
13165 println!("world");
13166 }
13167 "#
13168 .unindent(),
13169 );
13170
13171 cx.set_diff_base(&diff_base);
13172 executor.run_until_parked();
13173
13174 cx.update_editor(|editor, window, cx| {
13175 editor.go_to_next_hunk(&GoToHunk, window, cx);
13176 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13177 });
13178 executor.run_until_parked();
13179 cx.assert_state_with_diff(
13180 r#"
13181 use some::modified;
13182
13183
13184 fn main() {
13185 - println!("hello");
13186 + ˇ println!("hello there");
13187
13188 println!("around the");
13189 println!("world");
13190 }
13191 "#
13192 .unindent(),
13193 );
13194
13195 cx.update_editor(|editor, window, cx| {
13196 for _ in 0..2 {
13197 editor.go_to_next_hunk(&GoToHunk, window, cx);
13198 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13199 }
13200 });
13201 executor.run_until_parked();
13202 cx.assert_state_with_diff(
13203 r#"
13204 - use some::mod;
13205 + ˇuse some::modified;
13206
13207
13208 fn main() {
13209 - println!("hello");
13210 + println!("hello there");
13211
13212 + println!("around the");
13213 println!("world");
13214 }
13215 "#
13216 .unindent(),
13217 );
13218
13219 cx.update_editor(|editor, window, cx| {
13220 editor.go_to_next_hunk(&GoToHunk, window, cx);
13221 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13222 });
13223 executor.run_until_parked();
13224 cx.assert_state_with_diff(
13225 r#"
13226 - use some::mod;
13227 + use some::modified;
13228
13229 - const A: u32 = 42;
13230 ˇ
13231 fn main() {
13232 - println!("hello");
13233 + println!("hello there");
13234
13235 + println!("around the");
13236 println!("world");
13237 }
13238 "#
13239 .unindent(),
13240 );
13241
13242 cx.update_editor(|editor, window, cx| {
13243 editor.cancel(&Cancel, window, cx);
13244 });
13245
13246 cx.assert_state_with_diff(
13247 r#"
13248 use some::modified;
13249
13250 ˇ
13251 fn main() {
13252 println!("hello there");
13253
13254 println!("around the");
13255 println!("world");
13256 }
13257 "#
13258 .unindent(),
13259 );
13260}
13261
13262#[gpui::test]
13263async fn test_diff_base_change_with_expanded_diff_hunks(
13264 executor: BackgroundExecutor,
13265 cx: &mut TestAppContext,
13266) {
13267 init_test(cx, |_| {});
13268
13269 let mut cx = EditorTestContext::new(cx).await;
13270
13271 let diff_base = r#"
13272 use some::mod1;
13273 use some::mod2;
13274
13275 const A: u32 = 42;
13276 const B: u32 = 42;
13277 const C: u32 = 42;
13278
13279 fn main() {
13280 println!("hello");
13281
13282 println!("world");
13283 }
13284 "#
13285 .unindent();
13286
13287 cx.set_state(
13288 &r#"
13289 use some::mod2;
13290
13291 const A: u32 = 42;
13292 const C: u32 = 42;
13293
13294 fn main(ˇ) {
13295 //println!("hello");
13296
13297 println!("world");
13298 //
13299 //
13300 }
13301 "#
13302 .unindent(),
13303 );
13304
13305 cx.set_diff_base(&diff_base);
13306 executor.run_until_parked();
13307
13308 cx.update_editor(|editor, window, cx| {
13309 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13310 });
13311 executor.run_until_parked();
13312 cx.assert_state_with_diff(
13313 r#"
13314 - use some::mod1;
13315 use some::mod2;
13316
13317 const A: u32 = 42;
13318 - const B: u32 = 42;
13319 const C: u32 = 42;
13320
13321 fn main(ˇ) {
13322 - println!("hello");
13323 + //println!("hello");
13324
13325 println!("world");
13326 + //
13327 + //
13328 }
13329 "#
13330 .unindent(),
13331 );
13332
13333 cx.set_diff_base("new diff base!");
13334 executor.run_until_parked();
13335 cx.assert_state_with_diff(
13336 r#"
13337 - new diff base!
13338 + use some::mod2;
13339 +
13340 + const A: u32 = 42;
13341 + const C: u32 = 42;
13342 +
13343 + fn main(ˇ) {
13344 + //println!("hello");
13345 +
13346 + println!("world");
13347 + //
13348 + //
13349 + }
13350 "#
13351 .unindent(),
13352 );
13353}
13354
13355#[gpui::test]
13356async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13357 init_test(cx, |_| {});
13358
13359 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13360 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13361 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13362 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13363 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13364 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13365
13366 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13367 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13368 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13369
13370 let multi_buffer = cx.new(|cx| {
13371 let mut multibuffer = MultiBuffer::new(ReadWrite);
13372 multibuffer.push_excerpts(
13373 buffer_1.clone(),
13374 [
13375 ExcerptRange {
13376 context: Point::new(0, 0)..Point::new(3, 0),
13377 primary: None,
13378 },
13379 ExcerptRange {
13380 context: Point::new(5, 0)..Point::new(7, 0),
13381 primary: None,
13382 },
13383 ExcerptRange {
13384 context: Point::new(9, 0)..Point::new(10, 3),
13385 primary: None,
13386 },
13387 ],
13388 cx,
13389 );
13390 multibuffer.push_excerpts(
13391 buffer_2.clone(),
13392 [
13393 ExcerptRange {
13394 context: Point::new(0, 0)..Point::new(3, 0),
13395 primary: None,
13396 },
13397 ExcerptRange {
13398 context: Point::new(5, 0)..Point::new(7, 0),
13399 primary: None,
13400 },
13401 ExcerptRange {
13402 context: Point::new(9, 0)..Point::new(10, 3),
13403 primary: None,
13404 },
13405 ],
13406 cx,
13407 );
13408 multibuffer.push_excerpts(
13409 buffer_3.clone(),
13410 [
13411 ExcerptRange {
13412 context: Point::new(0, 0)..Point::new(3, 0),
13413 primary: None,
13414 },
13415 ExcerptRange {
13416 context: Point::new(5, 0)..Point::new(7, 0),
13417 primary: None,
13418 },
13419 ExcerptRange {
13420 context: Point::new(9, 0)..Point::new(10, 3),
13421 primary: None,
13422 },
13423 ],
13424 cx,
13425 );
13426 multibuffer
13427 });
13428
13429 let editor = cx.add_window(|window, cx| {
13430 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13431 });
13432 editor
13433 .update(cx, |editor, _window, cx| {
13434 for (buffer, diff_base) in [
13435 (buffer_1.clone(), file_1_old),
13436 (buffer_2.clone(), file_2_old),
13437 (buffer_3.clone(), file_3_old),
13438 ] {
13439 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13440 editor
13441 .buffer
13442 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13443 }
13444 })
13445 .unwrap();
13446
13447 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13448 cx.run_until_parked();
13449
13450 cx.assert_editor_state(
13451 &"
13452 ˇaaa
13453 ccc
13454 ddd
13455
13456 ggg
13457 hhh
13458
13459
13460 lll
13461 mmm
13462 NNN
13463
13464 qqq
13465 rrr
13466
13467 uuu
13468 111
13469 222
13470 333
13471
13472 666
13473 777
13474
13475 000
13476 !!!"
13477 .unindent(),
13478 );
13479
13480 cx.update_editor(|editor, window, cx| {
13481 editor.select_all(&SelectAll, window, cx);
13482 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13483 });
13484 cx.executor().run_until_parked();
13485
13486 cx.assert_state_with_diff(
13487 "
13488 «aaa
13489 - bbb
13490 ccc
13491 ddd
13492
13493 ggg
13494 hhh
13495
13496
13497 lll
13498 mmm
13499 - nnn
13500 + NNN
13501
13502 qqq
13503 rrr
13504
13505 uuu
13506 111
13507 222
13508 333
13509
13510 + 666
13511 777
13512
13513 000
13514 !!!ˇ»"
13515 .unindent(),
13516 );
13517}
13518
13519#[gpui::test]
13520async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13521 init_test(cx, |_| {});
13522
13523 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13524 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13525
13526 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13527 let multi_buffer = cx.new(|cx| {
13528 let mut multibuffer = MultiBuffer::new(ReadWrite);
13529 multibuffer.push_excerpts(
13530 buffer.clone(),
13531 [
13532 ExcerptRange {
13533 context: Point::new(0, 0)..Point::new(2, 0),
13534 primary: None,
13535 },
13536 ExcerptRange {
13537 context: Point::new(4, 0)..Point::new(7, 0),
13538 primary: None,
13539 },
13540 ExcerptRange {
13541 context: Point::new(9, 0)..Point::new(10, 0),
13542 primary: None,
13543 },
13544 ],
13545 cx,
13546 );
13547 multibuffer
13548 });
13549
13550 let editor = cx.add_window(|window, cx| {
13551 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13552 });
13553 editor
13554 .update(cx, |editor, _window, cx| {
13555 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13556 editor
13557 .buffer
13558 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13559 })
13560 .unwrap();
13561
13562 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13563 cx.run_until_parked();
13564
13565 cx.update_editor(|editor, window, cx| {
13566 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13567 });
13568 cx.executor().run_until_parked();
13569
13570 // When the start of a hunk coincides with the start of its excerpt,
13571 // the hunk is expanded. When the start of a a hunk is earlier than
13572 // the start of its excerpt, the hunk is not expanded.
13573 cx.assert_state_with_diff(
13574 "
13575 ˇaaa
13576 - bbb
13577 + BBB
13578
13579 - ddd
13580 - eee
13581 + DDD
13582 + EEE
13583 fff
13584
13585 iii
13586 "
13587 .unindent(),
13588 );
13589}
13590
13591#[gpui::test]
13592async fn test_edits_around_expanded_insertion_hunks(
13593 executor: BackgroundExecutor,
13594 cx: &mut TestAppContext,
13595) {
13596 init_test(cx, |_| {});
13597
13598 let mut cx = EditorTestContext::new(cx).await;
13599
13600 let diff_base = r#"
13601 use some::mod1;
13602 use some::mod2;
13603
13604 const A: u32 = 42;
13605
13606 fn main() {
13607 println!("hello");
13608
13609 println!("world");
13610 }
13611 "#
13612 .unindent();
13613 executor.run_until_parked();
13614 cx.set_state(
13615 &r#"
13616 use some::mod1;
13617 use some::mod2;
13618
13619 const A: u32 = 42;
13620 const B: u32 = 42;
13621 const C: u32 = 42;
13622 ˇ
13623
13624 fn main() {
13625 println!("hello");
13626
13627 println!("world");
13628 }
13629 "#
13630 .unindent(),
13631 );
13632
13633 cx.set_diff_base(&diff_base);
13634 executor.run_until_parked();
13635
13636 cx.update_editor(|editor, window, cx| {
13637 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13638 });
13639 executor.run_until_parked();
13640
13641 cx.assert_state_with_diff(
13642 r#"
13643 use some::mod1;
13644 use some::mod2;
13645
13646 const A: u32 = 42;
13647 + const B: u32 = 42;
13648 + const C: u32 = 42;
13649 + ˇ
13650
13651 fn main() {
13652 println!("hello");
13653
13654 println!("world");
13655 }
13656 "#
13657 .unindent(),
13658 );
13659
13660 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13661 executor.run_until_parked();
13662
13663 cx.assert_state_with_diff(
13664 r#"
13665 use some::mod1;
13666 use some::mod2;
13667
13668 const A: u32 = 42;
13669 + const B: u32 = 42;
13670 + const C: u32 = 42;
13671 + const D: u32 = 42;
13672 + ˇ
13673
13674 fn main() {
13675 println!("hello");
13676
13677 println!("world");
13678 }
13679 "#
13680 .unindent(),
13681 );
13682
13683 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13684 executor.run_until_parked();
13685
13686 cx.assert_state_with_diff(
13687 r#"
13688 use some::mod1;
13689 use some::mod2;
13690
13691 const A: u32 = 42;
13692 + const B: u32 = 42;
13693 + const C: u32 = 42;
13694 + const D: u32 = 42;
13695 + const E: u32 = 42;
13696 + ˇ
13697
13698 fn main() {
13699 println!("hello");
13700
13701 println!("world");
13702 }
13703 "#
13704 .unindent(),
13705 );
13706
13707 cx.update_editor(|editor, window, cx| {
13708 editor.delete_line(&DeleteLine, window, cx);
13709 });
13710 executor.run_until_parked();
13711
13712 cx.assert_state_with_diff(
13713 r#"
13714 use some::mod1;
13715 use some::mod2;
13716
13717 const A: u32 = 42;
13718 + const B: u32 = 42;
13719 + const C: u32 = 42;
13720 + const D: u32 = 42;
13721 + const E: u32 = 42;
13722 ˇ
13723 fn main() {
13724 println!("hello");
13725
13726 println!("world");
13727 }
13728 "#
13729 .unindent(),
13730 );
13731
13732 cx.update_editor(|editor, window, cx| {
13733 editor.move_up(&MoveUp, window, cx);
13734 editor.delete_line(&DeleteLine, window, cx);
13735 editor.move_up(&MoveUp, window, cx);
13736 editor.delete_line(&DeleteLine, window, cx);
13737 editor.move_up(&MoveUp, window, cx);
13738 editor.delete_line(&DeleteLine, window, cx);
13739 });
13740 executor.run_until_parked();
13741 cx.assert_state_with_diff(
13742 r#"
13743 use some::mod1;
13744 use some::mod2;
13745
13746 const A: u32 = 42;
13747 + const B: u32 = 42;
13748 ˇ
13749 fn main() {
13750 println!("hello");
13751
13752 println!("world");
13753 }
13754 "#
13755 .unindent(),
13756 );
13757
13758 cx.update_editor(|editor, window, cx| {
13759 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13760 editor.delete_line(&DeleteLine, window, cx);
13761 });
13762 executor.run_until_parked();
13763 cx.assert_state_with_diff(
13764 r#"
13765 ˇ
13766 fn main() {
13767 println!("hello");
13768
13769 println!("world");
13770 }
13771 "#
13772 .unindent(),
13773 );
13774}
13775
13776#[gpui::test]
13777async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13778 init_test(cx, |_| {});
13779
13780 let mut cx = EditorTestContext::new(cx).await;
13781 cx.set_diff_base(indoc! { "
13782 one
13783 two
13784 three
13785 four
13786 five
13787 "
13788 });
13789 cx.set_state(indoc! { "
13790 one
13791 ˇthree
13792 five
13793 "});
13794 cx.run_until_parked();
13795 cx.update_editor(|editor, window, cx| {
13796 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13797 });
13798 cx.assert_state_with_diff(
13799 indoc! { "
13800 one
13801 - two
13802 ˇthree
13803 - four
13804 five
13805 "}
13806 .to_string(),
13807 );
13808 cx.update_editor(|editor, window, cx| {
13809 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13810 });
13811
13812 cx.assert_state_with_diff(
13813 indoc! { "
13814 one
13815 ˇthree
13816 five
13817 "}
13818 .to_string(),
13819 );
13820
13821 cx.set_state(indoc! { "
13822 one
13823 ˇTWO
13824 three
13825 four
13826 five
13827 "});
13828 cx.run_until_parked();
13829 cx.update_editor(|editor, window, cx| {
13830 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13831 });
13832
13833 cx.assert_state_with_diff(
13834 indoc! { "
13835 one
13836 - two
13837 + ˇTWO
13838 three
13839 four
13840 five
13841 "}
13842 .to_string(),
13843 );
13844 cx.update_editor(|editor, window, cx| {
13845 editor.move_up(&Default::default(), window, cx);
13846 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13847 });
13848 cx.assert_state_with_diff(
13849 indoc! { "
13850 one
13851 ˇTWO
13852 three
13853 four
13854 five
13855 "}
13856 .to_string(),
13857 );
13858}
13859
13860#[gpui::test]
13861async fn test_edits_around_expanded_deletion_hunks(
13862 executor: BackgroundExecutor,
13863 cx: &mut TestAppContext,
13864) {
13865 init_test(cx, |_| {});
13866
13867 let mut cx = EditorTestContext::new(cx).await;
13868
13869 let diff_base = r#"
13870 use some::mod1;
13871 use some::mod2;
13872
13873 const A: u32 = 42;
13874 const B: u32 = 42;
13875 const C: u32 = 42;
13876
13877
13878 fn main() {
13879 println!("hello");
13880
13881 println!("world");
13882 }
13883 "#
13884 .unindent();
13885 executor.run_until_parked();
13886 cx.set_state(
13887 &r#"
13888 use some::mod1;
13889 use some::mod2;
13890
13891 ˇconst B: u32 = 42;
13892 const C: u32 = 42;
13893
13894
13895 fn main() {
13896 println!("hello");
13897
13898 println!("world");
13899 }
13900 "#
13901 .unindent(),
13902 );
13903
13904 cx.set_diff_base(&diff_base);
13905 executor.run_until_parked();
13906
13907 cx.update_editor(|editor, window, cx| {
13908 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13909 });
13910 executor.run_until_parked();
13911
13912 cx.assert_state_with_diff(
13913 r#"
13914 use some::mod1;
13915 use some::mod2;
13916
13917 - const A: u32 = 42;
13918 ˇconst B: u32 = 42;
13919 const C: u32 = 42;
13920
13921
13922 fn main() {
13923 println!("hello");
13924
13925 println!("world");
13926 }
13927 "#
13928 .unindent(),
13929 );
13930
13931 cx.update_editor(|editor, window, cx| {
13932 editor.delete_line(&DeleteLine, window, cx);
13933 });
13934 executor.run_until_parked();
13935 cx.assert_state_with_diff(
13936 r#"
13937 use some::mod1;
13938 use some::mod2;
13939
13940 - const A: u32 = 42;
13941 - const B: u32 = 42;
13942 ˇconst C: u32 = 42;
13943
13944
13945 fn main() {
13946 println!("hello");
13947
13948 println!("world");
13949 }
13950 "#
13951 .unindent(),
13952 );
13953
13954 cx.update_editor(|editor, window, cx| {
13955 editor.delete_line(&DeleteLine, window, cx);
13956 });
13957 executor.run_until_parked();
13958 cx.assert_state_with_diff(
13959 r#"
13960 use some::mod1;
13961 use some::mod2;
13962
13963 - const A: u32 = 42;
13964 - const B: u32 = 42;
13965 - const C: u32 = 42;
13966 ˇ
13967
13968 fn main() {
13969 println!("hello");
13970
13971 println!("world");
13972 }
13973 "#
13974 .unindent(),
13975 );
13976
13977 cx.update_editor(|editor, window, cx| {
13978 editor.handle_input("replacement", window, cx);
13979 });
13980 executor.run_until_parked();
13981 cx.assert_state_with_diff(
13982 r#"
13983 use some::mod1;
13984 use some::mod2;
13985
13986 - const A: u32 = 42;
13987 - const B: u32 = 42;
13988 - const C: u32 = 42;
13989 -
13990 + replacementˇ
13991
13992 fn main() {
13993 println!("hello");
13994
13995 println!("world");
13996 }
13997 "#
13998 .unindent(),
13999 );
14000}
14001
14002#[gpui::test]
14003async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14004 init_test(cx, |_| {});
14005
14006 let mut cx = EditorTestContext::new(cx).await;
14007
14008 let base_text = r#"
14009 one
14010 two
14011 three
14012 four
14013 five
14014 "#
14015 .unindent();
14016 executor.run_until_parked();
14017 cx.set_state(
14018 &r#"
14019 one
14020 two
14021 fˇour
14022 five
14023 "#
14024 .unindent(),
14025 );
14026
14027 cx.set_diff_base(&base_text);
14028 executor.run_until_parked();
14029
14030 cx.update_editor(|editor, window, cx| {
14031 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
14032 });
14033 executor.run_until_parked();
14034
14035 cx.assert_state_with_diff(
14036 r#"
14037 one
14038 two
14039 - three
14040 fˇour
14041 five
14042 "#
14043 .unindent(),
14044 );
14045
14046 cx.update_editor(|editor, window, cx| {
14047 editor.backspace(&Backspace, window, cx);
14048 editor.backspace(&Backspace, window, cx);
14049 });
14050 executor.run_until_parked();
14051 cx.assert_state_with_diff(
14052 r#"
14053 one
14054 two
14055 - threeˇ
14056 - four
14057 + our
14058 five
14059 "#
14060 .unindent(),
14061 );
14062}
14063
14064#[gpui::test]
14065async fn test_edit_after_expanded_modification_hunk(
14066 executor: BackgroundExecutor,
14067 cx: &mut TestAppContext,
14068) {
14069 init_test(cx, |_| {});
14070
14071 let mut cx = EditorTestContext::new(cx).await;
14072
14073 let diff_base = r#"
14074 use some::mod1;
14075 use some::mod2;
14076
14077 const A: u32 = 42;
14078 const B: u32 = 42;
14079 const C: u32 = 42;
14080 const D: u32 = 42;
14081
14082
14083 fn main() {
14084 println!("hello");
14085
14086 println!("world");
14087 }"#
14088 .unindent();
14089
14090 cx.set_state(
14091 &r#"
14092 use some::mod1;
14093 use some::mod2;
14094
14095 const A: u32 = 42;
14096 const B: u32 = 42;
14097 const C: u32 = 43ˇ
14098 const D: u32 = 42;
14099
14100
14101 fn main() {
14102 println!("hello");
14103
14104 println!("world");
14105 }"#
14106 .unindent(),
14107 );
14108
14109 cx.set_diff_base(&diff_base);
14110 executor.run_until_parked();
14111 cx.update_editor(|editor, window, cx| {
14112 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
14113 });
14114 executor.run_until_parked();
14115
14116 cx.assert_state_with_diff(
14117 r#"
14118 use some::mod1;
14119 use some::mod2;
14120
14121 const A: u32 = 42;
14122 const B: u32 = 42;
14123 - const C: u32 = 42;
14124 + const C: u32 = 43ˇ
14125 const D: u32 = 42;
14126
14127
14128 fn main() {
14129 println!("hello");
14130
14131 println!("world");
14132 }"#
14133 .unindent(),
14134 );
14135
14136 cx.update_editor(|editor, window, cx| {
14137 editor.handle_input("\nnew_line\n", window, cx);
14138 });
14139 executor.run_until_parked();
14140
14141 cx.assert_state_with_diff(
14142 r#"
14143 use some::mod1;
14144 use some::mod2;
14145
14146 const A: u32 = 42;
14147 const B: u32 = 42;
14148 - const C: u32 = 42;
14149 + const C: u32 = 43
14150 + new_line
14151 + ˇ
14152 const D: u32 = 42;
14153
14154
14155 fn main() {
14156 println!("hello");
14157
14158 println!("world");
14159 }"#
14160 .unindent(),
14161 );
14162}
14163
14164#[gpui::test]
14165async fn test_stage_and_unstage_added_file_hunk(
14166 executor: BackgroundExecutor,
14167 cx: &mut TestAppContext,
14168) {
14169 init_test(cx, |_| {});
14170
14171 let mut cx = EditorTestContext::new(cx).await;
14172 cx.update_editor(|editor, _, cx| {
14173 editor.set_expand_all_diff_hunks(cx);
14174 });
14175
14176 let working_copy = r#"
14177 ˇfn main() {
14178 println!("hello, world!");
14179 }
14180 "#
14181 .unindent();
14182
14183 cx.set_state(&working_copy);
14184 executor.run_until_parked();
14185
14186 cx.assert_state_with_diff(
14187 r#"
14188 + ˇfn main() {
14189 + println!("hello, world!");
14190 + }
14191 "#
14192 .unindent(),
14193 );
14194 cx.assert_index_text(None);
14195
14196 cx.update_editor(|editor, window, cx| {
14197 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14198 });
14199 executor.run_until_parked();
14200 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14201 cx.assert_state_with_diff(
14202 r#"
14203 + ˇfn main() {
14204 + println!("hello, world!");
14205 + }
14206 "#
14207 .unindent(),
14208 );
14209
14210 cx.update_editor(|editor, window, cx| {
14211 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14212 });
14213 executor.run_until_parked();
14214 cx.assert_index_text(None);
14215}
14216
14217async fn setup_indent_guides_editor(
14218 text: &str,
14219 cx: &mut TestAppContext,
14220) -> (BufferId, EditorTestContext) {
14221 init_test(cx, |_| {});
14222
14223 let mut cx = EditorTestContext::new(cx).await;
14224
14225 let buffer_id = cx.update_editor(|editor, window, cx| {
14226 editor.set_text(text, window, cx);
14227 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14228
14229 buffer_ids[0]
14230 });
14231
14232 (buffer_id, cx)
14233}
14234
14235fn assert_indent_guides(
14236 range: Range<u32>,
14237 expected: Vec<IndentGuide>,
14238 active_indices: Option<Vec<usize>>,
14239 cx: &mut EditorTestContext,
14240) {
14241 let indent_guides = cx.update_editor(|editor, window, cx| {
14242 let snapshot = editor.snapshot(window, cx).display_snapshot;
14243 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14244 editor,
14245 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14246 true,
14247 &snapshot,
14248 cx,
14249 );
14250
14251 indent_guides.sort_by(|a, b| {
14252 a.depth.cmp(&b.depth).then(
14253 a.start_row
14254 .cmp(&b.start_row)
14255 .then(a.end_row.cmp(&b.end_row)),
14256 )
14257 });
14258 indent_guides
14259 });
14260
14261 if let Some(expected) = active_indices {
14262 let active_indices = cx.update_editor(|editor, window, cx| {
14263 let snapshot = editor.snapshot(window, cx).display_snapshot;
14264 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14265 });
14266
14267 assert_eq!(
14268 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14269 expected,
14270 "Active indent guide indices do not match"
14271 );
14272 }
14273
14274 assert_eq!(indent_guides, expected, "Indent guides do not match");
14275}
14276
14277fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14278 IndentGuide {
14279 buffer_id,
14280 start_row: MultiBufferRow(start_row),
14281 end_row: MultiBufferRow(end_row),
14282 depth,
14283 tab_size: 4,
14284 settings: IndentGuideSettings {
14285 enabled: true,
14286 line_width: 1,
14287 active_line_width: 1,
14288 ..Default::default()
14289 },
14290 }
14291}
14292
14293#[gpui::test]
14294async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14295 let (buffer_id, mut cx) = setup_indent_guides_editor(
14296 &"
14297 fn main() {
14298 let a = 1;
14299 }"
14300 .unindent(),
14301 cx,
14302 )
14303 .await;
14304
14305 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14306}
14307
14308#[gpui::test]
14309async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14310 let (buffer_id, mut cx) = setup_indent_guides_editor(
14311 &"
14312 fn main() {
14313 let a = 1;
14314 let b = 2;
14315 }"
14316 .unindent(),
14317 cx,
14318 )
14319 .await;
14320
14321 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14322}
14323
14324#[gpui::test]
14325async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14326 let (buffer_id, mut cx) = setup_indent_guides_editor(
14327 &"
14328 fn main() {
14329 let a = 1;
14330 if a == 3 {
14331 let b = 2;
14332 } else {
14333 let c = 3;
14334 }
14335 }"
14336 .unindent(),
14337 cx,
14338 )
14339 .await;
14340
14341 assert_indent_guides(
14342 0..8,
14343 vec![
14344 indent_guide(buffer_id, 1, 6, 0),
14345 indent_guide(buffer_id, 3, 3, 1),
14346 indent_guide(buffer_id, 5, 5, 1),
14347 ],
14348 None,
14349 &mut cx,
14350 );
14351}
14352
14353#[gpui::test]
14354async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14355 let (buffer_id, mut cx) = setup_indent_guides_editor(
14356 &"
14357 fn main() {
14358 let a = 1;
14359 let b = 2;
14360 let c = 3;
14361 }"
14362 .unindent(),
14363 cx,
14364 )
14365 .await;
14366
14367 assert_indent_guides(
14368 0..5,
14369 vec![
14370 indent_guide(buffer_id, 1, 3, 0),
14371 indent_guide(buffer_id, 2, 2, 1),
14372 ],
14373 None,
14374 &mut cx,
14375 );
14376}
14377
14378#[gpui::test]
14379async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14380 let (buffer_id, mut cx) = setup_indent_guides_editor(
14381 &"
14382 fn main() {
14383 let a = 1;
14384
14385 let c = 3;
14386 }"
14387 .unindent(),
14388 cx,
14389 )
14390 .await;
14391
14392 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14393}
14394
14395#[gpui::test]
14396async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14397 let (buffer_id, mut cx) = setup_indent_guides_editor(
14398 &"
14399 fn main() {
14400 let a = 1;
14401
14402 let c = 3;
14403
14404 if a == 3 {
14405 let b = 2;
14406 } else {
14407 let c = 3;
14408 }
14409 }"
14410 .unindent(),
14411 cx,
14412 )
14413 .await;
14414
14415 assert_indent_guides(
14416 0..11,
14417 vec![
14418 indent_guide(buffer_id, 1, 9, 0),
14419 indent_guide(buffer_id, 6, 6, 1),
14420 indent_guide(buffer_id, 8, 8, 1),
14421 ],
14422 None,
14423 &mut cx,
14424 );
14425}
14426
14427#[gpui::test]
14428async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14429 let (buffer_id, mut cx) = setup_indent_guides_editor(
14430 &"
14431 fn main() {
14432 let a = 1;
14433
14434 let c = 3;
14435
14436 if a == 3 {
14437 let b = 2;
14438 } else {
14439 let c = 3;
14440 }
14441 }"
14442 .unindent(),
14443 cx,
14444 )
14445 .await;
14446
14447 assert_indent_guides(
14448 1..11,
14449 vec![
14450 indent_guide(buffer_id, 1, 9, 0),
14451 indent_guide(buffer_id, 6, 6, 1),
14452 indent_guide(buffer_id, 8, 8, 1),
14453 ],
14454 None,
14455 &mut cx,
14456 );
14457}
14458
14459#[gpui::test]
14460async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14461 let (buffer_id, mut cx) = setup_indent_guides_editor(
14462 &"
14463 fn main() {
14464 let a = 1;
14465
14466 let c = 3;
14467
14468 if a == 3 {
14469 let b = 2;
14470 } else {
14471 let c = 3;
14472 }
14473 }"
14474 .unindent(),
14475 cx,
14476 )
14477 .await;
14478
14479 assert_indent_guides(
14480 1..10,
14481 vec![
14482 indent_guide(buffer_id, 1, 9, 0),
14483 indent_guide(buffer_id, 6, 6, 1),
14484 indent_guide(buffer_id, 8, 8, 1),
14485 ],
14486 None,
14487 &mut cx,
14488 );
14489}
14490
14491#[gpui::test]
14492async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14493 let (buffer_id, mut cx) = setup_indent_guides_editor(
14494 &"
14495 block1
14496 block2
14497 block3
14498 block4
14499 block2
14500 block1
14501 block1"
14502 .unindent(),
14503 cx,
14504 )
14505 .await;
14506
14507 assert_indent_guides(
14508 1..10,
14509 vec![
14510 indent_guide(buffer_id, 1, 4, 0),
14511 indent_guide(buffer_id, 2, 3, 1),
14512 indent_guide(buffer_id, 3, 3, 2),
14513 ],
14514 None,
14515 &mut cx,
14516 );
14517}
14518
14519#[gpui::test]
14520async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14521 let (buffer_id, mut cx) = setup_indent_guides_editor(
14522 &"
14523 block1
14524 block2
14525 block3
14526
14527 block1
14528 block1"
14529 .unindent(),
14530 cx,
14531 )
14532 .await;
14533
14534 assert_indent_guides(
14535 0..6,
14536 vec![
14537 indent_guide(buffer_id, 1, 2, 0),
14538 indent_guide(buffer_id, 2, 2, 1),
14539 ],
14540 None,
14541 &mut cx,
14542 );
14543}
14544
14545#[gpui::test]
14546async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14547 let (buffer_id, mut cx) = setup_indent_guides_editor(
14548 &"
14549 block1
14550
14551
14552
14553 block2
14554 "
14555 .unindent(),
14556 cx,
14557 )
14558 .await;
14559
14560 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14561}
14562
14563#[gpui::test]
14564async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14565 let (buffer_id, mut cx) = setup_indent_guides_editor(
14566 &"
14567 def a:
14568 \tb = 3
14569 \tif True:
14570 \t\tc = 4
14571 \t\td = 5
14572 \tprint(b)
14573 "
14574 .unindent(),
14575 cx,
14576 )
14577 .await;
14578
14579 assert_indent_guides(
14580 0..6,
14581 vec![
14582 indent_guide(buffer_id, 1, 6, 0),
14583 indent_guide(buffer_id, 3, 4, 1),
14584 ],
14585 None,
14586 &mut cx,
14587 );
14588}
14589
14590#[gpui::test]
14591async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14592 let (buffer_id, mut cx) = setup_indent_guides_editor(
14593 &"
14594 fn main() {
14595 let a = 1;
14596 }"
14597 .unindent(),
14598 cx,
14599 )
14600 .await;
14601
14602 cx.update_editor(|editor, window, cx| {
14603 editor.change_selections(None, window, cx, |s| {
14604 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14605 });
14606 });
14607
14608 assert_indent_guides(
14609 0..3,
14610 vec![indent_guide(buffer_id, 1, 1, 0)],
14611 Some(vec![0]),
14612 &mut cx,
14613 );
14614}
14615
14616#[gpui::test]
14617async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14618 let (buffer_id, mut cx) = setup_indent_guides_editor(
14619 &"
14620 fn main() {
14621 if 1 == 2 {
14622 let a = 1;
14623 }
14624 }"
14625 .unindent(),
14626 cx,
14627 )
14628 .await;
14629
14630 cx.update_editor(|editor, window, cx| {
14631 editor.change_selections(None, window, cx, |s| {
14632 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14633 });
14634 });
14635
14636 assert_indent_guides(
14637 0..4,
14638 vec![
14639 indent_guide(buffer_id, 1, 3, 0),
14640 indent_guide(buffer_id, 2, 2, 1),
14641 ],
14642 Some(vec![1]),
14643 &mut cx,
14644 );
14645
14646 cx.update_editor(|editor, window, cx| {
14647 editor.change_selections(None, window, cx, |s| {
14648 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14649 });
14650 });
14651
14652 assert_indent_guides(
14653 0..4,
14654 vec![
14655 indent_guide(buffer_id, 1, 3, 0),
14656 indent_guide(buffer_id, 2, 2, 1),
14657 ],
14658 Some(vec![1]),
14659 &mut cx,
14660 );
14661
14662 cx.update_editor(|editor, window, cx| {
14663 editor.change_selections(None, window, cx, |s| {
14664 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14665 });
14666 });
14667
14668 assert_indent_guides(
14669 0..4,
14670 vec![
14671 indent_guide(buffer_id, 1, 3, 0),
14672 indent_guide(buffer_id, 2, 2, 1),
14673 ],
14674 Some(vec![0]),
14675 &mut cx,
14676 );
14677}
14678
14679#[gpui::test]
14680async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
14681 let (buffer_id, mut cx) = setup_indent_guides_editor(
14682 &"
14683 fn main() {
14684 let a = 1;
14685
14686 let b = 2;
14687 }"
14688 .unindent(),
14689 cx,
14690 )
14691 .await;
14692
14693 cx.update_editor(|editor, window, cx| {
14694 editor.change_selections(None, window, cx, |s| {
14695 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14696 });
14697 });
14698
14699 assert_indent_guides(
14700 0..5,
14701 vec![indent_guide(buffer_id, 1, 3, 0)],
14702 Some(vec![0]),
14703 &mut cx,
14704 );
14705}
14706
14707#[gpui::test]
14708async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
14709 let (buffer_id, mut cx) = setup_indent_guides_editor(
14710 &"
14711 def m:
14712 a = 1
14713 pass"
14714 .unindent(),
14715 cx,
14716 )
14717 .await;
14718
14719 cx.update_editor(|editor, window, cx| {
14720 editor.change_selections(None, window, cx, |s| {
14721 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14722 });
14723 });
14724
14725 assert_indent_guides(
14726 0..3,
14727 vec![indent_guide(buffer_id, 1, 2, 0)],
14728 Some(vec![0]),
14729 &mut cx,
14730 );
14731}
14732
14733#[gpui::test]
14734async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
14735 init_test(cx, |_| {});
14736 let mut cx = EditorTestContext::new(cx).await;
14737 let text = indoc! {
14738 "
14739 impl A {
14740 fn b() {
14741 0;
14742 3;
14743 5;
14744 6;
14745 7;
14746 }
14747 }
14748 "
14749 };
14750 let base_text = indoc! {
14751 "
14752 impl A {
14753 fn b() {
14754 0;
14755 1;
14756 2;
14757 3;
14758 4;
14759 }
14760 fn c() {
14761 5;
14762 6;
14763 7;
14764 }
14765 }
14766 "
14767 };
14768
14769 cx.update_editor(|editor, window, cx| {
14770 editor.set_text(text, window, cx);
14771
14772 editor.buffer().update(cx, |multibuffer, cx| {
14773 let buffer = multibuffer.as_singleton().unwrap();
14774 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
14775
14776 multibuffer.set_all_diff_hunks_expanded(cx);
14777 multibuffer.add_diff(diff, cx);
14778
14779 buffer.read(cx).remote_id()
14780 })
14781 });
14782 cx.run_until_parked();
14783
14784 cx.assert_state_with_diff(
14785 indoc! { "
14786 impl A {
14787 fn b() {
14788 0;
14789 - 1;
14790 - 2;
14791 3;
14792 - 4;
14793 - }
14794 - fn c() {
14795 5;
14796 6;
14797 7;
14798 }
14799 }
14800 ˇ"
14801 }
14802 .to_string(),
14803 );
14804
14805 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14806 editor
14807 .snapshot(window, cx)
14808 .buffer_snapshot
14809 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14810 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14811 .collect::<Vec<_>>()
14812 });
14813 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14814 assert_eq!(
14815 actual_guides,
14816 vec![
14817 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14818 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14819 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14820 ]
14821 );
14822}
14823
14824#[gpui::test]
14825fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
14826 init_test(cx, |_| {});
14827
14828 let editor = cx.add_window(|window, cx| {
14829 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
14830 build_editor(buffer, window, cx)
14831 });
14832
14833 let render_args = Arc::new(Mutex::new(None));
14834 let snapshot = editor
14835 .update(cx, |editor, window, cx| {
14836 let snapshot = editor.buffer().read(cx).snapshot(cx);
14837 let range =
14838 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
14839
14840 struct RenderArgs {
14841 row: MultiBufferRow,
14842 folded: bool,
14843 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
14844 }
14845
14846 let crease = Crease::inline(
14847 range,
14848 FoldPlaceholder::test(),
14849 {
14850 let toggle_callback = render_args.clone();
14851 move |row, folded, callback, _window, _cx| {
14852 *toggle_callback.lock() = Some(RenderArgs {
14853 row,
14854 folded,
14855 callback,
14856 });
14857 div()
14858 }
14859 },
14860 |_row, _folded, _window, _cx| div(),
14861 );
14862
14863 editor.insert_creases(Some(crease), cx);
14864 let snapshot = editor.snapshot(window, cx);
14865 let _div = snapshot.render_crease_toggle(
14866 MultiBufferRow(1),
14867 false,
14868 cx.entity().clone(),
14869 window,
14870 cx,
14871 );
14872 snapshot
14873 })
14874 .unwrap();
14875
14876 let render_args = render_args.lock().take().unwrap();
14877 assert_eq!(render_args.row, MultiBufferRow(1));
14878 assert!(!render_args.folded);
14879 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14880
14881 cx.update_window(*editor, |_, window, cx| {
14882 (render_args.callback)(true, window, cx)
14883 })
14884 .unwrap();
14885 let snapshot = editor
14886 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14887 .unwrap();
14888 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
14889
14890 cx.update_window(*editor, |_, window, cx| {
14891 (render_args.callback)(false, window, cx)
14892 })
14893 .unwrap();
14894 let snapshot = editor
14895 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14896 .unwrap();
14897 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14898}
14899
14900#[gpui::test]
14901async fn test_input_text(cx: &mut TestAppContext) {
14902 init_test(cx, |_| {});
14903 let mut cx = EditorTestContext::new(cx).await;
14904
14905 cx.set_state(
14906 &r#"ˇone
14907 two
14908
14909 three
14910 fourˇ
14911 five
14912
14913 siˇx"#
14914 .unindent(),
14915 );
14916
14917 cx.dispatch_action(HandleInput(String::new()));
14918 cx.assert_editor_state(
14919 &r#"ˇone
14920 two
14921
14922 three
14923 fourˇ
14924 five
14925
14926 siˇx"#
14927 .unindent(),
14928 );
14929
14930 cx.dispatch_action(HandleInput("AAAA".to_string()));
14931 cx.assert_editor_state(
14932 &r#"AAAAˇone
14933 two
14934
14935 three
14936 fourAAAAˇ
14937 five
14938
14939 siAAAAˇx"#
14940 .unindent(),
14941 );
14942}
14943
14944#[gpui::test]
14945async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
14946 init_test(cx, |_| {});
14947
14948 let mut cx = EditorTestContext::new(cx).await;
14949 cx.set_state(
14950 r#"let foo = 1;
14951let foo = 2;
14952let foo = 3;
14953let fooˇ = 4;
14954let foo = 5;
14955let foo = 6;
14956let foo = 7;
14957let foo = 8;
14958let foo = 9;
14959let foo = 10;
14960let foo = 11;
14961let foo = 12;
14962let foo = 13;
14963let foo = 14;
14964let foo = 15;"#,
14965 );
14966
14967 cx.update_editor(|e, window, cx| {
14968 assert_eq!(
14969 e.next_scroll_position,
14970 NextScrollCursorCenterTopBottom::Center,
14971 "Default next scroll direction is center",
14972 );
14973
14974 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14975 assert_eq!(
14976 e.next_scroll_position,
14977 NextScrollCursorCenterTopBottom::Top,
14978 "After center, next scroll direction should be top",
14979 );
14980
14981 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14982 assert_eq!(
14983 e.next_scroll_position,
14984 NextScrollCursorCenterTopBottom::Bottom,
14985 "After top, next scroll direction should be bottom",
14986 );
14987
14988 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14989 assert_eq!(
14990 e.next_scroll_position,
14991 NextScrollCursorCenterTopBottom::Center,
14992 "After bottom, scrolling should start over",
14993 );
14994
14995 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14996 assert_eq!(
14997 e.next_scroll_position,
14998 NextScrollCursorCenterTopBottom::Top,
14999 "Scrolling continues if retriggered fast enough"
15000 );
15001 });
15002
15003 cx.executor()
15004 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15005 cx.executor().run_until_parked();
15006 cx.update_editor(|e, _, _| {
15007 assert_eq!(
15008 e.next_scroll_position,
15009 NextScrollCursorCenterTopBottom::Center,
15010 "If scrolling is not triggered fast enough, it should reset"
15011 );
15012 });
15013}
15014
15015#[gpui::test]
15016async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15017 init_test(cx, |_| {});
15018 let mut cx = EditorLspTestContext::new_rust(
15019 lsp::ServerCapabilities {
15020 definition_provider: Some(lsp::OneOf::Left(true)),
15021 references_provider: Some(lsp::OneOf::Left(true)),
15022 ..lsp::ServerCapabilities::default()
15023 },
15024 cx,
15025 )
15026 .await;
15027
15028 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15029 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15030 move |params, _| async move {
15031 if empty_go_to_definition {
15032 Ok(None)
15033 } else {
15034 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15035 uri: params.text_document_position_params.text_document.uri,
15036 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15037 })))
15038 }
15039 },
15040 );
15041 let references =
15042 cx.lsp
15043 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15044 Ok(Some(vec![lsp::Location {
15045 uri: params.text_document_position.text_document.uri,
15046 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15047 }]))
15048 });
15049 (go_to_definition, references)
15050 };
15051
15052 cx.set_state(
15053 &r#"fn one() {
15054 let mut a = ˇtwo();
15055 }
15056
15057 fn two() {}"#
15058 .unindent(),
15059 );
15060 set_up_lsp_handlers(false, &mut cx);
15061 let navigated = cx
15062 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15063 .await
15064 .expect("Failed to navigate to definition");
15065 assert_eq!(
15066 navigated,
15067 Navigated::Yes,
15068 "Should have navigated to definition from the GetDefinition response"
15069 );
15070 cx.assert_editor_state(
15071 &r#"fn one() {
15072 let mut a = two();
15073 }
15074
15075 fn «twoˇ»() {}"#
15076 .unindent(),
15077 );
15078
15079 let editors = cx.update_workspace(|workspace, _, cx| {
15080 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15081 });
15082 cx.update_editor(|_, _, test_editor_cx| {
15083 assert_eq!(
15084 editors.len(),
15085 1,
15086 "Initially, only one, test, editor should be open in the workspace"
15087 );
15088 assert_eq!(
15089 test_editor_cx.entity(),
15090 editors.last().expect("Asserted len is 1").clone()
15091 );
15092 });
15093
15094 set_up_lsp_handlers(true, &mut cx);
15095 let navigated = cx
15096 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15097 .await
15098 .expect("Failed to navigate to lookup references");
15099 assert_eq!(
15100 navigated,
15101 Navigated::Yes,
15102 "Should have navigated to references as a fallback after empty GoToDefinition response"
15103 );
15104 // We should not change the selections in the existing file,
15105 // if opening another milti buffer with the references
15106 cx.assert_editor_state(
15107 &r#"fn one() {
15108 let mut a = two();
15109 }
15110
15111 fn «twoˇ»() {}"#
15112 .unindent(),
15113 );
15114 let editors = cx.update_workspace(|workspace, _, cx| {
15115 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15116 });
15117 cx.update_editor(|_, _, test_editor_cx| {
15118 assert_eq!(
15119 editors.len(),
15120 2,
15121 "After falling back to references search, we open a new editor with the results"
15122 );
15123 let references_fallback_text = editors
15124 .into_iter()
15125 .find(|new_editor| *new_editor != test_editor_cx.entity())
15126 .expect("Should have one non-test editor now")
15127 .read(test_editor_cx)
15128 .text(test_editor_cx);
15129 assert_eq!(
15130 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15131 "Should use the range from the references response and not the GoToDefinition one"
15132 );
15133 });
15134}
15135
15136#[gpui::test]
15137async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15138 init_test(cx, |_| {});
15139
15140 let language = Arc::new(Language::new(
15141 LanguageConfig::default(),
15142 Some(tree_sitter_rust::LANGUAGE.into()),
15143 ));
15144
15145 let text = r#"
15146 #[cfg(test)]
15147 mod tests() {
15148 #[test]
15149 fn runnable_1() {
15150 let a = 1;
15151 }
15152
15153 #[test]
15154 fn runnable_2() {
15155 let a = 1;
15156 let b = 2;
15157 }
15158 }
15159 "#
15160 .unindent();
15161
15162 let fs = FakeFs::new(cx.executor());
15163 fs.insert_file("/file.rs", Default::default()).await;
15164
15165 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15166 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15167 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15168 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15169 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15170
15171 let editor = cx.new_window_entity(|window, cx| {
15172 Editor::new(
15173 EditorMode::Full,
15174 multi_buffer,
15175 Some(project.clone()),
15176 true,
15177 window,
15178 cx,
15179 )
15180 });
15181
15182 editor.update_in(cx, |editor, window, cx| {
15183 editor.tasks.insert(
15184 (buffer.read(cx).remote_id(), 3),
15185 RunnableTasks {
15186 templates: vec![],
15187 offset: MultiBufferOffset(43),
15188 column: 0,
15189 extra_variables: HashMap::default(),
15190 context_range: BufferOffset(43)..BufferOffset(85),
15191 },
15192 );
15193 editor.tasks.insert(
15194 (buffer.read(cx).remote_id(), 8),
15195 RunnableTasks {
15196 templates: vec![],
15197 offset: MultiBufferOffset(86),
15198 column: 0,
15199 extra_variables: HashMap::default(),
15200 context_range: BufferOffset(86)..BufferOffset(191),
15201 },
15202 );
15203
15204 // Test finding task when cursor is inside function body
15205 editor.change_selections(None, window, cx, |s| {
15206 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15207 });
15208 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15209 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15210
15211 // Test finding task when cursor is on function name
15212 editor.change_selections(None, window, cx, |s| {
15213 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15214 });
15215 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15216 assert_eq!(row, 8, "Should find task when cursor is on function name");
15217 });
15218}
15219
15220#[gpui::test]
15221async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
15222 init_test(cx, |_| {});
15223
15224 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15225 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15226 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15227
15228 let fs = FakeFs::new(cx.executor());
15229 fs.insert_tree(
15230 path!("/a"),
15231 json!({
15232 "first.rs": sample_text_1,
15233 "second.rs": sample_text_2,
15234 "third.rs": sample_text_3,
15235 }),
15236 )
15237 .await;
15238 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15239 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15240 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15241 let worktree = project.update(cx, |project, cx| {
15242 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15243 assert_eq!(worktrees.len(), 1);
15244 worktrees.pop().unwrap()
15245 });
15246 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15247
15248 let buffer_1 = project
15249 .update(cx, |project, cx| {
15250 project.open_buffer((worktree_id, "first.rs"), cx)
15251 })
15252 .await
15253 .unwrap();
15254 let buffer_2 = project
15255 .update(cx, |project, cx| {
15256 project.open_buffer((worktree_id, "second.rs"), cx)
15257 })
15258 .await
15259 .unwrap();
15260 let buffer_3 = project
15261 .update(cx, |project, cx| {
15262 project.open_buffer((worktree_id, "third.rs"), cx)
15263 })
15264 .await
15265 .unwrap();
15266
15267 let multi_buffer = cx.new(|cx| {
15268 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15269 multi_buffer.push_excerpts(
15270 buffer_1.clone(),
15271 [
15272 ExcerptRange {
15273 context: Point::new(0, 0)..Point::new(3, 0),
15274 primary: None,
15275 },
15276 ExcerptRange {
15277 context: Point::new(5, 0)..Point::new(7, 0),
15278 primary: None,
15279 },
15280 ExcerptRange {
15281 context: Point::new(9, 0)..Point::new(10, 4),
15282 primary: None,
15283 },
15284 ],
15285 cx,
15286 );
15287 multi_buffer.push_excerpts(
15288 buffer_2.clone(),
15289 [
15290 ExcerptRange {
15291 context: Point::new(0, 0)..Point::new(3, 0),
15292 primary: None,
15293 },
15294 ExcerptRange {
15295 context: Point::new(5, 0)..Point::new(7, 0),
15296 primary: None,
15297 },
15298 ExcerptRange {
15299 context: Point::new(9, 0)..Point::new(10, 4),
15300 primary: None,
15301 },
15302 ],
15303 cx,
15304 );
15305 multi_buffer.push_excerpts(
15306 buffer_3.clone(),
15307 [
15308 ExcerptRange {
15309 context: Point::new(0, 0)..Point::new(3, 0),
15310 primary: None,
15311 },
15312 ExcerptRange {
15313 context: Point::new(5, 0)..Point::new(7, 0),
15314 primary: None,
15315 },
15316 ExcerptRange {
15317 context: Point::new(9, 0)..Point::new(10, 4),
15318 primary: None,
15319 },
15320 ],
15321 cx,
15322 );
15323 multi_buffer
15324 });
15325 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15326 Editor::new(
15327 EditorMode::Full,
15328 multi_buffer,
15329 Some(project.clone()),
15330 true,
15331 window,
15332 cx,
15333 )
15334 });
15335
15336 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";
15337 assert_eq!(
15338 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15339 full_text,
15340 );
15341
15342 multi_buffer_editor.update(cx, |editor, cx| {
15343 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15344 });
15345 assert_eq!(
15346 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15347 "\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",
15348 "After folding the first buffer, its text should not be displayed"
15349 );
15350
15351 multi_buffer_editor.update(cx, |editor, cx| {
15352 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15353 });
15354 assert_eq!(
15355 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15356 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15357 "After folding the second buffer, its text should not be displayed"
15358 );
15359
15360 multi_buffer_editor.update(cx, |editor, cx| {
15361 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15362 });
15363 assert_eq!(
15364 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15365 "\n\n\n\n\n",
15366 "After folding the third buffer, its text should not be displayed"
15367 );
15368
15369 // Emulate selection inside the fold logic, that should work
15370 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15371 editor
15372 .snapshot(window, cx)
15373 .next_line_boundary(Point::new(0, 4));
15374 });
15375
15376 multi_buffer_editor.update(cx, |editor, cx| {
15377 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15378 });
15379 assert_eq!(
15380 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15381 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15382 "After unfolding the second buffer, its text should be displayed"
15383 );
15384
15385 multi_buffer_editor.update(cx, |editor, cx| {
15386 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15387 });
15388 assert_eq!(
15389 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15390 "\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",
15391 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15392 );
15393
15394 multi_buffer_editor.update(cx, |editor, cx| {
15395 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15396 });
15397 assert_eq!(
15398 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15399 full_text,
15400 "After unfolding the all buffers, all original text should be displayed"
15401 );
15402}
15403
15404#[gpui::test]
15405async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
15406 init_test(cx, |_| {});
15407
15408 let sample_text_1 = "1111\n2222\n3333".to_string();
15409 let sample_text_2 = "4444\n5555\n6666".to_string();
15410 let sample_text_3 = "7777\n8888\n9999".to_string();
15411
15412 let fs = FakeFs::new(cx.executor());
15413 fs.insert_tree(
15414 path!("/a"),
15415 json!({
15416 "first.rs": sample_text_1,
15417 "second.rs": sample_text_2,
15418 "third.rs": sample_text_3,
15419 }),
15420 )
15421 .await;
15422 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15423 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15424 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15425 let worktree = project.update(cx, |project, cx| {
15426 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15427 assert_eq!(worktrees.len(), 1);
15428 worktrees.pop().unwrap()
15429 });
15430 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15431
15432 let buffer_1 = project
15433 .update(cx, |project, cx| {
15434 project.open_buffer((worktree_id, "first.rs"), cx)
15435 })
15436 .await
15437 .unwrap();
15438 let buffer_2 = project
15439 .update(cx, |project, cx| {
15440 project.open_buffer((worktree_id, "second.rs"), cx)
15441 })
15442 .await
15443 .unwrap();
15444 let buffer_3 = project
15445 .update(cx, |project, cx| {
15446 project.open_buffer((worktree_id, "third.rs"), cx)
15447 })
15448 .await
15449 .unwrap();
15450
15451 let multi_buffer = cx.new(|cx| {
15452 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15453 multi_buffer.push_excerpts(
15454 buffer_1.clone(),
15455 [ExcerptRange {
15456 context: Point::new(0, 0)..Point::new(3, 0),
15457 primary: None,
15458 }],
15459 cx,
15460 );
15461 multi_buffer.push_excerpts(
15462 buffer_2.clone(),
15463 [ExcerptRange {
15464 context: Point::new(0, 0)..Point::new(3, 0),
15465 primary: None,
15466 }],
15467 cx,
15468 );
15469 multi_buffer.push_excerpts(
15470 buffer_3.clone(),
15471 [ExcerptRange {
15472 context: Point::new(0, 0)..Point::new(3, 0),
15473 primary: None,
15474 }],
15475 cx,
15476 );
15477 multi_buffer
15478 });
15479
15480 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15481 Editor::new(
15482 EditorMode::Full,
15483 multi_buffer,
15484 Some(project.clone()),
15485 true,
15486 window,
15487 cx,
15488 )
15489 });
15490
15491 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
15492 assert_eq!(
15493 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15494 full_text,
15495 );
15496
15497 multi_buffer_editor.update(cx, |editor, cx| {
15498 editor.fold_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\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
15503 "After folding the first buffer, its text should not be displayed"
15504 );
15505
15506 multi_buffer_editor.update(cx, |editor, cx| {
15507 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15508 });
15509
15510 assert_eq!(
15511 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15512 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
15513 "After folding the second buffer, its text should not be displayed"
15514 );
15515
15516 multi_buffer_editor.update(cx, |editor, cx| {
15517 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15518 });
15519 assert_eq!(
15520 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15521 "\n\n\n\n\n",
15522 "After folding the third buffer, its text should not be displayed"
15523 );
15524
15525 multi_buffer_editor.update(cx, |editor, cx| {
15526 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15527 });
15528 assert_eq!(
15529 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15530 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
15531 "After unfolding the second buffer, its text should be displayed"
15532 );
15533
15534 multi_buffer_editor.update(cx, |editor, cx| {
15535 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15536 });
15537 assert_eq!(
15538 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15539 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
15540 "After unfolding the first buffer, its text should be displayed"
15541 );
15542
15543 multi_buffer_editor.update(cx, |editor, cx| {
15544 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15545 });
15546 assert_eq!(
15547 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15548 full_text,
15549 "After unfolding all buffers, all original text should be displayed"
15550 );
15551}
15552
15553#[gpui::test]
15554async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) {
15555 init_test(cx, |_| {});
15556
15557 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15558
15559 let fs = FakeFs::new(cx.executor());
15560 fs.insert_tree(
15561 path!("/a"),
15562 json!({
15563 "main.rs": sample_text,
15564 }),
15565 )
15566 .await;
15567 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15568 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15569 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15570 let worktree = project.update(cx, |project, cx| {
15571 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15572 assert_eq!(worktrees.len(), 1);
15573 worktrees.pop().unwrap()
15574 });
15575 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15576
15577 let buffer_1 = project
15578 .update(cx, |project, cx| {
15579 project.open_buffer((worktree_id, "main.rs"), cx)
15580 })
15581 .await
15582 .unwrap();
15583
15584 let multi_buffer = cx.new(|cx| {
15585 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15586 multi_buffer.push_excerpts(
15587 buffer_1.clone(),
15588 [ExcerptRange {
15589 context: Point::new(0, 0)
15590 ..Point::new(
15591 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
15592 0,
15593 ),
15594 primary: None,
15595 }],
15596 cx,
15597 );
15598 multi_buffer
15599 });
15600 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15601 Editor::new(
15602 EditorMode::Full,
15603 multi_buffer,
15604 Some(project.clone()),
15605 true,
15606 window,
15607 cx,
15608 )
15609 });
15610
15611 let selection_range = Point::new(1, 0)..Point::new(2, 0);
15612 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15613 enum TestHighlight {}
15614 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
15615 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
15616 editor.highlight_text::<TestHighlight>(
15617 vec![highlight_range.clone()],
15618 HighlightStyle::color(Hsla::green()),
15619 cx,
15620 );
15621 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
15622 });
15623
15624 let full_text = format!("\n\n\n{sample_text}\n");
15625 assert_eq!(
15626 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15627 full_text,
15628 );
15629}
15630
15631#[gpui::test]
15632async fn test_inline_completion_text(cx: &mut TestAppContext) {
15633 init_test(cx, |_| {});
15634
15635 // Simple insertion
15636 assert_highlighted_edits(
15637 "Hello, world!",
15638 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
15639 true,
15640 cx,
15641 |highlighted_edits, cx| {
15642 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
15643 assert_eq!(highlighted_edits.highlights.len(), 1);
15644 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
15645 assert_eq!(
15646 highlighted_edits.highlights[0].1.background_color,
15647 Some(cx.theme().status().created_background)
15648 );
15649 },
15650 )
15651 .await;
15652
15653 // Replacement
15654 assert_highlighted_edits(
15655 "This is a test.",
15656 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
15657 false,
15658 cx,
15659 |highlighted_edits, cx| {
15660 assert_eq!(highlighted_edits.text, "That is a test.");
15661 assert_eq!(highlighted_edits.highlights.len(), 1);
15662 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
15663 assert_eq!(
15664 highlighted_edits.highlights[0].1.background_color,
15665 Some(cx.theme().status().created_background)
15666 );
15667 },
15668 )
15669 .await;
15670
15671 // Multiple edits
15672 assert_highlighted_edits(
15673 "Hello, world!",
15674 vec![
15675 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
15676 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
15677 ],
15678 false,
15679 cx,
15680 |highlighted_edits, cx| {
15681 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
15682 assert_eq!(highlighted_edits.highlights.len(), 2);
15683 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
15684 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
15685 assert_eq!(
15686 highlighted_edits.highlights[0].1.background_color,
15687 Some(cx.theme().status().created_background)
15688 );
15689 assert_eq!(
15690 highlighted_edits.highlights[1].1.background_color,
15691 Some(cx.theme().status().created_background)
15692 );
15693 },
15694 )
15695 .await;
15696
15697 // Multiple lines with edits
15698 assert_highlighted_edits(
15699 "First line\nSecond line\nThird line\nFourth line",
15700 vec![
15701 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
15702 (
15703 Point::new(2, 0)..Point::new(2, 10),
15704 "New third line".to_string(),
15705 ),
15706 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
15707 ],
15708 false,
15709 cx,
15710 |highlighted_edits, cx| {
15711 assert_eq!(
15712 highlighted_edits.text,
15713 "Second modified\nNew third line\nFourth updated line"
15714 );
15715 assert_eq!(highlighted_edits.highlights.len(), 3);
15716 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
15717 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
15718 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
15719 for highlight in &highlighted_edits.highlights {
15720 assert_eq!(
15721 highlight.1.background_color,
15722 Some(cx.theme().status().created_background)
15723 );
15724 }
15725 },
15726 )
15727 .await;
15728}
15729
15730#[gpui::test]
15731async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
15732 init_test(cx, |_| {});
15733
15734 // Deletion
15735 assert_highlighted_edits(
15736 "Hello, world!",
15737 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
15738 true,
15739 cx,
15740 |highlighted_edits, cx| {
15741 assert_eq!(highlighted_edits.text, "Hello, world!");
15742 assert_eq!(highlighted_edits.highlights.len(), 1);
15743 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
15744 assert_eq!(
15745 highlighted_edits.highlights[0].1.background_color,
15746 Some(cx.theme().status().deleted_background)
15747 );
15748 },
15749 )
15750 .await;
15751
15752 // Insertion
15753 assert_highlighted_edits(
15754 "Hello, world!",
15755 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
15756 true,
15757 cx,
15758 |highlighted_edits, cx| {
15759 assert_eq!(highlighted_edits.highlights.len(), 1);
15760 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
15761 assert_eq!(
15762 highlighted_edits.highlights[0].1.background_color,
15763 Some(cx.theme().status().created_background)
15764 );
15765 },
15766 )
15767 .await;
15768}
15769
15770async fn assert_highlighted_edits(
15771 text: &str,
15772 edits: Vec<(Range<Point>, String)>,
15773 include_deletions: bool,
15774 cx: &mut TestAppContext,
15775 assertion_fn: impl Fn(HighlightedText, &App),
15776) {
15777 let window = cx.add_window(|window, cx| {
15778 let buffer = MultiBuffer::build_simple(text, cx);
15779 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
15780 });
15781 let cx = &mut VisualTestContext::from_window(*window, cx);
15782
15783 let (buffer, snapshot) = window
15784 .update(cx, |editor, _window, cx| {
15785 (
15786 editor.buffer().clone(),
15787 editor.buffer().read(cx).snapshot(cx),
15788 )
15789 })
15790 .unwrap();
15791
15792 let edits = edits
15793 .into_iter()
15794 .map(|(range, edit)| {
15795 (
15796 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
15797 edit,
15798 )
15799 })
15800 .collect::<Vec<_>>();
15801
15802 let text_anchor_edits = edits
15803 .clone()
15804 .into_iter()
15805 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
15806 .collect::<Vec<_>>();
15807
15808 let edit_preview = window
15809 .update(cx, |_, _window, cx| {
15810 buffer
15811 .read(cx)
15812 .as_singleton()
15813 .unwrap()
15814 .read(cx)
15815 .preview_edits(text_anchor_edits.into(), cx)
15816 })
15817 .unwrap()
15818 .await;
15819
15820 cx.update(|_window, cx| {
15821 let highlighted_edits = inline_completion_edit_text(
15822 &snapshot.as_singleton().unwrap().2,
15823 &edits,
15824 &edit_preview,
15825 include_deletions,
15826 cx,
15827 );
15828 assertion_fn(highlighted_edits, cx)
15829 });
15830}
15831
15832#[gpui::test]
15833async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
15834 init_test(cx, |_| {});
15835 let capabilities = lsp::ServerCapabilities {
15836 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
15837 prepare_provider: Some(true),
15838 work_done_progress_options: Default::default(),
15839 })),
15840 ..Default::default()
15841 };
15842 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15843
15844 cx.set_state(indoc! {"
15845 struct Fˇoo {}
15846 "});
15847
15848 cx.update_editor(|editor, _, cx| {
15849 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15850 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15851 editor.highlight_background::<DocumentHighlightRead>(
15852 &[highlight_range],
15853 |c| c.editor_document_highlight_read_background,
15854 cx,
15855 );
15856 });
15857
15858 let mut prepare_rename_handler =
15859 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
15860 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
15861 start: lsp::Position {
15862 line: 0,
15863 character: 7,
15864 },
15865 end: lsp::Position {
15866 line: 0,
15867 character: 10,
15868 },
15869 })))
15870 });
15871 let prepare_rename_task = cx
15872 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15873 .expect("Prepare rename was not started");
15874 prepare_rename_handler.next().await.unwrap();
15875 prepare_rename_task.await.expect("Prepare rename failed");
15876
15877 let mut rename_handler =
15878 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15879 let edit = lsp::TextEdit {
15880 range: lsp::Range {
15881 start: lsp::Position {
15882 line: 0,
15883 character: 7,
15884 },
15885 end: lsp::Position {
15886 line: 0,
15887 character: 10,
15888 },
15889 },
15890 new_text: "FooRenamed".to_string(),
15891 };
15892 Ok(Some(lsp::WorkspaceEdit::new(
15893 // Specify the same edit twice
15894 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
15895 )))
15896 });
15897 let rename_task = cx
15898 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15899 .expect("Confirm rename was not started");
15900 rename_handler.next().await.unwrap();
15901 rename_task.await.expect("Confirm rename failed");
15902 cx.run_until_parked();
15903
15904 // Despite two edits, only one is actually applied as those are identical
15905 cx.assert_editor_state(indoc! {"
15906 struct FooRenamedˇ {}
15907 "});
15908}
15909
15910#[gpui::test]
15911async fn test_rename_without_prepare(cx: &mut TestAppContext) {
15912 init_test(cx, |_| {});
15913 // These capabilities indicate that the server does not support prepare rename.
15914 let capabilities = lsp::ServerCapabilities {
15915 rename_provider: Some(lsp::OneOf::Left(true)),
15916 ..Default::default()
15917 };
15918 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15919
15920 cx.set_state(indoc! {"
15921 struct Fˇoo {}
15922 "});
15923
15924 cx.update_editor(|editor, _window, cx| {
15925 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15926 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15927 editor.highlight_background::<DocumentHighlightRead>(
15928 &[highlight_range],
15929 |c| c.editor_document_highlight_read_background,
15930 cx,
15931 );
15932 });
15933
15934 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15935 .expect("Prepare rename was not started")
15936 .await
15937 .expect("Prepare rename failed");
15938
15939 let mut rename_handler =
15940 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15941 let edit = lsp::TextEdit {
15942 range: lsp::Range {
15943 start: lsp::Position {
15944 line: 0,
15945 character: 7,
15946 },
15947 end: lsp::Position {
15948 line: 0,
15949 character: 10,
15950 },
15951 },
15952 new_text: "FooRenamed".to_string(),
15953 };
15954 Ok(Some(lsp::WorkspaceEdit::new(
15955 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
15956 )))
15957 });
15958 let rename_task = cx
15959 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15960 .expect("Confirm rename was not started");
15961 rename_handler.next().await.unwrap();
15962 rename_task.await.expect("Confirm rename failed");
15963 cx.run_until_parked();
15964
15965 // Correct range is renamed, as `surrounding_word` is used to find it.
15966 cx.assert_editor_state(indoc! {"
15967 struct FooRenamedˇ {}
15968 "});
15969}
15970
15971#[gpui::test]
15972async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
15973 init_test(cx, |_| {});
15974 let mut cx = EditorTestContext::new(cx).await;
15975
15976 let language = Arc::new(
15977 Language::new(
15978 LanguageConfig::default(),
15979 Some(tree_sitter_html::LANGUAGE.into()),
15980 )
15981 .with_brackets_query(
15982 r#"
15983 ("<" @open "/>" @close)
15984 ("</" @open ">" @close)
15985 ("<" @open ">" @close)
15986 ("\"" @open "\"" @close)
15987 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
15988 "#,
15989 )
15990 .unwrap(),
15991 );
15992 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15993
15994 cx.set_state(indoc! {"
15995 <span>ˇ</span>
15996 "});
15997 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
15998 cx.assert_editor_state(indoc! {"
15999 <span>
16000 ˇ
16001 </span>
16002 "});
16003
16004 cx.set_state(indoc! {"
16005 <span><span></span>ˇ</span>
16006 "});
16007 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16008 cx.assert_editor_state(indoc! {"
16009 <span><span></span>
16010 ˇ</span>
16011 "});
16012
16013 cx.set_state(indoc! {"
16014 <span>ˇ
16015 </span>
16016 "});
16017 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16018 cx.assert_editor_state(indoc! {"
16019 <span>
16020 ˇ
16021 </span>
16022 "});
16023}
16024
16025fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16026 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16027 point..point
16028}
16029
16030fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16031 let (text, ranges) = marked_text_ranges(marked_text, true);
16032 assert_eq!(editor.text(cx), text);
16033 assert_eq!(
16034 editor.selections.ranges(cx),
16035 ranges,
16036 "Assert selections are {}",
16037 marked_text
16038 );
16039}
16040
16041pub fn handle_signature_help_request(
16042 cx: &mut EditorLspTestContext,
16043 mocked_response: lsp::SignatureHelp,
16044) -> impl Future<Output = ()> {
16045 let mut request =
16046 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16047 let mocked_response = mocked_response.clone();
16048 async move { Ok(Some(mocked_response)) }
16049 });
16050
16051 async move {
16052 request.next().await;
16053 }
16054}
16055
16056/// Handle completion request passing a marked string specifying where the completion
16057/// should be triggered from using '|' character, what range should be replaced, and what completions
16058/// should be returned using '<' and '>' to delimit the range
16059pub fn handle_completion_request(
16060 cx: &mut EditorLspTestContext,
16061 marked_string: &str,
16062 completions: Vec<&'static str>,
16063 counter: Arc<AtomicUsize>,
16064) -> impl Future<Output = ()> {
16065 let complete_from_marker: TextRangeMarker = '|'.into();
16066 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16067 let (_, mut marked_ranges) = marked_text_ranges_by(
16068 marked_string,
16069 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16070 );
16071
16072 let complete_from_position =
16073 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16074 let replace_range =
16075 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16076
16077 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16078 let completions = completions.clone();
16079 counter.fetch_add(1, atomic::Ordering::Release);
16080 async move {
16081 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16082 assert_eq!(
16083 params.text_document_position.position,
16084 complete_from_position
16085 );
16086 Ok(Some(lsp::CompletionResponse::Array(
16087 completions
16088 .iter()
16089 .map(|completion_text| lsp::CompletionItem {
16090 label: completion_text.to_string(),
16091 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16092 range: replace_range,
16093 new_text: completion_text.to_string(),
16094 })),
16095 ..Default::default()
16096 })
16097 .collect(),
16098 )))
16099 }
16100 });
16101
16102 async move {
16103 request.next().await;
16104 }
16105}
16106
16107fn handle_resolve_completion_request(
16108 cx: &mut EditorLspTestContext,
16109 edits: Option<Vec<(&'static str, &'static str)>>,
16110) -> impl Future<Output = ()> {
16111 let edits = edits.map(|edits| {
16112 edits
16113 .iter()
16114 .map(|(marked_string, new_text)| {
16115 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16116 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16117 lsp::TextEdit::new(replace_range, new_text.to_string())
16118 })
16119 .collect::<Vec<_>>()
16120 });
16121
16122 let mut request =
16123 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16124 let edits = edits.clone();
16125 async move {
16126 Ok(lsp::CompletionItem {
16127 additional_text_edits: edits,
16128 ..Default::default()
16129 })
16130 }
16131 });
16132
16133 async move {
16134 request.next().await;
16135 }
16136}
16137
16138pub(crate) fn update_test_language_settings(
16139 cx: &mut TestAppContext,
16140 f: impl Fn(&mut AllLanguageSettingsContent),
16141) {
16142 cx.update(|cx| {
16143 SettingsStore::update_global(cx, |store, cx| {
16144 store.update_user_settings::<AllLanguageSettings>(cx, f);
16145 });
16146 });
16147}
16148
16149pub(crate) fn update_test_project_settings(
16150 cx: &mut TestAppContext,
16151 f: impl Fn(&mut ProjectSettings),
16152) {
16153 cx.update(|cx| {
16154 SettingsStore::update_global(cx, |store, cx| {
16155 store.update_user_settings::<ProjectSettings>(cx, f);
16156 });
16157 });
16158}
16159
16160pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16161 cx.update(|cx| {
16162 assets::Assets.load_test_fonts(cx);
16163 let store = SettingsStore::test(cx);
16164 cx.set_global(store);
16165 theme::init(theme::LoadThemes::JustBase, cx);
16166 release_channel::init(SemanticVersion::default(), cx);
16167 client::init_settings(cx);
16168 language::init(cx);
16169 Project::init_settings(cx);
16170 workspace::init_settings(cx);
16171 crate::init(cx);
16172 });
16173
16174 update_test_language_settings(cx, f);
16175}
16176
16177#[track_caller]
16178fn assert_hunk_revert(
16179 not_reverted_text_with_selections: &str,
16180 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
16181 expected_reverted_text_with_selections: &str,
16182 base_text: &str,
16183 cx: &mut EditorLspTestContext,
16184) {
16185 cx.set_state(not_reverted_text_with_selections);
16186 cx.set_diff_base(base_text);
16187 cx.executor().run_until_parked();
16188
16189 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16190 let snapshot = editor.snapshot(window, cx);
16191 let reverted_hunk_statuses = snapshot
16192 .buffer_snapshot
16193 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16194 .map(|hunk| hunk.status())
16195 .collect::<Vec<_>>();
16196
16197 editor.git_restore(&Default::default(), window, cx);
16198 reverted_hunk_statuses
16199 });
16200 cx.executor().run_until_parked();
16201 cx.assert_editor_state(expected_reverted_text_with_selections);
16202 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16203}