1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
24 Override, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::IndentGuide;
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 stop_at_indent: true,
1514 };
1515
1516 let move_to_end = MoveToEndOfLine {
1517 stop_at_soft_wraps: true,
1518 };
1519
1520 let editor = cx.add_window(|window, cx| {
1521 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1522 build_editor(buffer, window, cx)
1523 });
1524 _ = editor.update(cx, |editor, window, cx| {
1525 editor.change_selections(None, window, cx, |s| {
1526 s.select_display_ranges([
1527 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1528 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1529 ]);
1530 });
1531 });
1532
1533 _ = editor.update(cx, |editor, window, cx| {
1534 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1535 assert_eq!(
1536 editor.selections.display_ranges(cx),
1537 &[
1538 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1539 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1540 ]
1541 );
1542 });
1543
1544 _ = editor.update(cx, |editor, window, cx| {
1545 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1546 assert_eq!(
1547 editor.selections.display_ranges(cx),
1548 &[
1549 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1550 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1551 ]
1552 );
1553 });
1554
1555 _ = editor.update(cx, |editor, window, cx| {
1556 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1557 assert_eq!(
1558 editor.selections.display_ranges(cx),
1559 &[
1560 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1561 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1562 ]
1563 );
1564 });
1565
1566 _ = editor.update(cx, |editor, window, cx| {
1567 editor.move_to_end_of_line(&move_to_end, window, cx);
1568 assert_eq!(
1569 editor.selections.display_ranges(cx),
1570 &[
1571 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1572 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1573 ]
1574 );
1575 });
1576
1577 // Moving to the end of line again is a no-op.
1578 _ = editor.update(cx, |editor, window, cx| {
1579 editor.move_to_end_of_line(&move_to_end, window, cx);
1580 assert_eq!(
1581 editor.selections.display_ranges(cx),
1582 &[
1583 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1584 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1585 ]
1586 );
1587 });
1588
1589 _ = editor.update(cx, |editor, window, cx| {
1590 editor.move_left(&MoveLeft, window, cx);
1591 editor.select_to_beginning_of_line(
1592 &SelectToBeginningOfLine {
1593 stop_at_soft_wraps: true,
1594 stop_at_indent: true,
1595 },
1596 window,
1597 cx,
1598 );
1599 assert_eq!(
1600 editor.selections.display_ranges(cx),
1601 &[
1602 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1603 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1604 ]
1605 );
1606 });
1607
1608 _ = editor.update(cx, |editor, window, cx| {
1609 editor.select_to_beginning_of_line(
1610 &SelectToBeginningOfLine {
1611 stop_at_soft_wraps: true,
1612 stop_at_indent: true,
1613 },
1614 window,
1615 cx,
1616 );
1617 assert_eq!(
1618 editor.selections.display_ranges(cx),
1619 &[
1620 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1621 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1622 ]
1623 );
1624 });
1625
1626 _ = editor.update(cx, |editor, window, cx| {
1627 editor.select_to_beginning_of_line(
1628 &SelectToBeginningOfLine {
1629 stop_at_soft_wraps: true,
1630 stop_at_indent: true,
1631 },
1632 window,
1633 cx,
1634 );
1635 assert_eq!(
1636 editor.selections.display_ranges(cx),
1637 &[
1638 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1639 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1640 ]
1641 );
1642 });
1643
1644 _ = editor.update(cx, |editor, window, cx| {
1645 editor.select_to_end_of_line(
1646 &SelectToEndOfLine {
1647 stop_at_soft_wraps: true,
1648 },
1649 window,
1650 cx,
1651 );
1652 assert_eq!(
1653 editor.selections.display_ranges(cx),
1654 &[
1655 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1656 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1657 ]
1658 );
1659 });
1660
1661 _ = editor.update(cx, |editor, window, cx| {
1662 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1663 assert_eq!(editor.display_text(cx), "ab\n de");
1664 assert_eq!(
1665 editor.selections.display_ranges(cx),
1666 &[
1667 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1668 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1669 ]
1670 );
1671 });
1672
1673 _ = editor.update(cx, |editor, window, cx| {
1674 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
1675 assert_eq!(editor.display_text(cx), "\n");
1676 assert_eq!(
1677 editor.selections.display_ranges(cx),
1678 &[
1679 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1680 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1681 ]
1682 );
1683 });
1684}
1685
1686#[gpui::test]
1687fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1688 init_test(cx, |_| {});
1689 let move_to_beg = MoveToBeginningOfLine {
1690 stop_at_soft_wraps: false,
1691 stop_at_indent: false,
1692 };
1693
1694 let move_to_end = MoveToEndOfLine {
1695 stop_at_soft_wraps: false,
1696 };
1697
1698 let editor = cx.add_window(|window, cx| {
1699 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1700 build_editor(buffer, window, cx)
1701 });
1702
1703 _ = editor.update(cx, |editor, window, cx| {
1704 editor.set_wrap_width(Some(140.0.into()), cx);
1705
1706 // We expect the following lines after wrapping
1707 // ```
1708 // thequickbrownfox
1709 // jumpedoverthelazydo
1710 // gs
1711 // ```
1712 // The final `gs` was soft-wrapped onto a new line.
1713 assert_eq!(
1714 "thequickbrownfox\njumpedoverthelaz\nydogs",
1715 editor.display_text(cx),
1716 );
1717
1718 // First, let's assert behavior on the first line, that was not soft-wrapped.
1719 // Start the cursor at the `k` on the first line
1720 editor.change_selections(None, window, cx, |s| {
1721 s.select_display_ranges([
1722 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1723 ]);
1724 });
1725
1726 // Moving to the beginning of the line should put us at the beginning of the line.
1727 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1728 assert_eq!(
1729 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1730 editor.selections.display_ranges(cx)
1731 );
1732
1733 // Moving to the end of the line should put us at the end of the line.
1734 editor.move_to_end_of_line(&move_to_end, window, cx);
1735 assert_eq!(
1736 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1737 editor.selections.display_ranges(cx)
1738 );
1739
1740 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1741 // Start the cursor at the last line (`y` that was wrapped to a new line)
1742 editor.change_selections(None, window, cx, |s| {
1743 s.select_display_ranges([
1744 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1745 ]);
1746 });
1747
1748 // Moving to the beginning of the line should put us at the start of the second line of
1749 // display text, i.e., the `j`.
1750 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1751 assert_eq!(
1752 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1753 editor.selections.display_ranges(cx)
1754 );
1755
1756 // Moving to the beginning of the line again should be a no-op.
1757 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1758 assert_eq!(
1759 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1760 editor.selections.display_ranges(cx)
1761 );
1762
1763 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1764 // next display line.
1765 editor.move_to_end_of_line(&move_to_end, window, cx);
1766 assert_eq!(
1767 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1768 editor.selections.display_ranges(cx)
1769 );
1770
1771 // Moving to the end of the line again should be a no-op.
1772 editor.move_to_end_of_line(&move_to_end, window, cx);
1773 assert_eq!(
1774 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1775 editor.selections.display_ranges(cx)
1776 );
1777 });
1778}
1779
1780#[gpui::test]
1781fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1782 init_test(cx, |_| {});
1783
1784 let editor = cx.add_window(|window, cx| {
1785 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1786 build_editor(buffer, window, cx)
1787 });
1788 _ = editor.update(cx, |editor, window, cx| {
1789 editor.change_selections(None, window, cx, |s| {
1790 s.select_display_ranges([
1791 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1792 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1793 ])
1794 });
1795
1796 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1797 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1798
1799 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1800 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1801
1802 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1803 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1804
1805 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1806 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1807
1808 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1809 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1810
1811 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1812 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1813
1814 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1815 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1816
1817 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1818 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1819
1820 editor.move_right(&MoveRight, window, cx);
1821 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1822 assert_selection_ranges(
1823 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1824 editor,
1825 cx,
1826 );
1827
1828 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1829 assert_selection_ranges(
1830 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1831 editor,
1832 cx,
1833 );
1834
1835 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1836 assert_selection_ranges(
1837 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1838 editor,
1839 cx,
1840 );
1841 });
1842}
1843
1844#[gpui::test]
1845fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1846 init_test(cx, |_| {});
1847
1848 let editor = cx.add_window(|window, cx| {
1849 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1850 build_editor(buffer, window, cx)
1851 });
1852
1853 _ = editor.update(cx, |editor, window, cx| {
1854 editor.set_wrap_width(Some(140.0.into()), cx);
1855 assert_eq!(
1856 editor.display_text(cx),
1857 "use one::{\n two::three::\n four::five\n};"
1858 );
1859
1860 editor.change_selections(None, window, cx, |s| {
1861 s.select_display_ranges([
1862 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1863 ]);
1864 });
1865
1866 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1867 assert_eq!(
1868 editor.selections.display_ranges(cx),
1869 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1870 );
1871
1872 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1873 assert_eq!(
1874 editor.selections.display_ranges(cx),
1875 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1876 );
1877
1878 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1879 assert_eq!(
1880 editor.selections.display_ranges(cx),
1881 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1882 );
1883
1884 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1885 assert_eq!(
1886 editor.selections.display_ranges(cx),
1887 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1888 );
1889
1890 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1891 assert_eq!(
1892 editor.selections.display_ranges(cx),
1893 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1894 );
1895
1896 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1897 assert_eq!(
1898 editor.selections.display_ranges(cx),
1899 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1900 );
1901 });
1902}
1903
1904#[gpui::test]
1905async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
1906 init_test(cx, |_| {});
1907 let mut cx = EditorTestContext::new(cx).await;
1908
1909 let line_height = cx.editor(|editor, window, _| {
1910 editor
1911 .style()
1912 .unwrap()
1913 .text
1914 .line_height_in_pixels(window.rem_size())
1915 });
1916 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1917
1918 cx.set_state(
1919 &r#"ˇone
1920 two
1921
1922 three
1923 fourˇ
1924 five
1925
1926 six"#
1927 .unindent(),
1928 );
1929
1930 cx.update_editor(|editor, window, cx| {
1931 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1932 });
1933 cx.assert_editor_state(
1934 &r#"one
1935 two
1936 ˇ
1937 three
1938 four
1939 five
1940 ˇ
1941 six"#
1942 .unindent(),
1943 );
1944
1945 cx.update_editor(|editor, window, cx| {
1946 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1947 });
1948 cx.assert_editor_state(
1949 &r#"one
1950 two
1951
1952 three
1953 four
1954 five
1955 ˇ
1956 sixˇ"#
1957 .unindent(),
1958 );
1959
1960 cx.update_editor(|editor, window, cx| {
1961 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1962 });
1963 cx.assert_editor_state(
1964 &r#"one
1965 two
1966
1967 three
1968 four
1969 five
1970
1971 sixˇ"#
1972 .unindent(),
1973 );
1974
1975 cx.update_editor(|editor, window, cx| {
1976 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1977 });
1978 cx.assert_editor_state(
1979 &r#"one
1980 two
1981
1982 three
1983 four
1984 five
1985 ˇ
1986 six"#
1987 .unindent(),
1988 );
1989
1990 cx.update_editor(|editor, window, cx| {
1991 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1992 });
1993 cx.assert_editor_state(
1994 &r#"one
1995 two
1996 ˇ
1997 three
1998 four
1999 five
2000
2001 six"#
2002 .unindent(),
2003 );
2004
2005 cx.update_editor(|editor, window, cx| {
2006 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2007 });
2008 cx.assert_editor_state(
2009 &r#"ˇone
2010 two
2011
2012 three
2013 four
2014 five
2015
2016 six"#
2017 .unindent(),
2018 );
2019}
2020
2021#[gpui::test]
2022async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2023 init_test(cx, |_| {});
2024 let mut cx = EditorTestContext::new(cx).await;
2025 let line_height = cx.editor(|editor, window, _| {
2026 editor
2027 .style()
2028 .unwrap()
2029 .text
2030 .line_height_in_pixels(window.rem_size())
2031 });
2032 let window = cx.window;
2033 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2034
2035 cx.set_state(
2036 r#"ˇone
2037 two
2038 three
2039 four
2040 five
2041 six
2042 seven
2043 eight
2044 nine
2045 ten
2046 "#,
2047 );
2048
2049 cx.update_editor(|editor, window, cx| {
2050 assert_eq!(
2051 editor.snapshot(window, cx).scroll_position(),
2052 gpui::Point::new(0., 0.)
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., 3.)
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., 6.)
2063 );
2064 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2065 assert_eq!(
2066 editor.snapshot(window, cx).scroll_position(),
2067 gpui::Point::new(0., 3.)
2068 );
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., 1.)
2074 );
2075 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2076 assert_eq!(
2077 editor.snapshot(window, cx).scroll_position(),
2078 gpui::Point::new(0., 3.)
2079 );
2080 });
2081}
2082
2083#[gpui::test]
2084async fn test_autoscroll(cx: &mut TestAppContext) {
2085 init_test(cx, |_| {});
2086 let mut cx = EditorTestContext::new(cx).await;
2087
2088 let line_height = cx.update_editor(|editor, window, cx| {
2089 editor.set_vertical_scroll_margin(2, cx);
2090 editor
2091 .style()
2092 .unwrap()
2093 .text
2094 .line_height_in_pixels(window.rem_size())
2095 });
2096 let window = cx.window;
2097 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2098
2099 cx.set_state(
2100 r#"ˇone
2101 two
2102 three
2103 four
2104 five
2105 six
2106 seven
2107 eight
2108 nine
2109 ten
2110 "#,
2111 );
2112 cx.update_editor(|editor, window, cx| {
2113 assert_eq!(
2114 editor.snapshot(window, cx).scroll_position(),
2115 gpui::Point::new(0., 0.0)
2116 );
2117 });
2118
2119 // Add a cursor below the visible area. Since both cursors cannot fit
2120 // on screen, the editor autoscrolls to reveal the newest cursor, and
2121 // allows the vertical scroll margin below that cursor.
2122 cx.update_editor(|editor, window, cx| {
2123 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2124 selections.select_ranges([
2125 Point::new(0, 0)..Point::new(0, 0),
2126 Point::new(6, 0)..Point::new(6, 0),
2127 ]);
2128 })
2129 });
2130 cx.update_editor(|editor, window, cx| {
2131 assert_eq!(
2132 editor.snapshot(window, cx).scroll_position(),
2133 gpui::Point::new(0., 3.0)
2134 );
2135 });
2136
2137 // Move down. The editor cursor scrolls down to track the newest cursor.
2138 cx.update_editor(|editor, window, cx| {
2139 editor.move_down(&Default::default(), window, cx);
2140 });
2141 cx.update_editor(|editor, window, cx| {
2142 assert_eq!(
2143 editor.snapshot(window, cx).scroll_position(),
2144 gpui::Point::new(0., 4.0)
2145 );
2146 });
2147
2148 // Add a cursor above the visible area. Since both cursors fit on screen,
2149 // the editor scrolls to show both.
2150 cx.update_editor(|editor, window, cx| {
2151 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2152 selections.select_ranges([
2153 Point::new(1, 0)..Point::new(1, 0),
2154 Point::new(6, 0)..Point::new(6, 0),
2155 ]);
2156 })
2157 });
2158 cx.update_editor(|editor, window, cx| {
2159 assert_eq!(
2160 editor.snapshot(window, cx).scroll_position(),
2161 gpui::Point::new(0., 1.0)
2162 );
2163 });
2164}
2165
2166#[gpui::test]
2167async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2168 init_test(cx, |_| {});
2169 let mut cx = EditorTestContext::new(cx).await;
2170
2171 let line_height = cx.editor(|editor, window, _cx| {
2172 editor
2173 .style()
2174 .unwrap()
2175 .text
2176 .line_height_in_pixels(window.rem_size())
2177 });
2178 let window = cx.window;
2179 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2180 cx.set_state(
2181 &r#"
2182 ˇone
2183 two
2184 threeˇ
2185 four
2186 five
2187 six
2188 seven
2189 eight
2190 nine
2191 ten
2192 "#
2193 .unindent(),
2194 );
2195
2196 cx.update_editor(|editor, window, cx| {
2197 editor.move_page_down(&MovePageDown::default(), window, cx)
2198 });
2199 cx.assert_editor_state(
2200 &r#"
2201 one
2202 two
2203 three
2204 ˇfour
2205 five
2206 sixˇ
2207 seven
2208 eight
2209 nine
2210 ten
2211 "#
2212 .unindent(),
2213 );
2214
2215 cx.update_editor(|editor, window, cx| {
2216 editor.move_page_down(&MovePageDown::default(), window, cx)
2217 });
2218 cx.assert_editor_state(
2219 &r#"
2220 one
2221 two
2222 three
2223 four
2224 five
2225 six
2226 ˇseven
2227 eight
2228 nineˇ
2229 ten
2230 "#
2231 .unindent(),
2232 );
2233
2234 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2235 cx.assert_editor_state(
2236 &r#"
2237 one
2238 two
2239 three
2240 ˇfour
2241 five
2242 sixˇ
2243 seven
2244 eight
2245 nine
2246 ten
2247 "#
2248 .unindent(),
2249 );
2250
2251 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2252 cx.assert_editor_state(
2253 &r#"
2254 ˇone
2255 two
2256 threeˇ
2257 four
2258 five
2259 six
2260 seven
2261 eight
2262 nine
2263 ten
2264 "#
2265 .unindent(),
2266 );
2267
2268 // Test select collapsing
2269 cx.update_editor(|editor, window, cx| {
2270 editor.move_page_down(&MovePageDown::default(), window, cx);
2271 editor.move_page_down(&MovePageDown::default(), window, cx);
2272 editor.move_page_down(&MovePageDown::default(), window, cx);
2273 });
2274 cx.assert_editor_state(
2275 &r#"
2276 one
2277 two
2278 three
2279 four
2280 five
2281 six
2282 seven
2283 eight
2284 nine
2285 ˇten
2286 ˇ"#
2287 .unindent(),
2288 );
2289}
2290
2291#[gpui::test]
2292async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2293 init_test(cx, |_| {});
2294 let mut cx = EditorTestContext::new(cx).await;
2295 cx.set_state("one «two threeˇ» four");
2296 cx.update_editor(|editor, window, cx| {
2297 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
2298 assert_eq!(editor.text(cx), " four");
2299 });
2300}
2301
2302#[gpui::test]
2303fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2304 init_test(cx, |_| {});
2305
2306 let editor = cx.add_window(|window, cx| {
2307 let buffer = MultiBuffer::build_simple("one two three four", cx);
2308 build_editor(buffer.clone(), window, cx)
2309 });
2310
2311 _ = editor.update(cx, |editor, window, cx| {
2312 editor.change_selections(None, window, cx, |s| {
2313 s.select_display_ranges([
2314 // an empty selection - the preceding word fragment is deleted
2315 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2316 // characters selected - they are deleted
2317 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2318 ])
2319 });
2320 editor.delete_to_previous_word_start(
2321 &DeleteToPreviousWordStart {
2322 ignore_newlines: false,
2323 },
2324 window,
2325 cx,
2326 );
2327 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2328 });
2329
2330 _ = editor.update(cx, |editor, window, cx| {
2331 editor.change_selections(None, window, cx, |s| {
2332 s.select_display_ranges([
2333 // an empty selection - the following word fragment is deleted
2334 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2335 // characters selected - they are deleted
2336 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2337 ])
2338 });
2339 editor.delete_to_next_word_end(
2340 &DeleteToNextWordEnd {
2341 ignore_newlines: false,
2342 },
2343 window,
2344 cx,
2345 );
2346 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2347 });
2348}
2349
2350#[gpui::test]
2351fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2352 init_test(cx, |_| {});
2353
2354 let editor = cx.add_window(|window, cx| {
2355 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2356 build_editor(buffer.clone(), window, cx)
2357 });
2358 let del_to_prev_word_start = DeleteToPreviousWordStart {
2359 ignore_newlines: false,
2360 };
2361 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2362 ignore_newlines: true,
2363 };
2364
2365 _ = editor.update(cx, |editor, window, cx| {
2366 editor.change_selections(None, window, cx, |s| {
2367 s.select_display_ranges([
2368 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2369 ])
2370 });
2371 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2372 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2373 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2374 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2375 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2376 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2377 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2378 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2379 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2380 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2381 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2382 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2383 });
2384}
2385
2386#[gpui::test]
2387fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2388 init_test(cx, |_| {});
2389
2390 let editor = cx.add_window(|window, cx| {
2391 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2392 build_editor(buffer.clone(), window, cx)
2393 });
2394 let del_to_next_word_end = DeleteToNextWordEnd {
2395 ignore_newlines: false,
2396 };
2397 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2398 ignore_newlines: true,
2399 };
2400
2401 _ = editor.update(cx, |editor, window, cx| {
2402 editor.change_selections(None, window, cx, |s| {
2403 s.select_display_ranges([
2404 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2405 ])
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 "one\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 "\n two\nthree\n four"
2416 );
2417 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2418 assert_eq!(
2419 editor.buffer.read(cx).read(cx).text(),
2420 "two\nthree\n four"
2421 );
2422 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2423 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2424 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2425 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2426 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2427 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2428 });
2429}
2430
2431#[gpui::test]
2432fn test_newline(cx: &mut TestAppContext) {
2433 init_test(cx, |_| {});
2434
2435 let editor = cx.add_window(|window, cx| {
2436 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2437 build_editor(buffer.clone(), window, cx)
2438 });
2439
2440 _ = editor.update(cx, |editor, window, cx| {
2441 editor.change_selections(None, window, cx, |s| {
2442 s.select_display_ranges([
2443 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2444 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2445 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2446 ])
2447 });
2448
2449 editor.newline(&Newline, window, cx);
2450 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2451 });
2452}
2453
2454#[gpui::test]
2455fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2456 init_test(cx, |_| {});
2457
2458 let editor = cx.add_window(|window, cx| {
2459 let buffer = MultiBuffer::build_simple(
2460 "
2461 a
2462 b(
2463 X
2464 )
2465 c(
2466 X
2467 )
2468 "
2469 .unindent()
2470 .as_str(),
2471 cx,
2472 );
2473 let mut editor = build_editor(buffer.clone(), window, cx);
2474 editor.change_selections(None, window, cx, |s| {
2475 s.select_ranges([
2476 Point::new(2, 4)..Point::new(2, 5),
2477 Point::new(5, 4)..Point::new(5, 5),
2478 ])
2479 });
2480 editor
2481 });
2482
2483 _ = editor.update(cx, |editor, window, cx| {
2484 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2485 editor.buffer.update(cx, |buffer, cx| {
2486 buffer.edit(
2487 [
2488 (Point::new(1, 2)..Point::new(3, 0), ""),
2489 (Point::new(4, 2)..Point::new(6, 0), ""),
2490 ],
2491 None,
2492 cx,
2493 );
2494 assert_eq!(
2495 buffer.read(cx).text(),
2496 "
2497 a
2498 b()
2499 c()
2500 "
2501 .unindent()
2502 );
2503 });
2504 assert_eq!(
2505 editor.selections.ranges(cx),
2506 &[
2507 Point::new(1, 2)..Point::new(1, 2),
2508 Point::new(2, 2)..Point::new(2, 2),
2509 ],
2510 );
2511
2512 editor.newline(&Newline, window, cx);
2513 assert_eq!(
2514 editor.text(cx),
2515 "
2516 a
2517 b(
2518 )
2519 c(
2520 )
2521 "
2522 .unindent()
2523 );
2524
2525 // The selections are moved after the inserted newlines
2526 assert_eq!(
2527 editor.selections.ranges(cx),
2528 &[
2529 Point::new(2, 0)..Point::new(2, 0),
2530 Point::new(4, 0)..Point::new(4, 0),
2531 ],
2532 );
2533 });
2534}
2535
2536#[gpui::test]
2537async fn test_newline_above(cx: &mut TestAppContext) {
2538 init_test(cx, |settings| {
2539 settings.defaults.tab_size = NonZeroU32::new(4)
2540 });
2541
2542 let language = Arc::new(
2543 Language::new(
2544 LanguageConfig::default(),
2545 Some(tree_sitter_rust::LANGUAGE.into()),
2546 )
2547 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2548 .unwrap(),
2549 );
2550
2551 let mut cx = EditorTestContext::new(cx).await;
2552 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2553 cx.set_state(indoc! {"
2554 const a: ˇA = (
2555 (ˇ
2556 «const_functionˇ»(ˇ),
2557 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2558 )ˇ
2559 ˇ);ˇ
2560 "});
2561
2562 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2563 cx.assert_editor_state(indoc! {"
2564 ˇ
2565 const a: A = (
2566 ˇ
2567 (
2568 ˇ
2569 ˇ
2570 const_function(),
2571 ˇ
2572 ˇ
2573 ˇ
2574 ˇ
2575 something_else,
2576 ˇ
2577 )
2578 ˇ
2579 ˇ
2580 );
2581 "});
2582}
2583
2584#[gpui::test]
2585async fn test_newline_below(cx: &mut TestAppContext) {
2586 init_test(cx, |settings| {
2587 settings.defaults.tab_size = NonZeroU32::new(4)
2588 });
2589
2590 let language = Arc::new(
2591 Language::new(
2592 LanguageConfig::default(),
2593 Some(tree_sitter_rust::LANGUAGE.into()),
2594 )
2595 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2596 .unwrap(),
2597 );
2598
2599 let mut cx = EditorTestContext::new(cx).await;
2600 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2601 cx.set_state(indoc! {"
2602 const a: ˇA = (
2603 (ˇ
2604 «const_functionˇ»(ˇ),
2605 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2606 )ˇ
2607 ˇ);ˇ
2608 "});
2609
2610 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2611 cx.assert_editor_state(indoc! {"
2612 const a: A = (
2613 ˇ
2614 (
2615 ˇ
2616 const_function(),
2617 ˇ
2618 ˇ
2619 something_else,
2620 ˇ
2621 ˇ
2622 ˇ
2623 ˇ
2624 )
2625 ˇ
2626 );
2627 ˇ
2628 ˇ
2629 "});
2630}
2631
2632#[gpui::test]
2633async fn test_newline_comments(cx: &mut TestAppContext) {
2634 init_test(cx, |settings| {
2635 settings.defaults.tab_size = NonZeroU32::new(4)
2636 });
2637
2638 let language = Arc::new(Language::new(
2639 LanguageConfig {
2640 line_comments: vec!["//".into()],
2641 ..LanguageConfig::default()
2642 },
2643 None,
2644 ));
2645 {
2646 let mut cx = EditorTestContext::new(cx).await;
2647 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2648 cx.set_state(indoc! {"
2649 // Fooˇ
2650 "});
2651
2652 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2653 cx.assert_editor_state(indoc! {"
2654 // Foo
2655 //ˇ
2656 "});
2657 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2658 cx.set_state(indoc! {"
2659 ˇ// Foo
2660 "});
2661 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2662 cx.assert_editor_state(indoc! {"
2663
2664 ˇ// Foo
2665 "});
2666 }
2667 // Ensure that comment continuations can be disabled.
2668 update_test_language_settings(cx, |settings| {
2669 settings.defaults.extend_comment_on_newline = Some(false);
2670 });
2671 let mut cx = EditorTestContext::new(cx).await;
2672 cx.set_state(indoc! {"
2673 // Fooˇ
2674 "});
2675 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2676 cx.assert_editor_state(indoc! {"
2677 // Foo
2678 ˇ
2679 "});
2680}
2681
2682#[gpui::test]
2683fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2684 init_test(cx, |_| {});
2685
2686 let editor = cx.add_window(|window, cx| {
2687 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2688 let mut editor = build_editor(buffer.clone(), window, cx);
2689 editor.change_selections(None, window, cx, |s| {
2690 s.select_ranges([3..4, 11..12, 19..20])
2691 });
2692 editor
2693 });
2694
2695 _ = editor.update(cx, |editor, window, cx| {
2696 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2697 editor.buffer.update(cx, |buffer, cx| {
2698 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2699 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2700 });
2701 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2702
2703 editor.insert("Z", window, cx);
2704 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2705
2706 // The selections are moved after the inserted characters
2707 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2708 });
2709}
2710
2711#[gpui::test]
2712async fn test_tab(cx: &mut TestAppContext) {
2713 init_test(cx, |settings| {
2714 settings.defaults.tab_size = NonZeroU32::new(3)
2715 });
2716
2717 let mut cx = EditorTestContext::new(cx).await;
2718 cx.set_state(indoc! {"
2719 ˇabˇc
2720 ˇ🏀ˇ🏀ˇefg
2721 dˇ
2722 "});
2723 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2724 cx.assert_editor_state(indoc! {"
2725 ˇab ˇc
2726 ˇ🏀 ˇ🏀 ˇefg
2727 d ˇ
2728 "});
2729
2730 cx.set_state(indoc! {"
2731 a
2732 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2733 "});
2734 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2735 cx.assert_editor_state(indoc! {"
2736 a
2737 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2738 "});
2739}
2740
2741#[gpui::test]
2742async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2743 init_test(cx, |_| {});
2744
2745 let mut cx = EditorTestContext::new(cx).await;
2746 let language = Arc::new(
2747 Language::new(
2748 LanguageConfig::default(),
2749 Some(tree_sitter_rust::LANGUAGE.into()),
2750 )
2751 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2752 .unwrap(),
2753 );
2754 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2755
2756 // cursors that are already at the suggested indent level insert
2757 // a soft tab. cursors that are to the left of the suggested indent
2758 // auto-indent their line.
2759 cx.set_state(indoc! {"
2760 ˇ
2761 const a: B = (
2762 c(
2763 d(
2764 ˇ
2765 )
2766 ˇ
2767 ˇ )
2768 );
2769 "});
2770 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2771 cx.assert_editor_state(indoc! {"
2772 ˇ
2773 const a: B = (
2774 c(
2775 d(
2776 ˇ
2777 )
2778 ˇ
2779 ˇ)
2780 );
2781 "});
2782
2783 // handle auto-indent when there are multiple cursors on the same line
2784 cx.set_state(indoc! {"
2785 const a: B = (
2786 c(
2787 ˇ ˇ
2788 ˇ )
2789 );
2790 "});
2791 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2792 cx.assert_editor_state(indoc! {"
2793 const a: B = (
2794 c(
2795 ˇ
2796 ˇ)
2797 );
2798 "});
2799}
2800
2801#[gpui::test]
2802async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2803 init_test(cx, |settings| {
2804 settings.defaults.tab_size = NonZeroU32::new(4)
2805 });
2806
2807 let language = Arc::new(
2808 Language::new(
2809 LanguageConfig::default(),
2810 Some(tree_sitter_rust::LANGUAGE.into()),
2811 )
2812 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2813 .unwrap(),
2814 );
2815
2816 let mut cx = EditorTestContext::new(cx).await;
2817 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2818 cx.set_state(indoc! {"
2819 fn a() {
2820 if b {
2821 \t ˇc
2822 }
2823 }
2824 "});
2825
2826 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2827 cx.assert_editor_state(indoc! {"
2828 fn a() {
2829 if b {
2830 ˇc
2831 }
2832 }
2833 "});
2834}
2835
2836#[gpui::test]
2837async fn test_indent_outdent(cx: &mut TestAppContext) {
2838 init_test(cx, |settings| {
2839 settings.defaults.tab_size = NonZeroU32::new(4);
2840 });
2841
2842 let mut cx = EditorTestContext::new(cx).await;
2843
2844 cx.set_state(indoc! {"
2845 «oneˇ» «twoˇ»
2846 three
2847 four
2848 "});
2849 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2850 cx.assert_editor_state(indoc! {"
2851 «oneˇ» «twoˇ»
2852 three
2853 four
2854 "});
2855
2856 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2857 cx.assert_editor_state(indoc! {"
2858 «oneˇ» «twoˇ»
2859 three
2860 four
2861 "});
2862
2863 // select across line ending
2864 cx.set_state(indoc! {"
2865 one two
2866 t«hree
2867 ˇ» four
2868 "});
2869 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2870 cx.assert_editor_state(indoc! {"
2871 one two
2872 t«hree
2873 ˇ» four
2874 "});
2875
2876 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2877 cx.assert_editor_state(indoc! {"
2878 one two
2879 t«hree
2880 ˇ» four
2881 "});
2882
2883 // Ensure that indenting/outdenting works when the cursor is at column 0.
2884 cx.set_state(indoc! {"
2885 one two
2886 ˇthree
2887 four
2888 "});
2889 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2890 cx.assert_editor_state(indoc! {"
2891 one two
2892 ˇthree
2893 four
2894 "});
2895
2896 cx.set_state(indoc! {"
2897 one two
2898 ˇ three
2899 four
2900 "});
2901 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2902 cx.assert_editor_state(indoc! {"
2903 one two
2904 ˇthree
2905 four
2906 "});
2907}
2908
2909#[gpui::test]
2910async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
2911 init_test(cx, |settings| {
2912 settings.defaults.hard_tabs = Some(true);
2913 });
2914
2915 let mut cx = EditorTestContext::new(cx).await;
2916
2917 // select two ranges on one line
2918 cx.set_state(indoc! {"
2919 «oneˇ» «twoˇ»
2920 three
2921 four
2922 "});
2923 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2924 cx.assert_editor_state(indoc! {"
2925 \t«oneˇ» «twoˇ»
2926 three
2927 four
2928 "});
2929 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2930 cx.assert_editor_state(indoc! {"
2931 \t\t«oneˇ» «twoˇ»
2932 three
2933 four
2934 "});
2935 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2936 cx.assert_editor_state(indoc! {"
2937 \t«oneˇ» «twoˇ»
2938 three
2939 four
2940 "});
2941 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2942 cx.assert_editor_state(indoc! {"
2943 «oneˇ» «twoˇ»
2944 three
2945 four
2946 "});
2947
2948 // select across a line ending
2949 cx.set_state(indoc! {"
2950 one two
2951 t«hree
2952 ˇ»four
2953 "});
2954 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2955 cx.assert_editor_state(indoc! {"
2956 one two
2957 \tt«hree
2958 ˇ»four
2959 "});
2960 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2961 cx.assert_editor_state(indoc! {"
2962 one two
2963 \t\tt«hree
2964 ˇ»four
2965 "});
2966 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2967 cx.assert_editor_state(indoc! {"
2968 one two
2969 \tt«hree
2970 ˇ»four
2971 "});
2972 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2973 cx.assert_editor_state(indoc! {"
2974 one two
2975 t«hree
2976 ˇ»four
2977 "});
2978
2979 // Ensure that indenting/outdenting works when the cursor is at column 0.
2980 cx.set_state(indoc! {"
2981 one two
2982 ˇthree
2983 four
2984 "});
2985 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2986 cx.assert_editor_state(indoc! {"
2987 one two
2988 ˇthree
2989 four
2990 "});
2991 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2992 cx.assert_editor_state(indoc! {"
2993 one two
2994 \tˇthree
2995 four
2996 "});
2997 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2998 cx.assert_editor_state(indoc! {"
2999 one two
3000 ˇthree
3001 four
3002 "});
3003}
3004
3005#[gpui::test]
3006fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3007 init_test(cx, |settings| {
3008 settings.languages.extend([
3009 (
3010 "TOML".into(),
3011 LanguageSettingsContent {
3012 tab_size: NonZeroU32::new(2),
3013 ..Default::default()
3014 },
3015 ),
3016 (
3017 "Rust".into(),
3018 LanguageSettingsContent {
3019 tab_size: NonZeroU32::new(4),
3020 ..Default::default()
3021 },
3022 ),
3023 ]);
3024 });
3025
3026 let toml_language = Arc::new(Language::new(
3027 LanguageConfig {
3028 name: "TOML".into(),
3029 ..Default::default()
3030 },
3031 None,
3032 ));
3033 let rust_language = Arc::new(Language::new(
3034 LanguageConfig {
3035 name: "Rust".into(),
3036 ..Default::default()
3037 },
3038 None,
3039 ));
3040
3041 let toml_buffer =
3042 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3043 let rust_buffer =
3044 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3045 let multibuffer = cx.new(|cx| {
3046 let mut multibuffer = MultiBuffer::new(ReadWrite);
3047 multibuffer.push_excerpts(
3048 toml_buffer.clone(),
3049 [ExcerptRange {
3050 context: Point::new(0, 0)..Point::new(2, 0),
3051 primary: None,
3052 }],
3053 cx,
3054 );
3055 multibuffer.push_excerpts(
3056 rust_buffer.clone(),
3057 [ExcerptRange {
3058 context: Point::new(0, 0)..Point::new(1, 0),
3059 primary: None,
3060 }],
3061 cx,
3062 );
3063 multibuffer
3064 });
3065
3066 cx.add_window(|window, cx| {
3067 let mut editor = build_editor(multibuffer, window, cx);
3068
3069 assert_eq!(
3070 editor.text(cx),
3071 indoc! {"
3072 a = 1
3073 b = 2
3074
3075 const c: usize = 3;
3076 "}
3077 );
3078
3079 select_ranges(
3080 &mut editor,
3081 indoc! {"
3082 «aˇ» = 1
3083 b = 2
3084
3085 «const c:ˇ» usize = 3;
3086 "},
3087 window,
3088 cx,
3089 );
3090
3091 editor.tab(&Tab, window, cx);
3092 assert_text_with_selections(
3093 &mut editor,
3094 indoc! {"
3095 «aˇ» = 1
3096 b = 2
3097
3098 «const c:ˇ» usize = 3;
3099 "},
3100 cx,
3101 );
3102 editor.tab_prev(&TabPrev, window, cx);
3103 assert_text_with_selections(
3104 &mut editor,
3105 indoc! {"
3106 «aˇ» = 1
3107 b = 2
3108
3109 «const c:ˇ» usize = 3;
3110 "},
3111 cx,
3112 );
3113
3114 editor
3115 });
3116}
3117
3118#[gpui::test]
3119async fn test_backspace(cx: &mut TestAppContext) {
3120 init_test(cx, |_| {});
3121
3122 let mut cx = EditorTestContext::new(cx).await;
3123
3124 // Basic backspace
3125 cx.set_state(indoc! {"
3126 onˇe two three
3127 fou«rˇ» five six
3128 seven «ˇeight nine
3129 »ten
3130 "});
3131 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3132 cx.assert_editor_state(indoc! {"
3133 oˇe two three
3134 fouˇ five six
3135 seven ˇten
3136 "});
3137
3138 // Test backspace inside and around indents
3139 cx.set_state(indoc! {"
3140 zero
3141 ˇone
3142 ˇtwo
3143 ˇ ˇ ˇ three
3144 ˇ ˇ four
3145 "});
3146 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3147 cx.assert_editor_state(indoc! {"
3148 zero
3149 ˇone
3150 ˇtwo
3151 ˇ threeˇ four
3152 "});
3153
3154 // Test backspace with line_mode set to true
3155 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3156 cx.set_state(indoc! {"
3157 The ˇquick ˇbrown
3158 fox jumps over
3159 the lazy dog
3160 ˇThe qu«ick bˇ»rown"});
3161 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3162 cx.assert_editor_state(indoc! {"
3163 ˇfox jumps over
3164 the lazy dogˇ"});
3165}
3166
3167#[gpui::test]
3168async fn test_delete(cx: &mut TestAppContext) {
3169 init_test(cx, |_| {});
3170
3171 let mut cx = EditorTestContext::new(cx).await;
3172 cx.set_state(indoc! {"
3173 onˇe two three
3174 fou«rˇ» five six
3175 seven «ˇeight nine
3176 »ten
3177 "});
3178 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3179 cx.assert_editor_state(indoc! {"
3180 onˇ two three
3181 fouˇ five six
3182 seven ˇten
3183 "});
3184
3185 // Test backspace with line_mode set to true
3186 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3187 cx.set_state(indoc! {"
3188 The ˇquick ˇbrown
3189 fox «ˇjum»ps over
3190 the lazy dog
3191 ˇThe qu«ick bˇ»rown"});
3192 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3193 cx.assert_editor_state("ˇthe lazy dogˇ");
3194}
3195
3196#[gpui::test]
3197fn test_delete_line(cx: &mut TestAppContext) {
3198 init_test(cx, |_| {});
3199
3200 let editor = cx.add_window(|window, cx| {
3201 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3202 build_editor(buffer, window, cx)
3203 });
3204 _ = editor.update(cx, |editor, window, cx| {
3205 editor.change_selections(None, window, cx, |s| {
3206 s.select_display_ranges([
3207 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3208 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3209 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3210 ])
3211 });
3212 editor.delete_line(&DeleteLine, window, cx);
3213 assert_eq!(editor.display_text(cx), "ghi");
3214 assert_eq!(
3215 editor.selections.display_ranges(cx),
3216 vec![
3217 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3218 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3219 ]
3220 );
3221 });
3222
3223 let editor = cx.add_window(|window, cx| {
3224 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3225 build_editor(buffer, window, cx)
3226 });
3227 _ = editor.update(cx, |editor, window, cx| {
3228 editor.change_selections(None, window, cx, |s| {
3229 s.select_display_ranges([
3230 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3231 ])
3232 });
3233 editor.delete_line(&DeleteLine, window, cx);
3234 assert_eq!(editor.display_text(cx), "ghi\n");
3235 assert_eq!(
3236 editor.selections.display_ranges(cx),
3237 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3238 );
3239 });
3240}
3241
3242#[gpui::test]
3243fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3244 init_test(cx, |_| {});
3245
3246 cx.add_window(|window, cx| {
3247 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3248 let mut editor = build_editor(buffer.clone(), window, cx);
3249 let buffer = buffer.read(cx).as_singleton().unwrap();
3250
3251 assert_eq!(
3252 editor.selections.ranges::<Point>(cx),
3253 &[Point::new(0, 0)..Point::new(0, 0)]
3254 );
3255
3256 // When on single line, replace newline at end by space
3257 editor.join_lines(&JoinLines, window, cx);
3258 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3259 assert_eq!(
3260 editor.selections.ranges::<Point>(cx),
3261 &[Point::new(0, 3)..Point::new(0, 3)]
3262 );
3263
3264 // When multiple lines are selected, remove newlines that are spanned by the selection
3265 editor.change_selections(None, window, cx, |s| {
3266 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3267 });
3268 editor.join_lines(&JoinLines, window, cx);
3269 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3270 assert_eq!(
3271 editor.selections.ranges::<Point>(cx),
3272 &[Point::new(0, 11)..Point::new(0, 11)]
3273 );
3274
3275 // Undo should be transactional
3276 editor.undo(&Undo, window, cx);
3277 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3278 assert_eq!(
3279 editor.selections.ranges::<Point>(cx),
3280 &[Point::new(0, 5)..Point::new(2, 2)]
3281 );
3282
3283 // When joining an empty line don't insert a space
3284 editor.change_selections(None, window, cx, |s| {
3285 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3286 });
3287 editor.join_lines(&JoinLines, window, cx);
3288 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3289 assert_eq!(
3290 editor.selections.ranges::<Point>(cx),
3291 [Point::new(2, 3)..Point::new(2, 3)]
3292 );
3293
3294 // We can remove trailing newlines
3295 editor.join_lines(&JoinLines, window, cx);
3296 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3297 assert_eq!(
3298 editor.selections.ranges::<Point>(cx),
3299 [Point::new(2, 3)..Point::new(2, 3)]
3300 );
3301
3302 // We don't blow up on the last line
3303 editor.join_lines(&JoinLines, window, cx);
3304 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3305 assert_eq!(
3306 editor.selections.ranges::<Point>(cx),
3307 [Point::new(2, 3)..Point::new(2, 3)]
3308 );
3309
3310 // reset to test indentation
3311 editor.buffer.update(cx, |buffer, cx| {
3312 buffer.edit(
3313 [
3314 (Point::new(1, 0)..Point::new(1, 2), " "),
3315 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3316 ],
3317 None,
3318 cx,
3319 )
3320 });
3321
3322 // We remove any leading spaces
3323 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3324 editor.change_selections(None, window, cx, |s| {
3325 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3326 });
3327 editor.join_lines(&JoinLines, window, cx);
3328 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3329
3330 // We don't insert a space for a line containing only spaces
3331 editor.join_lines(&JoinLines, window, cx);
3332 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3333
3334 // We ignore any leading tabs
3335 editor.join_lines(&JoinLines, window, cx);
3336 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3337
3338 editor
3339 });
3340}
3341
3342#[gpui::test]
3343fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3344 init_test(cx, |_| {});
3345
3346 cx.add_window(|window, cx| {
3347 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3348 let mut editor = build_editor(buffer.clone(), window, cx);
3349 let buffer = buffer.read(cx).as_singleton().unwrap();
3350
3351 editor.change_selections(None, window, cx, |s| {
3352 s.select_ranges([
3353 Point::new(0, 2)..Point::new(1, 1),
3354 Point::new(1, 2)..Point::new(1, 2),
3355 Point::new(3, 1)..Point::new(3, 2),
3356 ])
3357 });
3358
3359 editor.join_lines(&JoinLines, window, cx);
3360 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3361
3362 assert_eq!(
3363 editor.selections.ranges::<Point>(cx),
3364 [
3365 Point::new(0, 7)..Point::new(0, 7),
3366 Point::new(1, 3)..Point::new(1, 3)
3367 ]
3368 );
3369 editor
3370 });
3371}
3372
3373#[gpui::test]
3374async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3375 init_test(cx, |_| {});
3376
3377 let mut cx = EditorTestContext::new(cx).await;
3378
3379 let diff_base = r#"
3380 Line 0
3381 Line 1
3382 Line 2
3383 Line 3
3384 "#
3385 .unindent();
3386
3387 cx.set_state(
3388 &r#"
3389 ˇLine 0
3390 Line 1
3391 Line 2
3392 Line 3
3393 "#
3394 .unindent(),
3395 );
3396
3397 cx.set_head_text(&diff_base);
3398 executor.run_until_parked();
3399
3400 // Join lines
3401 cx.update_editor(|editor, window, cx| {
3402 editor.join_lines(&JoinLines, window, cx);
3403 });
3404 executor.run_until_parked();
3405
3406 cx.assert_editor_state(
3407 &r#"
3408 Line 0ˇ Line 1
3409 Line 2
3410 Line 3
3411 "#
3412 .unindent(),
3413 );
3414 // Join again
3415 cx.update_editor(|editor, window, cx| {
3416 editor.join_lines(&JoinLines, window, cx);
3417 });
3418 executor.run_until_parked();
3419
3420 cx.assert_editor_state(
3421 &r#"
3422 Line 0 Line 1ˇ Line 2
3423 Line 3
3424 "#
3425 .unindent(),
3426 );
3427}
3428
3429#[gpui::test]
3430async fn test_custom_newlines_cause_no_false_positive_diffs(
3431 executor: BackgroundExecutor,
3432 cx: &mut TestAppContext,
3433) {
3434 init_test(cx, |_| {});
3435 let mut cx = EditorTestContext::new(cx).await;
3436 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3437 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3438 executor.run_until_parked();
3439
3440 cx.update_editor(|editor, window, cx| {
3441 let snapshot = editor.snapshot(window, cx);
3442 assert_eq!(
3443 snapshot
3444 .buffer_snapshot
3445 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3446 .collect::<Vec<_>>(),
3447 Vec::new(),
3448 "Should not have any diffs for files with custom newlines"
3449 );
3450 });
3451}
3452
3453#[gpui::test]
3454async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3455 init_test(cx, |_| {});
3456
3457 let mut cx = EditorTestContext::new(cx).await;
3458
3459 // Test sort_lines_case_insensitive()
3460 cx.set_state(indoc! {"
3461 «z
3462 y
3463 x
3464 Z
3465 Y
3466 Xˇ»
3467 "});
3468 cx.update_editor(|e, window, cx| {
3469 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3470 });
3471 cx.assert_editor_state(indoc! {"
3472 «x
3473 X
3474 y
3475 Y
3476 z
3477 Zˇ»
3478 "});
3479
3480 // Test reverse_lines()
3481 cx.set_state(indoc! {"
3482 «5
3483 4
3484 3
3485 2
3486 1ˇ»
3487 "});
3488 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3489 cx.assert_editor_state(indoc! {"
3490 «1
3491 2
3492 3
3493 4
3494 5ˇ»
3495 "});
3496
3497 // Skip testing shuffle_line()
3498
3499 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3500 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3501
3502 // Don't manipulate when cursor is on single line, but expand the selection
3503 cx.set_state(indoc! {"
3504 ddˇdd
3505 ccc
3506 bb
3507 a
3508 "});
3509 cx.update_editor(|e, window, cx| {
3510 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3511 });
3512 cx.assert_editor_state(indoc! {"
3513 «ddddˇ»
3514 ccc
3515 bb
3516 a
3517 "});
3518
3519 // Basic manipulate case
3520 // Start selection moves to column 0
3521 // End of selection shrinks to fit shorter line
3522 cx.set_state(indoc! {"
3523 dd«d
3524 ccc
3525 bb
3526 aaaaaˇ»
3527 "});
3528 cx.update_editor(|e, window, cx| {
3529 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3530 });
3531 cx.assert_editor_state(indoc! {"
3532 «aaaaa
3533 bb
3534 ccc
3535 dddˇ»
3536 "});
3537
3538 // Manipulate case with newlines
3539 cx.set_state(indoc! {"
3540 dd«d
3541 ccc
3542
3543 bb
3544 aaaaa
3545
3546 ˇ»
3547 "});
3548 cx.update_editor(|e, window, cx| {
3549 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3550 });
3551 cx.assert_editor_state(indoc! {"
3552 «
3553
3554 aaaaa
3555 bb
3556 ccc
3557 dddˇ»
3558
3559 "});
3560
3561 // Adding new line
3562 cx.set_state(indoc! {"
3563 aa«a
3564 bbˇ»b
3565 "});
3566 cx.update_editor(|e, window, cx| {
3567 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3568 });
3569 cx.assert_editor_state(indoc! {"
3570 «aaa
3571 bbb
3572 added_lineˇ»
3573 "});
3574
3575 // Removing line
3576 cx.set_state(indoc! {"
3577 aa«a
3578 bbbˇ»
3579 "});
3580 cx.update_editor(|e, window, cx| {
3581 e.manipulate_lines(window, cx, |lines| {
3582 lines.pop();
3583 })
3584 });
3585 cx.assert_editor_state(indoc! {"
3586 «aaaˇ»
3587 "});
3588
3589 // Removing all lines
3590 cx.set_state(indoc! {"
3591 aa«a
3592 bbbˇ»
3593 "});
3594 cx.update_editor(|e, window, cx| {
3595 e.manipulate_lines(window, cx, |lines| {
3596 lines.drain(..);
3597 })
3598 });
3599 cx.assert_editor_state(indoc! {"
3600 ˇ
3601 "});
3602}
3603
3604#[gpui::test]
3605async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3606 init_test(cx, |_| {});
3607
3608 let mut cx = EditorTestContext::new(cx).await;
3609
3610 // Consider continuous selection as single selection
3611 cx.set_state(indoc! {"
3612 Aaa«aa
3613 cˇ»c«c
3614 bb
3615 aaaˇ»aa
3616 "});
3617 cx.update_editor(|e, window, cx| {
3618 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3619 });
3620 cx.assert_editor_state(indoc! {"
3621 «Aaaaa
3622 ccc
3623 bb
3624 aaaaaˇ»
3625 "});
3626
3627 cx.set_state(indoc! {"
3628 Aaa«aa
3629 cˇ»c«c
3630 bb
3631 aaaˇ»aa
3632 "});
3633 cx.update_editor(|e, window, cx| {
3634 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3635 });
3636 cx.assert_editor_state(indoc! {"
3637 «Aaaaa
3638 ccc
3639 bbˇ»
3640 "});
3641
3642 // Consider non continuous selection as distinct dedup operations
3643 cx.set_state(indoc! {"
3644 «aaaaa
3645 bb
3646 aaaaa
3647 aaaaaˇ»
3648
3649 aaa«aaˇ»
3650 "});
3651 cx.update_editor(|e, window, cx| {
3652 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3653 });
3654 cx.assert_editor_state(indoc! {"
3655 «aaaaa
3656 bbˇ»
3657
3658 «aaaaaˇ»
3659 "});
3660}
3661
3662#[gpui::test]
3663async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3664 init_test(cx, |_| {});
3665
3666 let mut cx = EditorTestContext::new(cx).await;
3667
3668 cx.set_state(indoc! {"
3669 «Aaa
3670 aAa
3671 Aaaˇ»
3672 "});
3673 cx.update_editor(|e, window, cx| {
3674 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3675 });
3676 cx.assert_editor_state(indoc! {"
3677 «Aaa
3678 aAaˇ»
3679 "});
3680
3681 cx.set_state(indoc! {"
3682 «Aaa
3683 aAa
3684 aaAˇ»
3685 "});
3686 cx.update_editor(|e, window, cx| {
3687 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3688 });
3689 cx.assert_editor_state(indoc! {"
3690 «Aaaˇ»
3691 "});
3692}
3693
3694#[gpui::test]
3695async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3696 init_test(cx, |_| {});
3697
3698 let mut cx = EditorTestContext::new(cx).await;
3699
3700 // Manipulate with multiple selections on a single line
3701 cx.set_state(indoc! {"
3702 dd«dd
3703 cˇ»c«c
3704 bb
3705 aaaˇ»aa
3706 "});
3707 cx.update_editor(|e, window, cx| {
3708 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3709 });
3710 cx.assert_editor_state(indoc! {"
3711 «aaaaa
3712 bb
3713 ccc
3714 ddddˇ»
3715 "});
3716
3717 // Manipulate with multiple disjoin selections
3718 cx.set_state(indoc! {"
3719 5«
3720 4
3721 3
3722 2
3723 1ˇ»
3724
3725 dd«dd
3726 ccc
3727 bb
3728 aaaˇ»aa
3729 "});
3730 cx.update_editor(|e, window, cx| {
3731 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3732 });
3733 cx.assert_editor_state(indoc! {"
3734 «1
3735 2
3736 3
3737 4
3738 5ˇ»
3739
3740 «aaaaa
3741 bb
3742 ccc
3743 ddddˇ»
3744 "});
3745
3746 // Adding lines on each selection
3747 cx.set_state(indoc! {"
3748 2«
3749 1ˇ»
3750
3751 bb«bb
3752 aaaˇ»aa
3753 "});
3754 cx.update_editor(|e, window, cx| {
3755 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3756 });
3757 cx.assert_editor_state(indoc! {"
3758 «2
3759 1
3760 added lineˇ»
3761
3762 «bbbb
3763 aaaaa
3764 added lineˇ»
3765 "});
3766
3767 // Removing lines on each selection
3768 cx.set_state(indoc! {"
3769 2«
3770 1ˇ»
3771
3772 bb«bb
3773 aaaˇ»aa
3774 "});
3775 cx.update_editor(|e, window, cx| {
3776 e.manipulate_lines(window, cx, |lines| {
3777 lines.pop();
3778 })
3779 });
3780 cx.assert_editor_state(indoc! {"
3781 «2ˇ»
3782
3783 «bbbbˇ»
3784 "});
3785}
3786
3787#[gpui::test]
3788async fn test_manipulate_text(cx: &mut TestAppContext) {
3789 init_test(cx, |_| {});
3790
3791 let mut cx = EditorTestContext::new(cx).await;
3792
3793 // Test convert_to_upper_case()
3794 cx.set_state(indoc! {"
3795 «hello worldˇ»
3796 "});
3797 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3798 cx.assert_editor_state(indoc! {"
3799 «HELLO WORLDˇ»
3800 "});
3801
3802 // Test convert_to_lower_case()
3803 cx.set_state(indoc! {"
3804 «HELLO WORLDˇ»
3805 "});
3806 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3807 cx.assert_editor_state(indoc! {"
3808 «hello worldˇ»
3809 "});
3810
3811 // Test multiple line, single selection case
3812 cx.set_state(indoc! {"
3813 «The quick brown
3814 fox jumps over
3815 the lazy dogˇ»
3816 "});
3817 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3818 cx.assert_editor_state(indoc! {"
3819 «The Quick Brown
3820 Fox Jumps Over
3821 The Lazy Dogˇ»
3822 "});
3823
3824 // Test multiple line, single selection case
3825 cx.set_state(indoc! {"
3826 «The quick brown
3827 fox jumps over
3828 the lazy dogˇ»
3829 "});
3830 cx.update_editor(|e, window, cx| {
3831 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3832 });
3833 cx.assert_editor_state(indoc! {"
3834 «TheQuickBrown
3835 FoxJumpsOver
3836 TheLazyDogˇ»
3837 "});
3838
3839 // From here on out, test more complex cases of manipulate_text()
3840
3841 // Test no selection case - should affect words cursors are in
3842 // Cursor at beginning, middle, and end of word
3843 cx.set_state(indoc! {"
3844 ˇhello big beauˇtiful worldˇ
3845 "});
3846 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3847 cx.assert_editor_state(indoc! {"
3848 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3849 "});
3850
3851 // Test multiple selections on a single line and across multiple lines
3852 cx.set_state(indoc! {"
3853 «Theˇ» quick «brown
3854 foxˇ» jumps «overˇ»
3855 the «lazyˇ» dog
3856 "});
3857 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3858 cx.assert_editor_state(indoc! {"
3859 «THEˇ» quick «BROWN
3860 FOXˇ» jumps «OVERˇ»
3861 the «LAZYˇ» dog
3862 "});
3863
3864 // Test case where text length grows
3865 cx.set_state(indoc! {"
3866 «tschüߡ»
3867 "});
3868 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3869 cx.assert_editor_state(indoc! {"
3870 «TSCHÜSSˇ»
3871 "});
3872
3873 // Test to make sure we don't crash when text shrinks
3874 cx.set_state(indoc! {"
3875 aaa_bbbˇ
3876 "});
3877 cx.update_editor(|e, window, cx| {
3878 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3879 });
3880 cx.assert_editor_state(indoc! {"
3881 «aaaBbbˇ»
3882 "});
3883
3884 // Test to make sure we all aware of the fact that each word can grow and shrink
3885 // Final selections should be aware of this fact
3886 cx.set_state(indoc! {"
3887 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3888 "});
3889 cx.update_editor(|e, window, cx| {
3890 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3891 });
3892 cx.assert_editor_state(indoc! {"
3893 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3894 "});
3895
3896 cx.set_state(indoc! {"
3897 «hElLo, WoRld!ˇ»
3898 "});
3899 cx.update_editor(|e, window, cx| {
3900 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3901 });
3902 cx.assert_editor_state(indoc! {"
3903 «HeLlO, wOrLD!ˇ»
3904 "});
3905}
3906
3907#[gpui::test]
3908fn test_duplicate_line(cx: &mut TestAppContext) {
3909 init_test(cx, |_| {});
3910
3911 let editor = cx.add_window(|window, cx| {
3912 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3913 build_editor(buffer, window, cx)
3914 });
3915 _ = editor.update(cx, |editor, window, cx| {
3916 editor.change_selections(None, window, cx, |s| {
3917 s.select_display_ranges([
3918 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3919 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3920 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3921 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3922 ])
3923 });
3924 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3925 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3926 assert_eq!(
3927 editor.selections.display_ranges(cx),
3928 vec![
3929 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3930 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3931 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3932 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3933 ]
3934 );
3935 });
3936
3937 let editor = cx.add_window(|window, cx| {
3938 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3939 build_editor(buffer, window, cx)
3940 });
3941 _ = editor.update(cx, |editor, window, cx| {
3942 editor.change_selections(None, window, cx, |s| {
3943 s.select_display_ranges([
3944 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3945 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3946 ])
3947 });
3948 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3949 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3950 assert_eq!(
3951 editor.selections.display_ranges(cx),
3952 vec![
3953 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3954 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3955 ]
3956 );
3957 });
3958
3959 // With `move_upwards` the selections stay in place, except for
3960 // the lines inserted above them
3961 let editor = cx.add_window(|window, cx| {
3962 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3963 build_editor(buffer, window, cx)
3964 });
3965 _ = editor.update(cx, |editor, window, cx| {
3966 editor.change_selections(None, window, cx, |s| {
3967 s.select_display_ranges([
3968 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3969 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3970 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3971 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3972 ])
3973 });
3974 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3975 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3976 assert_eq!(
3977 editor.selections.display_ranges(cx),
3978 vec![
3979 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3980 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3981 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3982 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3983 ]
3984 );
3985 });
3986
3987 let editor = cx.add_window(|window, cx| {
3988 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3989 build_editor(buffer, window, cx)
3990 });
3991 _ = editor.update(cx, |editor, window, cx| {
3992 editor.change_selections(None, window, cx, |s| {
3993 s.select_display_ranges([
3994 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3995 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3996 ])
3997 });
3998 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3999 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4000 assert_eq!(
4001 editor.selections.display_ranges(cx),
4002 vec![
4003 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4004 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4005 ]
4006 );
4007 });
4008
4009 let editor = cx.add_window(|window, cx| {
4010 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4011 build_editor(buffer, window, cx)
4012 });
4013 _ = editor.update(cx, |editor, window, cx| {
4014 editor.change_selections(None, window, cx, |s| {
4015 s.select_display_ranges([
4016 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4017 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4018 ])
4019 });
4020 editor.duplicate_selection(&DuplicateSelection, window, cx);
4021 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4022 assert_eq!(
4023 editor.selections.display_ranges(cx),
4024 vec![
4025 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4026 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4027 ]
4028 );
4029 });
4030}
4031
4032#[gpui::test]
4033fn test_move_line_up_down(cx: &mut TestAppContext) {
4034 init_test(cx, |_| {});
4035
4036 let editor = cx.add_window(|window, cx| {
4037 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4038 build_editor(buffer, window, cx)
4039 });
4040 _ = editor.update(cx, |editor, window, cx| {
4041 editor.fold_creases(
4042 vec![
4043 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4044 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4045 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4046 ],
4047 true,
4048 window,
4049 cx,
4050 );
4051 editor.change_selections(None, window, cx, |s| {
4052 s.select_display_ranges([
4053 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4054 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4055 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4056 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4057 ])
4058 });
4059 assert_eq!(
4060 editor.display_text(cx),
4061 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4062 );
4063
4064 editor.move_line_up(&MoveLineUp, window, cx);
4065 assert_eq!(
4066 editor.display_text(cx),
4067 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4068 );
4069 assert_eq!(
4070 editor.selections.display_ranges(cx),
4071 vec![
4072 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4073 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4074 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4075 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4076 ]
4077 );
4078 });
4079
4080 _ = editor.update(cx, |editor, window, cx| {
4081 editor.move_line_down(&MoveLineDown, window, cx);
4082 assert_eq!(
4083 editor.display_text(cx),
4084 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4085 );
4086 assert_eq!(
4087 editor.selections.display_ranges(cx),
4088 vec![
4089 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4090 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4091 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4092 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4093 ]
4094 );
4095 });
4096
4097 _ = editor.update(cx, |editor, window, cx| {
4098 editor.move_line_down(&MoveLineDown, window, cx);
4099 assert_eq!(
4100 editor.display_text(cx),
4101 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4102 );
4103 assert_eq!(
4104 editor.selections.display_ranges(cx),
4105 vec![
4106 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4107 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4108 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4109 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4110 ]
4111 );
4112 });
4113
4114 _ = editor.update(cx, |editor, window, cx| {
4115 editor.move_line_up(&MoveLineUp, window, cx);
4116 assert_eq!(
4117 editor.display_text(cx),
4118 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4119 );
4120 assert_eq!(
4121 editor.selections.display_ranges(cx),
4122 vec![
4123 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4124 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4125 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4126 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4127 ]
4128 );
4129 });
4130}
4131
4132#[gpui::test]
4133fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4134 init_test(cx, |_| {});
4135
4136 let editor = cx.add_window(|window, cx| {
4137 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4138 build_editor(buffer, window, cx)
4139 });
4140 _ = editor.update(cx, |editor, window, cx| {
4141 let snapshot = editor.buffer.read(cx).snapshot(cx);
4142 editor.insert_blocks(
4143 [BlockProperties {
4144 style: BlockStyle::Fixed,
4145 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4146 height: 1,
4147 render: Arc::new(|_| div().into_any()),
4148 priority: 0,
4149 }],
4150 Some(Autoscroll::fit()),
4151 cx,
4152 );
4153 editor.change_selections(None, window, cx, |s| {
4154 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4155 });
4156 editor.move_line_down(&MoveLineDown, window, cx);
4157 });
4158}
4159
4160#[gpui::test]
4161async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4162 init_test(cx, |_| {});
4163
4164 let mut cx = EditorTestContext::new(cx).await;
4165 cx.set_state(
4166 &"
4167 ˇzero
4168 one
4169 two
4170 three
4171 four
4172 five
4173 "
4174 .unindent(),
4175 );
4176
4177 // Create a four-line block that replaces three lines of text.
4178 cx.update_editor(|editor, window, cx| {
4179 let snapshot = editor.snapshot(window, cx);
4180 let snapshot = &snapshot.buffer_snapshot;
4181 let placement = BlockPlacement::Replace(
4182 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4183 );
4184 editor.insert_blocks(
4185 [BlockProperties {
4186 placement,
4187 height: 4,
4188 style: BlockStyle::Sticky,
4189 render: Arc::new(|_| gpui::div().into_any_element()),
4190 priority: 0,
4191 }],
4192 None,
4193 cx,
4194 );
4195 });
4196
4197 // Move down so that the cursor touches the block.
4198 cx.update_editor(|editor, window, cx| {
4199 editor.move_down(&Default::default(), window, cx);
4200 });
4201 cx.assert_editor_state(
4202 &"
4203 zero
4204 «one
4205 two
4206 threeˇ»
4207 four
4208 five
4209 "
4210 .unindent(),
4211 );
4212
4213 // Move down past the block.
4214 cx.update_editor(|editor, window, cx| {
4215 editor.move_down(&Default::default(), window, cx);
4216 });
4217 cx.assert_editor_state(
4218 &"
4219 zero
4220 one
4221 two
4222 three
4223 ˇfour
4224 five
4225 "
4226 .unindent(),
4227 );
4228}
4229
4230#[gpui::test]
4231fn test_transpose(cx: &mut TestAppContext) {
4232 init_test(cx, |_| {});
4233
4234 _ = cx.add_window(|window, cx| {
4235 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4236 editor.set_style(EditorStyle::default(), window, cx);
4237 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4238 editor.transpose(&Default::default(), window, cx);
4239 assert_eq!(editor.text(cx), "bac");
4240 assert_eq!(editor.selections.ranges(cx), [2..2]);
4241
4242 editor.transpose(&Default::default(), window, cx);
4243 assert_eq!(editor.text(cx), "bca");
4244 assert_eq!(editor.selections.ranges(cx), [3..3]);
4245
4246 editor.transpose(&Default::default(), window, cx);
4247 assert_eq!(editor.text(cx), "bac");
4248 assert_eq!(editor.selections.ranges(cx), [3..3]);
4249
4250 editor
4251 });
4252
4253 _ = cx.add_window(|window, cx| {
4254 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4255 editor.set_style(EditorStyle::default(), window, cx);
4256 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4257 editor.transpose(&Default::default(), window, cx);
4258 assert_eq!(editor.text(cx), "acb\nde");
4259 assert_eq!(editor.selections.ranges(cx), [3..3]);
4260
4261 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4262 editor.transpose(&Default::default(), window, cx);
4263 assert_eq!(editor.text(cx), "acbd\ne");
4264 assert_eq!(editor.selections.ranges(cx), [5..5]);
4265
4266 editor.transpose(&Default::default(), window, cx);
4267 assert_eq!(editor.text(cx), "acbde\n");
4268 assert_eq!(editor.selections.ranges(cx), [6..6]);
4269
4270 editor.transpose(&Default::default(), window, cx);
4271 assert_eq!(editor.text(cx), "acbd\ne");
4272 assert_eq!(editor.selections.ranges(cx), [6..6]);
4273
4274 editor
4275 });
4276
4277 _ = cx.add_window(|window, cx| {
4278 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4279 editor.set_style(EditorStyle::default(), window, cx);
4280 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4281 editor.transpose(&Default::default(), window, cx);
4282 assert_eq!(editor.text(cx), "bacd\ne");
4283 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4284
4285 editor.transpose(&Default::default(), window, cx);
4286 assert_eq!(editor.text(cx), "bcade\n");
4287 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4288
4289 editor.transpose(&Default::default(), window, cx);
4290 assert_eq!(editor.text(cx), "bcda\ne");
4291 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4292
4293 editor.transpose(&Default::default(), window, cx);
4294 assert_eq!(editor.text(cx), "bcade\n");
4295 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4296
4297 editor.transpose(&Default::default(), window, cx);
4298 assert_eq!(editor.text(cx), "bcaed\n");
4299 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4300
4301 editor
4302 });
4303
4304 _ = cx.add_window(|window, cx| {
4305 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4306 editor.set_style(EditorStyle::default(), window, cx);
4307 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4308 editor.transpose(&Default::default(), window, cx);
4309 assert_eq!(editor.text(cx), "🏀🍐✋");
4310 assert_eq!(editor.selections.ranges(cx), [8..8]);
4311
4312 editor.transpose(&Default::default(), window, cx);
4313 assert_eq!(editor.text(cx), "🏀✋🍐");
4314 assert_eq!(editor.selections.ranges(cx), [11..11]);
4315
4316 editor.transpose(&Default::default(), window, cx);
4317 assert_eq!(editor.text(cx), "🏀🍐✋");
4318 assert_eq!(editor.selections.ranges(cx), [11..11]);
4319
4320 editor
4321 });
4322}
4323
4324#[gpui::test]
4325async fn test_rewrap(cx: &mut TestAppContext) {
4326 init_test(cx, |settings| {
4327 settings.languages.extend([
4328 (
4329 "Markdown".into(),
4330 LanguageSettingsContent {
4331 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4332 ..Default::default()
4333 },
4334 ),
4335 (
4336 "Plain Text".into(),
4337 LanguageSettingsContent {
4338 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4339 ..Default::default()
4340 },
4341 ),
4342 ])
4343 });
4344
4345 let mut cx = EditorTestContext::new(cx).await;
4346
4347 let language_with_c_comments = Arc::new(Language::new(
4348 LanguageConfig {
4349 line_comments: vec!["// ".into()],
4350 ..LanguageConfig::default()
4351 },
4352 None,
4353 ));
4354 let language_with_pound_comments = Arc::new(Language::new(
4355 LanguageConfig {
4356 line_comments: vec!["# ".into()],
4357 ..LanguageConfig::default()
4358 },
4359 None,
4360 ));
4361 let markdown_language = Arc::new(Language::new(
4362 LanguageConfig {
4363 name: "Markdown".into(),
4364 ..LanguageConfig::default()
4365 },
4366 None,
4367 ));
4368 let language_with_doc_comments = Arc::new(Language::new(
4369 LanguageConfig {
4370 line_comments: vec!["// ".into(), "/// ".into()],
4371 ..LanguageConfig::default()
4372 },
4373 Some(tree_sitter_rust::LANGUAGE.into()),
4374 ));
4375
4376 let plaintext_language = Arc::new(Language::new(
4377 LanguageConfig {
4378 name: "Plain Text".into(),
4379 ..LanguageConfig::default()
4380 },
4381 None,
4382 ));
4383
4384 assert_rewrap(
4385 indoc! {"
4386 // ˇ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.
4387 "},
4388 indoc! {"
4389 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4390 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4391 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4392 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4393 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4394 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4395 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4396 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4397 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4398 // porttitor id. Aliquam id accumsan eros.
4399 "},
4400 language_with_c_comments.clone(),
4401 &mut cx,
4402 );
4403
4404 // Test that rewrapping works inside of a selection
4405 assert_rewrap(
4406 indoc! {"
4407 «// 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.ˇ»
4408 "},
4409 indoc! {"
4410 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4411 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4412 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4413 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4414 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4415 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4416 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4417 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4418 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4419 // porttitor id. Aliquam id accumsan eros.ˇ»
4420 "},
4421 language_with_c_comments.clone(),
4422 &mut cx,
4423 );
4424
4425 // Test that cursors that expand to the same region are collapsed.
4426 assert_rewrap(
4427 indoc! {"
4428 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4429 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4430 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4431 // ˇ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.
4432 "},
4433 indoc! {"
4434 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4435 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4436 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4437 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4438 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4439 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4440 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4441 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4442 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4443 // porttitor id. Aliquam id accumsan eros.
4444 "},
4445 language_with_c_comments.clone(),
4446 &mut cx,
4447 );
4448
4449 // Test that non-contiguous selections are treated separately.
4450 assert_rewrap(
4451 indoc! {"
4452 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4453 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4454 //
4455 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4456 // ˇ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.
4457 "},
4458 indoc! {"
4459 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4460 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4461 // auctor, eu lacinia sapien scelerisque.
4462 //
4463 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4464 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4465 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4466 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4467 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4468 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4469 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4470 "},
4471 language_with_c_comments.clone(),
4472 &mut cx,
4473 );
4474
4475 // Test that different comment prefixes are supported.
4476 assert_rewrap(
4477 indoc! {"
4478 # ˇ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.
4479 "},
4480 indoc! {"
4481 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4482 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4483 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4484 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4485 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4486 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4487 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4488 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4489 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4490 # accumsan eros.
4491 "},
4492 language_with_pound_comments.clone(),
4493 &mut cx,
4494 );
4495
4496 // Test that rewrapping is ignored outside of comments in most languages.
4497 assert_rewrap(
4498 indoc! {"
4499 /// Adds two numbers.
4500 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4501 fn add(a: u32, b: u32) -> u32 {
4502 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ˇ
4503 }
4504 "},
4505 indoc! {"
4506 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4507 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4508 fn add(a: u32, b: u32) -> u32 {
4509 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ˇ
4510 }
4511 "},
4512 language_with_doc_comments.clone(),
4513 &mut cx,
4514 );
4515
4516 // Test that rewrapping works in Markdown and Plain Text languages.
4517 assert_rewrap(
4518 indoc! {"
4519 # Hello
4520
4521 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.
4522 "},
4523 indoc! {"
4524 # Hello
4525
4526 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4527 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4528 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4529 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4530 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4531 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4532 Integer sit amet scelerisque nisi.
4533 "},
4534 markdown_language,
4535 &mut cx,
4536 );
4537
4538 assert_rewrap(
4539 indoc! {"
4540 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.
4541 "},
4542 indoc! {"
4543 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4544 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4545 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4546 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4547 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4548 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4549 Integer sit amet scelerisque nisi.
4550 "},
4551 plaintext_language,
4552 &mut cx,
4553 );
4554
4555 // Test rewrapping unaligned comments in a selection.
4556 assert_rewrap(
4557 indoc! {"
4558 fn foo() {
4559 if true {
4560 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4561 // Praesent semper egestas tellus id dignissim.ˇ»
4562 do_something();
4563 } else {
4564 //
4565 }
4566 }
4567 "},
4568 indoc! {"
4569 fn foo() {
4570 if true {
4571 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4572 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4573 // egestas tellus id dignissim.ˇ»
4574 do_something();
4575 } else {
4576 //
4577 }
4578 }
4579 "},
4580 language_with_doc_comments.clone(),
4581 &mut cx,
4582 );
4583
4584 assert_rewrap(
4585 indoc! {"
4586 fn foo() {
4587 if true {
4588 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4589 // Praesent semper egestas tellus id dignissim.»
4590 do_something();
4591 } else {
4592 //
4593 }
4594
4595 }
4596 "},
4597 indoc! {"
4598 fn foo() {
4599 if true {
4600 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4601 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4602 // egestas tellus id dignissim.»
4603 do_something();
4604 } else {
4605 //
4606 }
4607
4608 }
4609 "},
4610 language_with_doc_comments.clone(),
4611 &mut cx,
4612 );
4613
4614 #[track_caller]
4615 fn assert_rewrap(
4616 unwrapped_text: &str,
4617 wrapped_text: &str,
4618 language: Arc<Language>,
4619 cx: &mut EditorTestContext,
4620 ) {
4621 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4622 cx.set_state(unwrapped_text);
4623 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4624 cx.assert_editor_state(wrapped_text);
4625 }
4626}
4627
4628#[gpui::test]
4629async fn test_clipboard(cx: &mut TestAppContext) {
4630 init_test(cx, |_| {});
4631
4632 let mut cx = EditorTestContext::new(cx).await;
4633
4634 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4635 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4636 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4637
4638 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4639 cx.set_state("two ˇfour ˇsix ˇ");
4640 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4641 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4642
4643 // Paste again but with only two cursors. Since the number of cursors doesn't
4644 // match the number of slices in the clipboard, the entire clipboard text
4645 // is pasted at each cursor.
4646 cx.set_state("ˇtwo one✅ four three six five ˇ");
4647 cx.update_editor(|e, window, cx| {
4648 e.handle_input("( ", window, cx);
4649 e.paste(&Paste, window, cx);
4650 e.handle_input(") ", window, cx);
4651 });
4652 cx.assert_editor_state(
4653 &([
4654 "( one✅ ",
4655 "three ",
4656 "five ) ˇtwo one✅ four three six five ( one✅ ",
4657 "three ",
4658 "five ) ˇ",
4659 ]
4660 .join("\n")),
4661 );
4662
4663 // Cut with three selections, one of which is full-line.
4664 cx.set_state(indoc! {"
4665 1«2ˇ»3
4666 4ˇ567
4667 «8ˇ»9"});
4668 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4669 cx.assert_editor_state(indoc! {"
4670 1ˇ3
4671 ˇ9"});
4672
4673 // Paste with three selections, noticing how the copied selection that was full-line
4674 // gets inserted before the second cursor.
4675 cx.set_state(indoc! {"
4676 1ˇ3
4677 9ˇ
4678 «oˇ»ne"});
4679 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4680 cx.assert_editor_state(indoc! {"
4681 12ˇ3
4682 4567
4683 9ˇ
4684 8ˇne"});
4685
4686 // Copy with a single cursor only, which writes the whole line into the clipboard.
4687 cx.set_state(indoc! {"
4688 The quick brown
4689 fox juˇmps over
4690 the lazy dog"});
4691 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4692 assert_eq!(
4693 cx.read_from_clipboard()
4694 .and_then(|item| item.text().as_deref().map(str::to_string)),
4695 Some("fox jumps over\n".to_string())
4696 );
4697
4698 // Paste with three selections, noticing how the copied full-line selection is inserted
4699 // before the empty selections but replaces the selection that is non-empty.
4700 cx.set_state(indoc! {"
4701 Tˇhe quick brown
4702 «foˇ»x jumps over
4703 tˇhe lazy dog"});
4704 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4705 cx.assert_editor_state(indoc! {"
4706 fox jumps over
4707 Tˇhe quick brown
4708 fox jumps over
4709 ˇx jumps over
4710 fox jumps over
4711 tˇhe lazy dog"});
4712}
4713
4714#[gpui::test]
4715async fn test_paste_multiline(cx: &mut TestAppContext) {
4716 init_test(cx, |_| {});
4717
4718 let mut cx = EditorTestContext::new(cx).await;
4719 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4720
4721 // Cut an indented block, without the leading whitespace.
4722 cx.set_state(indoc! {"
4723 const a: B = (
4724 c(),
4725 «d(
4726 e,
4727 f
4728 )ˇ»
4729 );
4730 "});
4731 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4732 cx.assert_editor_state(indoc! {"
4733 const a: B = (
4734 c(),
4735 ˇ
4736 );
4737 "});
4738
4739 // Paste it at the same position.
4740 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4741 cx.assert_editor_state(indoc! {"
4742 const a: B = (
4743 c(),
4744 d(
4745 e,
4746 f
4747 )ˇ
4748 );
4749 "});
4750
4751 // Paste it at a line with a lower indent level.
4752 cx.set_state(indoc! {"
4753 ˇ
4754 const a: B = (
4755 c(),
4756 );
4757 "});
4758 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4759 cx.assert_editor_state(indoc! {"
4760 d(
4761 e,
4762 f
4763 )ˇ
4764 const a: B = (
4765 c(),
4766 );
4767 "});
4768
4769 // Cut an indented block, with the leading whitespace.
4770 cx.set_state(indoc! {"
4771 const a: B = (
4772 c(),
4773 « d(
4774 e,
4775 f
4776 )
4777 ˇ»);
4778 "});
4779 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4780 cx.assert_editor_state(indoc! {"
4781 const a: B = (
4782 c(),
4783 ˇ);
4784 "});
4785
4786 // Paste it at the same position.
4787 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4788 cx.assert_editor_state(indoc! {"
4789 const a: B = (
4790 c(),
4791 d(
4792 e,
4793 f
4794 )
4795 ˇ);
4796 "});
4797
4798 // Paste it at a line with a higher indent level.
4799 cx.set_state(indoc! {"
4800 const a: B = (
4801 c(),
4802 d(
4803 e,
4804 fˇ
4805 )
4806 );
4807 "});
4808 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4809 cx.assert_editor_state(indoc! {"
4810 const a: B = (
4811 c(),
4812 d(
4813 e,
4814 f d(
4815 e,
4816 f
4817 )
4818 ˇ
4819 )
4820 );
4821 "});
4822}
4823
4824#[gpui::test]
4825async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4826 init_test(cx, |_| {});
4827
4828 cx.write_to_clipboard(ClipboardItem::new_string(
4829 " d(\n e\n );\n".into(),
4830 ));
4831
4832 let mut cx = EditorTestContext::new(cx).await;
4833 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4834
4835 cx.set_state(indoc! {"
4836 fn a() {
4837 b();
4838 if c() {
4839 ˇ
4840 }
4841 }
4842 "});
4843
4844 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4845 cx.assert_editor_state(indoc! {"
4846 fn a() {
4847 b();
4848 if c() {
4849 d(
4850 e
4851 );
4852 ˇ
4853 }
4854 }
4855 "});
4856
4857 cx.set_state(indoc! {"
4858 fn a() {
4859 b();
4860 ˇ
4861 }
4862 "});
4863
4864 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4865 cx.assert_editor_state(indoc! {"
4866 fn a() {
4867 b();
4868 d(
4869 e
4870 );
4871 ˇ
4872 }
4873 "});
4874}
4875
4876#[gpui::test]
4877fn test_select_all(cx: &mut TestAppContext) {
4878 init_test(cx, |_| {});
4879
4880 let editor = cx.add_window(|window, cx| {
4881 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4882 build_editor(buffer, window, cx)
4883 });
4884 _ = editor.update(cx, |editor, window, cx| {
4885 editor.select_all(&SelectAll, window, cx);
4886 assert_eq!(
4887 editor.selections.display_ranges(cx),
4888 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4889 );
4890 });
4891}
4892
4893#[gpui::test]
4894fn test_select_line(cx: &mut TestAppContext) {
4895 init_test(cx, |_| {});
4896
4897 let editor = cx.add_window(|window, cx| {
4898 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4899 build_editor(buffer, window, cx)
4900 });
4901 _ = editor.update(cx, |editor, window, cx| {
4902 editor.change_selections(None, window, cx, |s| {
4903 s.select_display_ranges([
4904 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4905 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4906 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4907 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4908 ])
4909 });
4910 editor.select_line(&SelectLine, window, cx);
4911 assert_eq!(
4912 editor.selections.display_ranges(cx),
4913 vec![
4914 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4915 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4916 ]
4917 );
4918 });
4919
4920 _ = editor.update(cx, |editor, window, cx| {
4921 editor.select_line(&SelectLine, window, cx);
4922 assert_eq!(
4923 editor.selections.display_ranges(cx),
4924 vec![
4925 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4926 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4927 ]
4928 );
4929 });
4930
4931 _ = editor.update(cx, |editor, window, cx| {
4932 editor.select_line(&SelectLine, window, cx);
4933 assert_eq!(
4934 editor.selections.display_ranges(cx),
4935 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4936 );
4937 });
4938}
4939
4940#[gpui::test]
4941async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4942 init_test(cx, |_| {});
4943 let mut cx = EditorTestContext::new(cx).await;
4944
4945 #[track_caller]
4946 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
4947 cx.set_state(initial_state);
4948 cx.update_editor(|e, window, cx| {
4949 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
4950 });
4951 cx.assert_editor_state(expected_state);
4952 }
4953
4954 // Selection starts and ends at the middle of lines, left-to-right
4955 test(
4956 &mut cx,
4957 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
4958 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4959 );
4960 // Same thing, right-to-left
4961 test(
4962 &mut cx,
4963 "aa\nb«b\ncc\ndd\neˇ»e\nff",
4964 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4965 );
4966
4967 // Whole buffer, left-to-right, last line *doesn't* end with newline
4968 test(
4969 &mut cx,
4970 "«ˇaa\nbb\ncc\ndd\nee\nff»",
4971 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4972 );
4973 // Same thing, right-to-left
4974 test(
4975 &mut cx,
4976 "«aa\nbb\ncc\ndd\nee\nffˇ»",
4977 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4978 );
4979
4980 // Whole buffer, left-to-right, last line ends with newline
4981 test(
4982 &mut cx,
4983 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
4984 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4985 );
4986 // Same thing, right-to-left
4987 test(
4988 &mut cx,
4989 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
4990 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4991 );
4992
4993 // Starts at the end of a line, ends at the start of another
4994 test(
4995 &mut cx,
4996 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
4997 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
4998 );
4999}
5000
5001#[gpui::test]
5002async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5003 init_test(cx, |_| {});
5004
5005 let editor = cx.add_window(|window, cx| {
5006 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5007 build_editor(buffer, window, cx)
5008 });
5009
5010 // setup
5011 _ = editor.update(cx, |editor, window, cx| {
5012 editor.fold_creases(
5013 vec![
5014 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5015 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5016 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5017 ],
5018 true,
5019 window,
5020 cx,
5021 );
5022 assert_eq!(
5023 editor.display_text(cx),
5024 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5025 );
5026 });
5027
5028 _ = editor.update(cx, |editor, window, cx| {
5029 editor.change_selections(None, window, cx, |s| {
5030 s.select_display_ranges([
5031 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5032 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5033 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5034 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5035 ])
5036 });
5037 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5038 assert_eq!(
5039 editor.display_text(cx),
5040 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5041 );
5042 });
5043 EditorTestContext::for_editor(editor, cx)
5044 .await
5045 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5046
5047 _ = editor.update(cx, |editor, window, cx| {
5048 editor.change_selections(None, window, cx, |s| {
5049 s.select_display_ranges([
5050 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5051 ])
5052 });
5053 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5054 assert_eq!(
5055 editor.display_text(cx),
5056 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5057 );
5058 assert_eq!(
5059 editor.selections.display_ranges(cx),
5060 [
5061 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5062 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5063 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5064 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5065 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5066 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5067 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5068 ]
5069 );
5070 });
5071 EditorTestContext::for_editor(editor, cx)
5072 .await
5073 .assert_editor_state(
5074 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5075 );
5076}
5077
5078#[gpui::test]
5079async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5080 init_test(cx, |_| {});
5081
5082 let mut cx = EditorTestContext::new(cx).await;
5083
5084 cx.set_state(indoc!(
5085 r#"abc
5086 defˇghi
5087
5088 jk
5089 nlmo
5090 "#
5091 ));
5092
5093 cx.update_editor(|editor, window, cx| {
5094 editor.add_selection_above(&Default::default(), window, cx);
5095 });
5096
5097 cx.assert_editor_state(indoc!(
5098 r#"abcˇ
5099 defˇghi
5100
5101 jk
5102 nlmo
5103 "#
5104 ));
5105
5106 cx.update_editor(|editor, window, cx| {
5107 editor.add_selection_above(&Default::default(), window, cx);
5108 });
5109
5110 cx.assert_editor_state(indoc!(
5111 r#"abcˇ
5112 defˇghi
5113
5114 jk
5115 nlmo
5116 "#
5117 ));
5118
5119 cx.update_editor(|editor, window, cx| {
5120 editor.add_selection_below(&Default::default(), window, cx);
5121 });
5122
5123 cx.assert_editor_state(indoc!(
5124 r#"abc
5125 defˇghi
5126
5127 jk
5128 nlmo
5129 "#
5130 ));
5131
5132 cx.update_editor(|editor, window, cx| {
5133 editor.undo_selection(&Default::default(), window, cx);
5134 });
5135
5136 cx.assert_editor_state(indoc!(
5137 r#"abcˇ
5138 defˇghi
5139
5140 jk
5141 nlmo
5142 "#
5143 ));
5144
5145 cx.update_editor(|editor, window, cx| {
5146 editor.redo_selection(&Default::default(), window, cx);
5147 });
5148
5149 cx.assert_editor_state(indoc!(
5150 r#"abc
5151 defˇghi
5152
5153 jk
5154 nlmo
5155 "#
5156 ));
5157
5158 cx.update_editor(|editor, window, cx| {
5159 editor.add_selection_below(&Default::default(), window, cx);
5160 });
5161
5162 cx.assert_editor_state(indoc!(
5163 r#"abc
5164 defˇghi
5165
5166 jk
5167 nlmˇo
5168 "#
5169 ));
5170
5171 cx.update_editor(|editor, window, cx| {
5172 editor.add_selection_below(&Default::default(), window, cx);
5173 });
5174
5175 cx.assert_editor_state(indoc!(
5176 r#"abc
5177 defˇghi
5178
5179 jk
5180 nlmˇo
5181 "#
5182 ));
5183
5184 // change selections
5185 cx.set_state(indoc!(
5186 r#"abc
5187 def«ˇg»hi
5188
5189 jk
5190 nlmo
5191 "#
5192 ));
5193
5194 cx.update_editor(|editor, window, cx| {
5195 editor.add_selection_below(&Default::default(), window, cx);
5196 });
5197
5198 cx.assert_editor_state(indoc!(
5199 r#"abc
5200 def«ˇg»hi
5201
5202 jk
5203 nlm«ˇo»
5204 "#
5205 ));
5206
5207 cx.update_editor(|editor, window, cx| {
5208 editor.add_selection_below(&Default::default(), window, cx);
5209 });
5210
5211 cx.assert_editor_state(indoc!(
5212 r#"abc
5213 def«ˇg»hi
5214
5215 jk
5216 nlm«ˇo»
5217 "#
5218 ));
5219
5220 cx.update_editor(|editor, window, cx| {
5221 editor.add_selection_above(&Default::default(), window, cx);
5222 });
5223
5224 cx.assert_editor_state(indoc!(
5225 r#"abc
5226 def«ˇg»hi
5227
5228 jk
5229 nlmo
5230 "#
5231 ));
5232
5233 cx.update_editor(|editor, window, cx| {
5234 editor.add_selection_above(&Default::default(), window, cx);
5235 });
5236
5237 cx.assert_editor_state(indoc!(
5238 r#"abc
5239 def«ˇg»hi
5240
5241 jk
5242 nlmo
5243 "#
5244 ));
5245
5246 // Change selections again
5247 cx.set_state(indoc!(
5248 r#"a«bc
5249 defgˇ»hi
5250
5251 jk
5252 nlmo
5253 "#
5254 ));
5255
5256 cx.update_editor(|editor, window, cx| {
5257 editor.add_selection_below(&Default::default(), window, cx);
5258 });
5259
5260 cx.assert_editor_state(indoc!(
5261 r#"a«bcˇ»
5262 d«efgˇ»hi
5263
5264 j«kˇ»
5265 nlmo
5266 "#
5267 ));
5268
5269 cx.update_editor(|editor, window, cx| {
5270 editor.add_selection_below(&Default::default(), window, cx);
5271 });
5272 cx.assert_editor_state(indoc!(
5273 r#"a«bcˇ»
5274 d«efgˇ»hi
5275
5276 j«kˇ»
5277 n«lmoˇ»
5278 "#
5279 ));
5280 cx.update_editor(|editor, window, cx| {
5281 editor.add_selection_above(&Default::default(), window, cx);
5282 });
5283
5284 cx.assert_editor_state(indoc!(
5285 r#"a«bcˇ»
5286 d«efgˇ»hi
5287
5288 j«kˇ»
5289 nlmo
5290 "#
5291 ));
5292
5293 // Change selections again
5294 cx.set_state(indoc!(
5295 r#"abc
5296 d«ˇefghi
5297
5298 jk
5299 nlm»o
5300 "#
5301 ));
5302
5303 cx.update_editor(|editor, window, cx| {
5304 editor.add_selection_above(&Default::default(), window, cx);
5305 });
5306
5307 cx.assert_editor_state(indoc!(
5308 r#"a«ˇbc»
5309 d«ˇef»ghi
5310
5311 j«ˇk»
5312 n«ˇlm»o
5313 "#
5314 ));
5315
5316 cx.update_editor(|editor, window, cx| {
5317 editor.add_selection_below(&Default::default(), window, cx);
5318 });
5319
5320 cx.assert_editor_state(indoc!(
5321 r#"abc
5322 d«ˇef»ghi
5323
5324 j«ˇk»
5325 n«ˇlm»o
5326 "#
5327 ));
5328}
5329
5330#[gpui::test]
5331async fn test_select_next(cx: &mut TestAppContext) {
5332 init_test(cx, |_| {});
5333
5334 let mut cx = EditorTestContext::new(cx).await;
5335 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5336
5337 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5338 .unwrap();
5339 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5340
5341 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5342 .unwrap();
5343 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5344
5345 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5346 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5347
5348 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5349 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5350
5351 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5352 .unwrap();
5353 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5354
5355 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5356 .unwrap();
5357 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5358}
5359
5360#[gpui::test]
5361async fn test_select_all_matches(cx: &mut TestAppContext) {
5362 init_test(cx, |_| {});
5363
5364 let mut cx = EditorTestContext::new(cx).await;
5365
5366 // Test caret-only selections
5367 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5368 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5369 .unwrap();
5370 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5371
5372 // Test left-to-right selections
5373 cx.set_state("abc\n«abcˇ»\nabc");
5374 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5375 .unwrap();
5376 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5377
5378 // Test right-to-left selections
5379 cx.set_state("abc\n«ˇabc»\nabc");
5380 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5381 .unwrap();
5382 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5383
5384 // Test selecting whitespace with caret selection
5385 cx.set_state("abc\nˇ abc\nabc");
5386 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5387 .unwrap();
5388 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5389
5390 // Test selecting whitespace with left-to-right selection
5391 cx.set_state("abc\n«ˇ »abc\nabc");
5392 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5393 .unwrap();
5394 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5395
5396 // Test no matches with right-to-left selection
5397 cx.set_state("abc\n« ˇ»abc\nabc");
5398 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5399 .unwrap();
5400 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5401}
5402
5403#[gpui::test]
5404async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5405 init_test(cx, |_| {});
5406
5407 let mut cx = EditorTestContext::new(cx).await;
5408 cx.set_state(
5409 r#"let foo = 2;
5410lˇet foo = 2;
5411let fooˇ = 2;
5412let foo = 2;
5413let foo = ˇ2;"#,
5414 );
5415
5416 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5417 .unwrap();
5418 cx.assert_editor_state(
5419 r#"let foo = 2;
5420«letˇ» foo = 2;
5421let «fooˇ» = 2;
5422let foo = 2;
5423let foo = «2ˇ»;"#,
5424 );
5425
5426 // noop for multiple selections with different contents
5427 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5428 .unwrap();
5429 cx.assert_editor_state(
5430 r#"let foo = 2;
5431«letˇ» foo = 2;
5432let «fooˇ» = 2;
5433let foo = 2;
5434let foo = «2ˇ»;"#,
5435 );
5436}
5437
5438#[gpui::test]
5439async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5440 init_test(cx, |_| {});
5441
5442 let mut cx =
5443 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5444
5445 cx.assert_editor_state(indoc! {"
5446 ˇbbb
5447 ccc
5448
5449 bbb
5450 ccc
5451 "});
5452 cx.dispatch_action(SelectPrevious::default());
5453 cx.assert_editor_state(indoc! {"
5454 «bbbˇ»
5455 ccc
5456
5457 bbb
5458 ccc
5459 "});
5460 cx.dispatch_action(SelectPrevious::default());
5461 cx.assert_editor_state(indoc! {"
5462 «bbbˇ»
5463 ccc
5464
5465 «bbbˇ»
5466 ccc
5467 "});
5468}
5469
5470#[gpui::test]
5471async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5472 init_test(cx, |_| {});
5473
5474 let mut cx = EditorTestContext::new(cx).await;
5475 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5476
5477 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5478 .unwrap();
5479 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5480
5481 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5482 .unwrap();
5483 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5484
5485 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5486 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5487
5488 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5489 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5490
5491 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5492 .unwrap();
5493 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5494
5495 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5496 .unwrap();
5497 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5498
5499 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5500 .unwrap();
5501 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5502}
5503
5504#[gpui::test]
5505async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5506 init_test(cx, |_| {});
5507
5508 let mut cx = EditorTestContext::new(cx).await;
5509 cx.set_state("aˇ");
5510
5511 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5512 .unwrap();
5513 cx.assert_editor_state("«aˇ»");
5514 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5515 .unwrap();
5516 cx.assert_editor_state("«aˇ»");
5517}
5518
5519#[gpui::test]
5520async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5521 init_test(cx, |_| {});
5522
5523 let mut cx = EditorTestContext::new(cx).await;
5524 cx.set_state(
5525 r#"let foo = 2;
5526lˇet foo = 2;
5527let fooˇ = 2;
5528let foo = 2;
5529let foo = ˇ2;"#,
5530 );
5531
5532 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5533 .unwrap();
5534 cx.assert_editor_state(
5535 r#"let foo = 2;
5536«letˇ» foo = 2;
5537let «fooˇ» = 2;
5538let foo = 2;
5539let foo = «2ˇ»;"#,
5540 );
5541
5542 // noop for multiple selections with different contents
5543 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5544 .unwrap();
5545 cx.assert_editor_state(
5546 r#"let foo = 2;
5547«letˇ» foo = 2;
5548let «fooˇ» = 2;
5549let foo = 2;
5550let foo = «2ˇ»;"#,
5551 );
5552}
5553
5554#[gpui::test]
5555async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5556 init_test(cx, |_| {});
5557
5558 let mut cx = EditorTestContext::new(cx).await;
5559 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5560
5561 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5562 .unwrap();
5563 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5564
5565 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5566 .unwrap();
5567 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5568
5569 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5570 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5571
5572 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5573 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5574
5575 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5576 .unwrap();
5577 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5578
5579 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5580 .unwrap();
5581 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5582}
5583
5584#[gpui::test]
5585async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5586 init_test(cx, |_| {});
5587
5588 let language = Arc::new(Language::new(
5589 LanguageConfig::default(),
5590 Some(tree_sitter_rust::LANGUAGE.into()),
5591 ));
5592
5593 let text = r#"
5594 use mod1::mod2::{mod3, mod4};
5595
5596 fn fn_1(param1: bool, param2: &str) {
5597 let var1 = "text";
5598 }
5599 "#
5600 .unindent();
5601
5602 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5603 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5604 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5605
5606 editor
5607 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5608 .await;
5609
5610 editor.update_in(cx, |editor, window, cx| {
5611 editor.change_selections(None, window, cx, |s| {
5612 s.select_display_ranges([
5613 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5614 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5615 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5616 ]);
5617 });
5618 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5619 });
5620 editor.update(cx, |editor, cx| {
5621 assert_text_with_selections(
5622 editor,
5623 indoc! {r#"
5624 use mod1::mod2::{mod3, «mod4ˇ»};
5625
5626 fn fn_1«ˇ(param1: bool, param2: &str)» {
5627 let var1 = "«textˇ»";
5628 }
5629 "#},
5630 cx,
5631 );
5632 });
5633
5634 editor.update_in(cx, |editor, window, cx| {
5635 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5636 });
5637 editor.update(cx, |editor, cx| {
5638 assert_text_with_selections(
5639 editor,
5640 indoc! {r#"
5641 use mod1::mod2::«{mod3, mod4}ˇ»;
5642
5643 «ˇfn fn_1(param1: bool, param2: &str) {
5644 let var1 = "text";
5645 }»
5646 "#},
5647 cx,
5648 );
5649 });
5650
5651 editor.update_in(cx, |editor, window, cx| {
5652 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5653 });
5654 assert_eq!(
5655 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5656 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5657 );
5658
5659 // Trying to expand the selected syntax node one more time has no effect.
5660 editor.update_in(cx, |editor, window, cx| {
5661 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5662 });
5663 assert_eq!(
5664 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5665 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5666 );
5667
5668 editor.update_in(cx, |editor, window, cx| {
5669 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5670 });
5671 editor.update(cx, |editor, cx| {
5672 assert_text_with_selections(
5673 editor,
5674 indoc! {r#"
5675 use mod1::mod2::«{mod3, mod4}ˇ»;
5676
5677 «ˇfn fn_1(param1: bool, param2: &str) {
5678 let var1 = "text";
5679 }»
5680 "#},
5681 cx,
5682 );
5683 });
5684
5685 editor.update_in(cx, |editor, window, cx| {
5686 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5687 });
5688 editor.update(cx, |editor, cx| {
5689 assert_text_with_selections(
5690 editor,
5691 indoc! {r#"
5692 use mod1::mod2::{mod3, «mod4ˇ»};
5693
5694 fn fn_1«ˇ(param1: bool, param2: &str)» {
5695 let var1 = "«textˇ»";
5696 }
5697 "#},
5698 cx,
5699 );
5700 });
5701
5702 editor.update_in(cx, |editor, window, cx| {
5703 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5704 });
5705 editor.update(cx, |editor, cx| {
5706 assert_text_with_selections(
5707 editor,
5708 indoc! {r#"
5709 use mod1::mod2::{mod3, mo«ˇ»d4};
5710
5711 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5712 let var1 = "te«ˇ»xt";
5713 }
5714 "#},
5715 cx,
5716 );
5717 });
5718
5719 // Trying to shrink the selected syntax node one more time has no effect.
5720 editor.update_in(cx, |editor, window, cx| {
5721 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5722 });
5723 editor.update_in(cx, |editor, _, cx| {
5724 assert_text_with_selections(
5725 editor,
5726 indoc! {r#"
5727 use mod1::mod2::{mod3, mo«ˇ»d4};
5728
5729 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5730 let var1 = "te«ˇ»xt";
5731 }
5732 "#},
5733 cx,
5734 );
5735 });
5736
5737 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5738 // a fold.
5739 editor.update_in(cx, |editor, window, cx| {
5740 editor.fold_creases(
5741 vec![
5742 Crease::simple(
5743 Point::new(0, 21)..Point::new(0, 24),
5744 FoldPlaceholder::test(),
5745 ),
5746 Crease::simple(
5747 Point::new(3, 20)..Point::new(3, 22),
5748 FoldPlaceholder::test(),
5749 ),
5750 ],
5751 true,
5752 window,
5753 cx,
5754 );
5755 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5756 });
5757 editor.update(cx, |editor, cx| {
5758 assert_text_with_selections(
5759 editor,
5760 indoc! {r#"
5761 use mod1::mod2::«{mod3, mod4}ˇ»;
5762
5763 fn fn_1«ˇ(param1: bool, param2: &str)» {
5764 «let var1 = "text";ˇ»
5765 }
5766 "#},
5767 cx,
5768 );
5769 });
5770}
5771
5772#[gpui::test]
5773async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5774 init_test(cx, |_| {});
5775
5776 let base_text = r#"
5777 impl A {
5778 // this is an uncommitted comment
5779
5780 fn b() {
5781 c();
5782 }
5783
5784 // this is another uncommitted comment
5785
5786 fn d() {
5787 // e
5788 // f
5789 }
5790 }
5791
5792 fn g() {
5793 // h
5794 }
5795 "#
5796 .unindent();
5797
5798 let text = r#"
5799 ˇimpl A {
5800
5801 fn b() {
5802 c();
5803 }
5804
5805 fn d() {
5806 // e
5807 // f
5808 }
5809 }
5810
5811 fn g() {
5812 // h
5813 }
5814 "#
5815 .unindent();
5816
5817 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5818 cx.set_state(&text);
5819 cx.set_head_text(&base_text);
5820 cx.update_editor(|editor, window, cx| {
5821 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5822 });
5823
5824 cx.assert_state_with_diff(
5825 "
5826 ˇimpl A {
5827 - // this is an uncommitted comment
5828
5829 fn b() {
5830 c();
5831 }
5832
5833 - // this is another uncommitted comment
5834 -
5835 fn d() {
5836 // e
5837 // f
5838 }
5839 }
5840
5841 fn g() {
5842 // h
5843 }
5844 "
5845 .unindent(),
5846 );
5847
5848 let expected_display_text = "
5849 impl A {
5850 // this is an uncommitted comment
5851
5852 fn b() {
5853 ⋯
5854 }
5855
5856 // this is another uncommitted comment
5857
5858 fn d() {
5859 ⋯
5860 }
5861 }
5862
5863 fn g() {
5864 ⋯
5865 }
5866 "
5867 .unindent();
5868
5869 cx.update_editor(|editor, window, cx| {
5870 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5871 assert_eq!(editor.display_text(cx), expected_display_text);
5872 });
5873}
5874
5875#[gpui::test]
5876async fn test_autoindent(cx: &mut TestAppContext) {
5877 init_test(cx, |_| {});
5878
5879 let language = Arc::new(
5880 Language::new(
5881 LanguageConfig {
5882 brackets: BracketPairConfig {
5883 pairs: vec![
5884 BracketPair {
5885 start: "{".to_string(),
5886 end: "}".to_string(),
5887 close: false,
5888 surround: false,
5889 newline: true,
5890 },
5891 BracketPair {
5892 start: "(".to_string(),
5893 end: ")".to_string(),
5894 close: false,
5895 surround: false,
5896 newline: true,
5897 },
5898 ],
5899 ..Default::default()
5900 },
5901 ..Default::default()
5902 },
5903 Some(tree_sitter_rust::LANGUAGE.into()),
5904 )
5905 .with_indents_query(
5906 r#"
5907 (_ "(" ")" @end) @indent
5908 (_ "{" "}" @end) @indent
5909 "#,
5910 )
5911 .unwrap(),
5912 );
5913
5914 let text = "fn a() {}";
5915
5916 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5917 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5918 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5919 editor
5920 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5921 .await;
5922
5923 editor.update_in(cx, |editor, window, cx| {
5924 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5925 editor.newline(&Newline, window, cx);
5926 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5927 assert_eq!(
5928 editor.selections.ranges(cx),
5929 &[
5930 Point::new(1, 4)..Point::new(1, 4),
5931 Point::new(3, 4)..Point::new(3, 4),
5932 Point::new(5, 0)..Point::new(5, 0)
5933 ]
5934 );
5935 });
5936}
5937
5938#[gpui::test]
5939async fn test_autoindent_selections(cx: &mut TestAppContext) {
5940 init_test(cx, |_| {});
5941
5942 {
5943 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5944 cx.set_state(indoc! {"
5945 impl A {
5946
5947 fn b() {}
5948
5949 «fn c() {
5950
5951 }ˇ»
5952 }
5953 "});
5954
5955 cx.update_editor(|editor, window, cx| {
5956 editor.autoindent(&Default::default(), window, cx);
5957 });
5958
5959 cx.assert_editor_state(indoc! {"
5960 impl A {
5961
5962 fn b() {}
5963
5964 «fn c() {
5965
5966 }ˇ»
5967 }
5968 "});
5969 }
5970
5971 {
5972 let mut cx = EditorTestContext::new_multibuffer(
5973 cx,
5974 [indoc! { "
5975 impl A {
5976 «
5977 // a
5978 fn b(){}
5979 »
5980 «
5981 }
5982 fn c(){}
5983 »
5984 "}],
5985 );
5986
5987 let buffer = cx.update_editor(|editor, _, cx| {
5988 let buffer = editor.buffer().update(cx, |buffer, _| {
5989 buffer.all_buffers().iter().next().unwrap().clone()
5990 });
5991 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5992 buffer
5993 });
5994
5995 cx.run_until_parked();
5996 cx.update_editor(|editor, window, cx| {
5997 editor.select_all(&Default::default(), window, cx);
5998 editor.autoindent(&Default::default(), window, cx)
5999 });
6000 cx.run_until_parked();
6001
6002 cx.update(|_, cx| {
6003 pretty_assertions::assert_eq!(
6004 buffer.read(cx).text(),
6005 indoc! { "
6006 impl A {
6007
6008 // a
6009 fn b(){}
6010
6011
6012 }
6013 fn c(){}
6014
6015 " }
6016 )
6017 });
6018 }
6019}
6020
6021#[gpui::test]
6022async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6023 init_test(cx, |_| {});
6024
6025 let mut cx = EditorTestContext::new(cx).await;
6026
6027 let language = Arc::new(Language::new(
6028 LanguageConfig {
6029 brackets: BracketPairConfig {
6030 pairs: vec![
6031 BracketPair {
6032 start: "{".to_string(),
6033 end: "}".to_string(),
6034 close: true,
6035 surround: true,
6036 newline: true,
6037 },
6038 BracketPair {
6039 start: "(".to_string(),
6040 end: ")".to_string(),
6041 close: true,
6042 surround: true,
6043 newline: true,
6044 },
6045 BracketPair {
6046 start: "/*".to_string(),
6047 end: " */".to_string(),
6048 close: true,
6049 surround: true,
6050 newline: true,
6051 },
6052 BracketPair {
6053 start: "[".to_string(),
6054 end: "]".to_string(),
6055 close: false,
6056 surround: false,
6057 newline: true,
6058 },
6059 BracketPair {
6060 start: "\"".to_string(),
6061 end: "\"".to_string(),
6062 close: true,
6063 surround: true,
6064 newline: false,
6065 },
6066 BracketPair {
6067 start: "<".to_string(),
6068 end: ">".to_string(),
6069 close: false,
6070 surround: true,
6071 newline: true,
6072 },
6073 ],
6074 ..Default::default()
6075 },
6076 autoclose_before: "})]".to_string(),
6077 ..Default::default()
6078 },
6079 Some(tree_sitter_rust::LANGUAGE.into()),
6080 ));
6081
6082 cx.language_registry().add(language.clone());
6083 cx.update_buffer(|buffer, cx| {
6084 buffer.set_language(Some(language), cx);
6085 });
6086
6087 cx.set_state(
6088 &r#"
6089 🏀ˇ
6090 εˇ
6091 ❤️ˇ
6092 "#
6093 .unindent(),
6094 );
6095
6096 // autoclose multiple nested brackets at multiple cursors
6097 cx.update_editor(|editor, window, cx| {
6098 editor.handle_input("{", window, cx);
6099 editor.handle_input("{", window, cx);
6100 editor.handle_input("{", window, cx);
6101 });
6102 cx.assert_editor_state(
6103 &"
6104 🏀{{{ˇ}}}
6105 ε{{{ˇ}}}
6106 ❤️{{{ˇ}}}
6107 "
6108 .unindent(),
6109 );
6110
6111 // insert a different closing bracket
6112 cx.update_editor(|editor, window, cx| {
6113 editor.handle_input(")", window, cx);
6114 });
6115 cx.assert_editor_state(
6116 &"
6117 🏀{{{)ˇ}}}
6118 ε{{{)ˇ}}}
6119 ❤️{{{)ˇ}}}
6120 "
6121 .unindent(),
6122 );
6123
6124 // skip over the auto-closed brackets when typing a closing bracket
6125 cx.update_editor(|editor, window, cx| {
6126 editor.move_right(&MoveRight, window, cx);
6127 editor.handle_input("}", window, cx);
6128 editor.handle_input("}", window, cx);
6129 editor.handle_input("}", window, cx);
6130 });
6131 cx.assert_editor_state(
6132 &"
6133 🏀{{{)}}}}ˇ
6134 ε{{{)}}}}ˇ
6135 ❤️{{{)}}}}ˇ
6136 "
6137 .unindent(),
6138 );
6139
6140 // autoclose multi-character pairs
6141 cx.set_state(
6142 &"
6143 ˇ
6144 ˇ
6145 "
6146 .unindent(),
6147 );
6148 cx.update_editor(|editor, window, cx| {
6149 editor.handle_input("/", window, cx);
6150 editor.handle_input("*", window, cx);
6151 });
6152 cx.assert_editor_state(
6153 &"
6154 /*ˇ */
6155 /*ˇ */
6156 "
6157 .unindent(),
6158 );
6159
6160 // one cursor autocloses a multi-character pair, one cursor
6161 // does not autoclose.
6162 cx.set_state(
6163 &"
6164 /ˇ
6165 ˇ
6166 "
6167 .unindent(),
6168 );
6169 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6170 cx.assert_editor_state(
6171 &"
6172 /*ˇ */
6173 *ˇ
6174 "
6175 .unindent(),
6176 );
6177
6178 // Don't autoclose if the next character isn't whitespace and isn't
6179 // listed in the language's "autoclose_before" section.
6180 cx.set_state("ˇa b");
6181 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6182 cx.assert_editor_state("{ˇa b");
6183
6184 // Don't autoclose if `close` is false for the bracket pair
6185 cx.set_state("ˇ");
6186 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6187 cx.assert_editor_state("[ˇ");
6188
6189 // Surround with brackets if text is selected
6190 cx.set_state("«aˇ» b");
6191 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6192 cx.assert_editor_state("{«aˇ»} b");
6193
6194 // Autclose pair where the start and end characters are the same
6195 cx.set_state("aˇ");
6196 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6197 cx.assert_editor_state("a\"ˇ\"");
6198 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6199 cx.assert_editor_state("a\"\"ˇ");
6200
6201 // Don't autoclose pair if autoclose is disabled
6202 cx.set_state("ˇ");
6203 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6204 cx.assert_editor_state("<ˇ");
6205
6206 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6207 cx.set_state("«aˇ» b");
6208 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6209 cx.assert_editor_state("<«aˇ»> b");
6210}
6211
6212#[gpui::test]
6213async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6214 init_test(cx, |settings| {
6215 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6216 });
6217
6218 let mut cx = EditorTestContext::new(cx).await;
6219
6220 let language = Arc::new(Language::new(
6221 LanguageConfig {
6222 brackets: BracketPairConfig {
6223 pairs: vec![
6224 BracketPair {
6225 start: "{".to_string(),
6226 end: "}".to_string(),
6227 close: true,
6228 surround: true,
6229 newline: true,
6230 },
6231 BracketPair {
6232 start: "(".to_string(),
6233 end: ")".to_string(),
6234 close: true,
6235 surround: true,
6236 newline: true,
6237 },
6238 BracketPair {
6239 start: "[".to_string(),
6240 end: "]".to_string(),
6241 close: false,
6242 surround: false,
6243 newline: true,
6244 },
6245 ],
6246 ..Default::default()
6247 },
6248 autoclose_before: "})]".to_string(),
6249 ..Default::default()
6250 },
6251 Some(tree_sitter_rust::LANGUAGE.into()),
6252 ));
6253
6254 cx.language_registry().add(language.clone());
6255 cx.update_buffer(|buffer, cx| {
6256 buffer.set_language(Some(language), cx);
6257 });
6258
6259 cx.set_state(
6260 &"
6261 ˇ
6262 ˇ
6263 ˇ
6264 "
6265 .unindent(),
6266 );
6267
6268 // ensure only matching closing brackets are skipped over
6269 cx.update_editor(|editor, window, cx| {
6270 editor.handle_input("}", window, cx);
6271 editor.move_left(&MoveLeft, window, cx);
6272 editor.handle_input(")", window, cx);
6273 editor.move_left(&MoveLeft, window, cx);
6274 });
6275 cx.assert_editor_state(
6276 &"
6277 ˇ)}
6278 ˇ)}
6279 ˇ)}
6280 "
6281 .unindent(),
6282 );
6283
6284 // skip-over closing brackets at multiple cursors
6285 cx.update_editor(|editor, window, cx| {
6286 editor.handle_input(")", window, cx);
6287 editor.handle_input("}", window, cx);
6288 });
6289 cx.assert_editor_state(
6290 &"
6291 )}ˇ
6292 )}ˇ
6293 )}ˇ
6294 "
6295 .unindent(),
6296 );
6297
6298 // ignore non-close brackets
6299 cx.update_editor(|editor, window, cx| {
6300 editor.handle_input("]", window, cx);
6301 editor.move_left(&MoveLeft, window, cx);
6302 editor.handle_input("]", window, cx);
6303 });
6304 cx.assert_editor_state(
6305 &"
6306 )}]ˇ]
6307 )}]ˇ]
6308 )}]ˇ]
6309 "
6310 .unindent(),
6311 );
6312}
6313
6314#[gpui::test]
6315async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6316 init_test(cx, |_| {});
6317
6318 let mut cx = EditorTestContext::new(cx).await;
6319
6320 let html_language = Arc::new(
6321 Language::new(
6322 LanguageConfig {
6323 name: "HTML".into(),
6324 brackets: BracketPairConfig {
6325 pairs: vec![
6326 BracketPair {
6327 start: "<".into(),
6328 end: ">".into(),
6329 close: true,
6330 ..Default::default()
6331 },
6332 BracketPair {
6333 start: "{".into(),
6334 end: "}".into(),
6335 close: true,
6336 ..Default::default()
6337 },
6338 BracketPair {
6339 start: "(".into(),
6340 end: ")".into(),
6341 close: true,
6342 ..Default::default()
6343 },
6344 ],
6345 ..Default::default()
6346 },
6347 autoclose_before: "})]>".into(),
6348 ..Default::default()
6349 },
6350 Some(tree_sitter_html::LANGUAGE.into()),
6351 )
6352 .with_injection_query(
6353 r#"
6354 (script_element
6355 (raw_text) @injection.content
6356 (#set! injection.language "javascript"))
6357 "#,
6358 )
6359 .unwrap(),
6360 );
6361
6362 let javascript_language = Arc::new(Language::new(
6363 LanguageConfig {
6364 name: "JavaScript".into(),
6365 brackets: BracketPairConfig {
6366 pairs: vec![
6367 BracketPair {
6368 start: "/*".into(),
6369 end: " */".into(),
6370 close: true,
6371 ..Default::default()
6372 },
6373 BracketPair {
6374 start: "{".into(),
6375 end: "}".into(),
6376 close: true,
6377 ..Default::default()
6378 },
6379 BracketPair {
6380 start: "(".into(),
6381 end: ")".into(),
6382 close: true,
6383 ..Default::default()
6384 },
6385 ],
6386 ..Default::default()
6387 },
6388 autoclose_before: "})]>".into(),
6389 ..Default::default()
6390 },
6391 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6392 ));
6393
6394 cx.language_registry().add(html_language.clone());
6395 cx.language_registry().add(javascript_language.clone());
6396
6397 cx.update_buffer(|buffer, cx| {
6398 buffer.set_language(Some(html_language), cx);
6399 });
6400
6401 cx.set_state(
6402 &r#"
6403 <body>ˇ
6404 <script>
6405 var x = 1;ˇ
6406 </script>
6407 </body>ˇ
6408 "#
6409 .unindent(),
6410 );
6411
6412 // Precondition: different languages are active at different locations.
6413 cx.update_editor(|editor, window, cx| {
6414 let snapshot = editor.snapshot(window, cx);
6415 let cursors = editor.selections.ranges::<usize>(cx);
6416 let languages = cursors
6417 .iter()
6418 .map(|c| snapshot.language_at(c.start).unwrap().name())
6419 .collect::<Vec<_>>();
6420 assert_eq!(
6421 languages,
6422 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6423 );
6424 });
6425
6426 // Angle brackets autoclose in HTML, but not JavaScript.
6427 cx.update_editor(|editor, window, cx| {
6428 editor.handle_input("<", window, cx);
6429 editor.handle_input("a", window, cx);
6430 });
6431 cx.assert_editor_state(
6432 &r#"
6433 <body><aˇ>
6434 <script>
6435 var x = 1;<aˇ
6436 </script>
6437 </body><aˇ>
6438 "#
6439 .unindent(),
6440 );
6441
6442 // Curly braces and parens autoclose in both HTML and JavaScript.
6443 cx.update_editor(|editor, window, cx| {
6444 editor.handle_input(" b=", window, cx);
6445 editor.handle_input("{", window, cx);
6446 editor.handle_input("c", window, cx);
6447 editor.handle_input("(", window, cx);
6448 });
6449 cx.assert_editor_state(
6450 &r#"
6451 <body><a b={c(ˇ)}>
6452 <script>
6453 var x = 1;<a b={c(ˇ)}
6454 </script>
6455 </body><a b={c(ˇ)}>
6456 "#
6457 .unindent(),
6458 );
6459
6460 // Brackets that were already autoclosed are skipped.
6461 cx.update_editor(|editor, window, cx| {
6462 editor.handle_input(")", window, cx);
6463 editor.handle_input("d", window, cx);
6464 editor.handle_input("}", window, cx);
6465 });
6466 cx.assert_editor_state(
6467 &r#"
6468 <body><a b={c()d}ˇ>
6469 <script>
6470 var x = 1;<a b={c()d}ˇ
6471 </script>
6472 </body><a b={c()d}ˇ>
6473 "#
6474 .unindent(),
6475 );
6476 cx.update_editor(|editor, window, cx| {
6477 editor.handle_input(">", window, cx);
6478 });
6479 cx.assert_editor_state(
6480 &r#"
6481 <body><a b={c()d}>ˇ
6482 <script>
6483 var x = 1;<a b={c()d}>ˇ
6484 </script>
6485 </body><a b={c()d}>ˇ
6486 "#
6487 .unindent(),
6488 );
6489
6490 // Reset
6491 cx.set_state(
6492 &r#"
6493 <body>ˇ
6494 <script>
6495 var x = 1;ˇ
6496 </script>
6497 </body>ˇ
6498 "#
6499 .unindent(),
6500 );
6501
6502 cx.update_editor(|editor, window, cx| {
6503 editor.handle_input("<", window, cx);
6504 });
6505 cx.assert_editor_state(
6506 &r#"
6507 <body><ˇ>
6508 <script>
6509 var x = 1;<ˇ
6510 </script>
6511 </body><ˇ>
6512 "#
6513 .unindent(),
6514 );
6515
6516 // When backspacing, the closing angle brackets are removed.
6517 cx.update_editor(|editor, window, cx| {
6518 editor.backspace(&Backspace, window, cx);
6519 });
6520 cx.assert_editor_state(
6521 &r#"
6522 <body>ˇ
6523 <script>
6524 var x = 1;ˇ
6525 </script>
6526 </body>ˇ
6527 "#
6528 .unindent(),
6529 );
6530
6531 // Block comments autoclose in JavaScript, but not HTML.
6532 cx.update_editor(|editor, window, cx| {
6533 editor.handle_input("/", window, cx);
6534 editor.handle_input("*", window, cx);
6535 });
6536 cx.assert_editor_state(
6537 &r#"
6538 <body>/*ˇ
6539 <script>
6540 var x = 1;/*ˇ */
6541 </script>
6542 </body>/*ˇ
6543 "#
6544 .unindent(),
6545 );
6546}
6547
6548#[gpui::test]
6549async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6550 init_test(cx, |_| {});
6551
6552 let mut cx = EditorTestContext::new(cx).await;
6553
6554 let rust_language = Arc::new(
6555 Language::new(
6556 LanguageConfig {
6557 name: "Rust".into(),
6558 brackets: serde_json::from_value(json!([
6559 { "start": "{", "end": "}", "close": true, "newline": true },
6560 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6561 ]))
6562 .unwrap(),
6563 autoclose_before: "})]>".into(),
6564 ..Default::default()
6565 },
6566 Some(tree_sitter_rust::LANGUAGE.into()),
6567 )
6568 .with_override_query("(string_literal) @string")
6569 .unwrap(),
6570 );
6571
6572 cx.language_registry().add(rust_language.clone());
6573 cx.update_buffer(|buffer, cx| {
6574 buffer.set_language(Some(rust_language), cx);
6575 });
6576
6577 cx.set_state(
6578 &r#"
6579 let x = ˇ
6580 "#
6581 .unindent(),
6582 );
6583
6584 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6585 cx.update_editor(|editor, window, cx| {
6586 editor.handle_input("\"", window, cx);
6587 });
6588 cx.assert_editor_state(
6589 &r#"
6590 let x = "ˇ"
6591 "#
6592 .unindent(),
6593 );
6594
6595 // Inserting another quotation mark. The cursor moves across the existing
6596 // automatically-inserted quotation mark.
6597 cx.update_editor(|editor, window, cx| {
6598 editor.handle_input("\"", window, cx);
6599 });
6600 cx.assert_editor_state(
6601 &r#"
6602 let x = ""ˇ
6603 "#
6604 .unindent(),
6605 );
6606
6607 // Reset
6608 cx.set_state(
6609 &r#"
6610 let x = ˇ
6611 "#
6612 .unindent(),
6613 );
6614
6615 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6616 cx.update_editor(|editor, window, cx| {
6617 editor.handle_input("\"", window, cx);
6618 editor.handle_input(" ", window, cx);
6619 editor.move_left(&Default::default(), window, cx);
6620 editor.handle_input("\\", window, cx);
6621 editor.handle_input("\"", window, cx);
6622 });
6623 cx.assert_editor_state(
6624 &r#"
6625 let x = "\"ˇ "
6626 "#
6627 .unindent(),
6628 );
6629
6630 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6631 // mark. Nothing is inserted.
6632 cx.update_editor(|editor, window, cx| {
6633 editor.move_right(&Default::default(), window, cx);
6634 editor.handle_input("\"", window, cx);
6635 });
6636 cx.assert_editor_state(
6637 &r#"
6638 let x = "\" "ˇ
6639 "#
6640 .unindent(),
6641 );
6642}
6643
6644#[gpui::test]
6645async fn test_surround_with_pair(cx: &mut TestAppContext) {
6646 init_test(cx, |_| {});
6647
6648 let language = Arc::new(Language::new(
6649 LanguageConfig {
6650 brackets: BracketPairConfig {
6651 pairs: vec![
6652 BracketPair {
6653 start: "{".to_string(),
6654 end: "}".to_string(),
6655 close: true,
6656 surround: true,
6657 newline: true,
6658 },
6659 BracketPair {
6660 start: "/* ".to_string(),
6661 end: "*/".to_string(),
6662 close: true,
6663 surround: true,
6664 ..Default::default()
6665 },
6666 ],
6667 ..Default::default()
6668 },
6669 ..Default::default()
6670 },
6671 Some(tree_sitter_rust::LANGUAGE.into()),
6672 ));
6673
6674 let text = r#"
6675 a
6676 b
6677 c
6678 "#
6679 .unindent();
6680
6681 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6683 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6684 editor
6685 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6686 .await;
6687
6688 editor.update_in(cx, |editor, window, cx| {
6689 editor.change_selections(None, window, cx, |s| {
6690 s.select_display_ranges([
6691 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6692 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6693 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6694 ])
6695 });
6696
6697 editor.handle_input("{", window, cx);
6698 editor.handle_input("{", window, cx);
6699 editor.handle_input("{", window, cx);
6700 assert_eq!(
6701 editor.text(cx),
6702 "
6703 {{{a}}}
6704 {{{b}}}
6705 {{{c}}}
6706 "
6707 .unindent()
6708 );
6709 assert_eq!(
6710 editor.selections.display_ranges(cx),
6711 [
6712 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6713 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6714 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6715 ]
6716 );
6717
6718 editor.undo(&Undo, window, cx);
6719 editor.undo(&Undo, window, cx);
6720 editor.undo(&Undo, window, cx);
6721 assert_eq!(
6722 editor.text(cx),
6723 "
6724 a
6725 b
6726 c
6727 "
6728 .unindent()
6729 );
6730 assert_eq!(
6731 editor.selections.display_ranges(cx),
6732 [
6733 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6734 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6735 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6736 ]
6737 );
6738
6739 // Ensure inserting the first character of a multi-byte bracket pair
6740 // doesn't surround the selections with the bracket.
6741 editor.handle_input("/", window, cx);
6742 assert_eq!(
6743 editor.text(cx),
6744 "
6745 /
6746 /
6747 /
6748 "
6749 .unindent()
6750 );
6751 assert_eq!(
6752 editor.selections.display_ranges(cx),
6753 [
6754 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6755 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6756 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6757 ]
6758 );
6759
6760 editor.undo(&Undo, window, cx);
6761 assert_eq!(
6762 editor.text(cx),
6763 "
6764 a
6765 b
6766 c
6767 "
6768 .unindent()
6769 );
6770 assert_eq!(
6771 editor.selections.display_ranges(cx),
6772 [
6773 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6774 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6775 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6776 ]
6777 );
6778
6779 // Ensure inserting the last character of a multi-byte bracket pair
6780 // doesn't surround the selections with the bracket.
6781 editor.handle_input("*", window, cx);
6782 assert_eq!(
6783 editor.text(cx),
6784 "
6785 *
6786 *
6787 *
6788 "
6789 .unindent()
6790 );
6791 assert_eq!(
6792 editor.selections.display_ranges(cx),
6793 [
6794 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6795 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6796 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6797 ]
6798 );
6799 });
6800}
6801
6802#[gpui::test]
6803async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6804 init_test(cx, |_| {});
6805
6806 let language = Arc::new(Language::new(
6807 LanguageConfig {
6808 brackets: BracketPairConfig {
6809 pairs: vec![BracketPair {
6810 start: "{".to_string(),
6811 end: "}".to_string(),
6812 close: true,
6813 surround: true,
6814 newline: true,
6815 }],
6816 ..Default::default()
6817 },
6818 autoclose_before: "}".to_string(),
6819 ..Default::default()
6820 },
6821 Some(tree_sitter_rust::LANGUAGE.into()),
6822 ));
6823
6824 let text = r#"
6825 a
6826 b
6827 c
6828 "#
6829 .unindent();
6830
6831 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6832 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6833 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6834 editor
6835 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6836 .await;
6837
6838 editor.update_in(cx, |editor, window, cx| {
6839 editor.change_selections(None, window, cx, |s| {
6840 s.select_ranges([
6841 Point::new(0, 1)..Point::new(0, 1),
6842 Point::new(1, 1)..Point::new(1, 1),
6843 Point::new(2, 1)..Point::new(2, 1),
6844 ])
6845 });
6846
6847 editor.handle_input("{", window, cx);
6848 editor.handle_input("{", window, cx);
6849 editor.handle_input("_", window, cx);
6850 assert_eq!(
6851 editor.text(cx),
6852 "
6853 a{{_}}
6854 b{{_}}
6855 c{{_}}
6856 "
6857 .unindent()
6858 );
6859 assert_eq!(
6860 editor.selections.ranges::<Point>(cx),
6861 [
6862 Point::new(0, 4)..Point::new(0, 4),
6863 Point::new(1, 4)..Point::new(1, 4),
6864 Point::new(2, 4)..Point::new(2, 4)
6865 ]
6866 );
6867
6868 editor.backspace(&Default::default(), window, cx);
6869 editor.backspace(&Default::default(), window, cx);
6870 assert_eq!(
6871 editor.text(cx),
6872 "
6873 a{}
6874 b{}
6875 c{}
6876 "
6877 .unindent()
6878 );
6879 assert_eq!(
6880 editor.selections.ranges::<Point>(cx),
6881 [
6882 Point::new(0, 2)..Point::new(0, 2),
6883 Point::new(1, 2)..Point::new(1, 2),
6884 Point::new(2, 2)..Point::new(2, 2)
6885 ]
6886 );
6887
6888 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6889 assert_eq!(
6890 editor.text(cx),
6891 "
6892 a
6893 b
6894 c
6895 "
6896 .unindent()
6897 );
6898 assert_eq!(
6899 editor.selections.ranges::<Point>(cx),
6900 [
6901 Point::new(0, 1)..Point::new(0, 1),
6902 Point::new(1, 1)..Point::new(1, 1),
6903 Point::new(2, 1)..Point::new(2, 1)
6904 ]
6905 );
6906 });
6907}
6908
6909#[gpui::test]
6910async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
6911 init_test(cx, |settings| {
6912 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6913 });
6914
6915 let mut cx = EditorTestContext::new(cx).await;
6916
6917 let language = Arc::new(Language::new(
6918 LanguageConfig {
6919 brackets: BracketPairConfig {
6920 pairs: vec![
6921 BracketPair {
6922 start: "{".to_string(),
6923 end: "}".to_string(),
6924 close: true,
6925 surround: true,
6926 newline: true,
6927 },
6928 BracketPair {
6929 start: "(".to_string(),
6930 end: ")".to_string(),
6931 close: true,
6932 surround: true,
6933 newline: true,
6934 },
6935 BracketPair {
6936 start: "[".to_string(),
6937 end: "]".to_string(),
6938 close: false,
6939 surround: true,
6940 newline: true,
6941 },
6942 ],
6943 ..Default::default()
6944 },
6945 autoclose_before: "})]".to_string(),
6946 ..Default::default()
6947 },
6948 Some(tree_sitter_rust::LANGUAGE.into()),
6949 ));
6950
6951 cx.language_registry().add(language.clone());
6952 cx.update_buffer(|buffer, cx| {
6953 buffer.set_language(Some(language), cx);
6954 });
6955
6956 cx.set_state(
6957 &"
6958 {(ˇ)}
6959 [[ˇ]]
6960 {(ˇ)}
6961 "
6962 .unindent(),
6963 );
6964
6965 cx.update_editor(|editor, window, cx| {
6966 editor.backspace(&Default::default(), window, cx);
6967 editor.backspace(&Default::default(), window, cx);
6968 });
6969
6970 cx.assert_editor_state(
6971 &"
6972 ˇ
6973 ˇ]]
6974 ˇ
6975 "
6976 .unindent(),
6977 );
6978
6979 cx.update_editor(|editor, window, cx| {
6980 editor.handle_input("{", window, cx);
6981 editor.handle_input("{", window, cx);
6982 editor.move_right(&MoveRight, window, cx);
6983 editor.move_right(&MoveRight, window, cx);
6984 editor.move_left(&MoveLeft, window, cx);
6985 editor.move_left(&MoveLeft, window, cx);
6986 editor.backspace(&Default::default(), window, cx);
6987 });
6988
6989 cx.assert_editor_state(
6990 &"
6991 {ˇ}
6992 {ˇ}]]
6993 {ˇ}
6994 "
6995 .unindent(),
6996 );
6997
6998 cx.update_editor(|editor, window, cx| {
6999 editor.backspace(&Default::default(), window, cx);
7000 });
7001
7002 cx.assert_editor_state(
7003 &"
7004 ˇ
7005 ˇ]]
7006 ˇ
7007 "
7008 .unindent(),
7009 );
7010}
7011
7012#[gpui::test]
7013async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7014 init_test(cx, |_| {});
7015
7016 let language = Arc::new(Language::new(
7017 LanguageConfig::default(),
7018 Some(tree_sitter_rust::LANGUAGE.into()),
7019 ));
7020
7021 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7022 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7023 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7024 editor
7025 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7026 .await;
7027
7028 editor.update_in(cx, |editor, window, cx| {
7029 editor.set_auto_replace_emoji_shortcode(true);
7030
7031 editor.handle_input("Hello ", window, cx);
7032 editor.handle_input(":wave", window, cx);
7033 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7034
7035 editor.handle_input(":", window, cx);
7036 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7037
7038 editor.handle_input(" :smile", window, cx);
7039 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7040
7041 editor.handle_input(":", window, cx);
7042 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7043
7044 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7045 editor.handle_input(":wave", window, cx);
7046 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7047
7048 editor.handle_input(":", window, cx);
7049 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7050
7051 editor.handle_input(":1", window, cx);
7052 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7053
7054 editor.handle_input(":", window, cx);
7055 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7056
7057 // Ensure shortcode does not get replaced when it is part of a word
7058 editor.handle_input(" Test:wave", window, cx);
7059 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7060
7061 editor.handle_input(":", window, cx);
7062 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7063
7064 editor.set_auto_replace_emoji_shortcode(false);
7065
7066 // Ensure shortcode does not get replaced when auto replace is off
7067 editor.handle_input(" :wave", window, cx);
7068 assert_eq!(
7069 editor.text(cx),
7070 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7071 );
7072
7073 editor.handle_input(":", window, cx);
7074 assert_eq!(
7075 editor.text(cx),
7076 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7077 );
7078 });
7079}
7080
7081#[gpui::test]
7082async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7083 init_test(cx, |_| {});
7084
7085 let (text, insertion_ranges) = marked_text_ranges(
7086 indoc! {"
7087 ˇ
7088 "},
7089 false,
7090 );
7091
7092 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7093 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7094
7095 _ = editor.update_in(cx, |editor, window, cx| {
7096 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7097
7098 editor
7099 .insert_snippet(&insertion_ranges, snippet, window, cx)
7100 .unwrap();
7101
7102 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7103 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7104 assert_eq!(editor.text(cx), expected_text);
7105 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7106 }
7107
7108 assert(
7109 editor,
7110 cx,
7111 indoc! {"
7112 type «» =•
7113 "},
7114 );
7115
7116 assert!(editor.context_menu_visible(), "There should be a matches");
7117 });
7118}
7119
7120#[gpui::test]
7121async fn test_snippets(cx: &mut TestAppContext) {
7122 init_test(cx, |_| {});
7123
7124 let (text, insertion_ranges) = marked_text_ranges(
7125 indoc! {"
7126 a.ˇ b
7127 a.ˇ b
7128 a.ˇ b
7129 "},
7130 false,
7131 );
7132
7133 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7134 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7135
7136 editor.update_in(cx, |editor, window, cx| {
7137 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7138
7139 editor
7140 .insert_snippet(&insertion_ranges, snippet, window, cx)
7141 .unwrap();
7142
7143 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7144 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7145 assert_eq!(editor.text(cx), expected_text);
7146 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7147 }
7148
7149 assert(
7150 editor,
7151 cx,
7152 indoc! {"
7153 a.f(«one», two, «three») b
7154 a.f(«one», two, «three») b
7155 a.f(«one», two, «three») b
7156 "},
7157 );
7158
7159 // Can't move earlier than the first tab stop
7160 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7161 assert(
7162 editor,
7163 cx,
7164 indoc! {"
7165 a.f(«one», two, «three») b
7166 a.f(«one», two, «three») b
7167 a.f(«one», two, «three») b
7168 "},
7169 );
7170
7171 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7172 assert(
7173 editor,
7174 cx,
7175 indoc! {"
7176 a.f(one, «two», three) b
7177 a.f(one, «two», three) b
7178 a.f(one, «two», three) b
7179 "},
7180 );
7181
7182 editor.move_to_prev_snippet_tabstop(window, cx);
7183 assert(
7184 editor,
7185 cx,
7186 indoc! {"
7187 a.f(«one», two, «three») b
7188 a.f(«one», two, «three») b
7189 a.f(«one», two, «three») b
7190 "},
7191 );
7192
7193 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7194 assert(
7195 editor,
7196 cx,
7197 indoc! {"
7198 a.f(one, «two», three) b
7199 a.f(one, «two», three) b
7200 a.f(one, «two», three) b
7201 "},
7202 );
7203 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7204 assert(
7205 editor,
7206 cx,
7207 indoc! {"
7208 a.f(one, two, three)ˇ b
7209 a.f(one, two, three)ˇ b
7210 a.f(one, two, three)ˇ b
7211 "},
7212 );
7213
7214 // As soon as the last tab stop is reached, snippet state is gone
7215 editor.move_to_prev_snippet_tabstop(window, cx);
7216 assert(
7217 editor,
7218 cx,
7219 indoc! {"
7220 a.f(one, two, three)ˇ b
7221 a.f(one, two, three)ˇ b
7222 a.f(one, two, three)ˇ b
7223 "},
7224 );
7225 });
7226}
7227
7228#[gpui::test]
7229async fn test_document_format_during_save(cx: &mut TestAppContext) {
7230 init_test(cx, |_| {});
7231
7232 let fs = FakeFs::new(cx.executor());
7233 fs.insert_file(path!("/file.rs"), Default::default()).await;
7234
7235 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7236
7237 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7238 language_registry.add(rust_lang());
7239 let mut fake_servers = language_registry.register_fake_lsp(
7240 "Rust",
7241 FakeLspAdapter {
7242 capabilities: lsp::ServerCapabilities {
7243 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7244 ..Default::default()
7245 },
7246 ..Default::default()
7247 },
7248 );
7249
7250 let buffer = project
7251 .update(cx, |project, cx| {
7252 project.open_local_buffer(path!("/file.rs"), cx)
7253 })
7254 .await
7255 .unwrap();
7256
7257 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7258 let (editor, cx) = cx.add_window_view(|window, cx| {
7259 build_editor_with_project(project.clone(), buffer, window, cx)
7260 });
7261 editor.update_in(cx, |editor, window, cx| {
7262 editor.set_text("one\ntwo\nthree\n", window, cx)
7263 });
7264 assert!(cx.read(|cx| editor.is_dirty(cx)));
7265
7266 cx.executor().start_waiting();
7267 let fake_server = fake_servers.next().await.unwrap();
7268
7269 let save = editor
7270 .update_in(cx, |editor, window, cx| {
7271 editor.save(true, project.clone(), window, cx)
7272 })
7273 .unwrap();
7274 fake_server
7275 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7276 assert_eq!(
7277 params.text_document.uri,
7278 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7279 );
7280 assert_eq!(params.options.tab_size, 4);
7281 Ok(Some(vec![lsp::TextEdit::new(
7282 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7283 ", ".to_string(),
7284 )]))
7285 })
7286 .next()
7287 .await;
7288 cx.executor().start_waiting();
7289 save.await;
7290
7291 assert_eq!(
7292 editor.update(cx, |editor, cx| editor.text(cx)),
7293 "one, two\nthree\n"
7294 );
7295 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7296
7297 editor.update_in(cx, |editor, window, cx| {
7298 editor.set_text("one\ntwo\nthree\n", window, cx)
7299 });
7300 assert!(cx.read(|cx| editor.is_dirty(cx)));
7301
7302 // Ensure we can still save even if formatting hangs.
7303 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7304 assert_eq!(
7305 params.text_document.uri,
7306 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7307 );
7308 futures::future::pending::<()>().await;
7309 unreachable!()
7310 });
7311 let save = editor
7312 .update_in(cx, |editor, window, cx| {
7313 editor.save(true, project.clone(), window, cx)
7314 })
7315 .unwrap();
7316 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7317 cx.executor().start_waiting();
7318 save.await;
7319 assert_eq!(
7320 editor.update(cx, |editor, cx| editor.text(cx)),
7321 "one\ntwo\nthree\n"
7322 );
7323 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7324
7325 // For non-dirty buffer, no formatting request should be sent
7326 let save = editor
7327 .update_in(cx, |editor, window, cx| {
7328 editor.save(true, project.clone(), window, cx)
7329 })
7330 .unwrap();
7331 let _pending_format_request = fake_server
7332 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7333 panic!("Should not be invoked on non-dirty buffer");
7334 })
7335 .next();
7336 cx.executor().start_waiting();
7337 save.await;
7338
7339 // Set rust language override and assert overridden tabsize is sent to language server
7340 update_test_language_settings(cx, |settings| {
7341 settings.languages.insert(
7342 "Rust".into(),
7343 LanguageSettingsContent {
7344 tab_size: NonZeroU32::new(8),
7345 ..Default::default()
7346 },
7347 );
7348 });
7349
7350 editor.update_in(cx, |editor, window, cx| {
7351 editor.set_text("somehting_new\n", window, cx)
7352 });
7353 assert!(cx.read(|cx| editor.is_dirty(cx)));
7354 let save = editor
7355 .update_in(cx, |editor, window, cx| {
7356 editor.save(true, project.clone(), window, cx)
7357 })
7358 .unwrap();
7359 fake_server
7360 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7361 assert_eq!(
7362 params.text_document.uri,
7363 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7364 );
7365 assert_eq!(params.options.tab_size, 8);
7366 Ok(Some(vec![]))
7367 })
7368 .next()
7369 .await;
7370 cx.executor().start_waiting();
7371 save.await;
7372}
7373
7374#[gpui::test]
7375async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7376 init_test(cx, |_| {});
7377
7378 let cols = 4;
7379 let rows = 10;
7380 let sample_text_1 = sample_text(rows, cols, 'a');
7381 assert_eq!(
7382 sample_text_1,
7383 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7384 );
7385 let sample_text_2 = sample_text(rows, cols, 'l');
7386 assert_eq!(
7387 sample_text_2,
7388 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7389 );
7390 let sample_text_3 = sample_text(rows, cols, 'v');
7391 assert_eq!(
7392 sample_text_3,
7393 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7394 );
7395
7396 let fs = FakeFs::new(cx.executor());
7397 fs.insert_tree(
7398 path!("/a"),
7399 json!({
7400 "main.rs": sample_text_1,
7401 "other.rs": sample_text_2,
7402 "lib.rs": sample_text_3,
7403 }),
7404 )
7405 .await;
7406
7407 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7408 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7409 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7410
7411 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7412 language_registry.add(rust_lang());
7413 let mut fake_servers = language_registry.register_fake_lsp(
7414 "Rust",
7415 FakeLspAdapter {
7416 capabilities: lsp::ServerCapabilities {
7417 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7418 ..Default::default()
7419 },
7420 ..Default::default()
7421 },
7422 );
7423
7424 let worktree = project.update(cx, |project, cx| {
7425 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7426 assert_eq!(worktrees.len(), 1);
7427 worktrees.pop().unwrap()
7428 });
7429 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7430
7431 let buffer_1 = project
7432 .update(cx, |project, cx| {
7433 project.open_buffer((worktree_id, "main.rs"), cx)
7434 })
7435 .await
7436 .unwrap();
7437 let buffer_2 = project
7438 .update(cx, |project, cx| {
7439 project.open_buffer((worktree_id, "other.rs"), cx)
7440 })
7441 .await
7442 .unwrap();
7443 let buffer_3 = project
7444 .update(cx, |project, cx| {
7445 project.open_buffer((worktree_id, "lib.rs"), cx)
7446 })
7447 .await
7448 .unwrap();
7449
7450 let multi_buffer = cx.new(|cx| {
7451 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7452 multi_buffer.push_excerpts(
7453 buffer_1.clone(),
7454 [
7455 ExcerptRange {
7456 context: Point::new(0, 0)..Point::new(3, 0),
7457 primary: None,
7458 },
7459 ExcerptRange {
7460 context: Point::new(5, 0)..Point::new(7, 0),
7461 primary: None,
7462 },
7463 ExcerptRange {
7464 context: Point::new(9, 0)..Point::new(10, 4),
7465 primary: None,
7466 },
7467 ],
7468 cx,
7469 );
7470 multi_buffer.push_excerpts(
7471 buffer_2.clone(),
7472 [
7473 ExcerptRange {
7474 context: Point::new(0, 0)..Point::new(3, 0),
7475 primary: None,
7476 },
7477 ExcerptRange {
7478 context: Point::new(5, 0)..Point::new(7, 0),
7479 primary: None,
7480 },
7481 ExcerptRange {
7482 context: Point::new(9, 0)..Point::new(10, 4),
7483 primary: None,
7484 },
7485 ],
7486 cx,
7487 );
7488 multi_buffer.push_excerpts(
7489 buffer_3.clone(),
7490 [
7491 ExcerptRange {
7492 context: Point::new(0, 0)..Point::new(3, 0),
7493 primary: None,
7494 },
7495 ExcerptRange {
7496 context: Point::new(5, 0)..Point::new(7, 0),
7497 primary: None,
7498 },
7499 ExcerptRange {
7500 context: Point::new(9, 0)..Point::new(10, 4),
7501 primary: None,
7502 },
7503 ],
7504 cx,
7505 );
7506 multi_buffer
7507 });
7508 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7509 Editor::new(
7510 EditorMode::Full,
7511 multi_buffer,
7512 Some(project.clone()),
7513 true,
7514 window,
7515 cx,
7516 )
7517 });
7518
7519 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7520 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7521 s.select_ranges(Some(1..2))
7522 });
7523 editor.insert("|one|two|three|", window, cx);
7524 });
7525 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7526 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7527 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7528 s.select_ranges(Some(60..70))
7529 });
7530 editor.insert("|four|five|six|", window, cx);
7531 });
7532 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7533
7534 // First two buffers should be edited, but not the third one.
7535 assert_eq!(
7536 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7537 "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}",
7538 );
7539 buffer_1.update(cx, |buffer, _| {
7540 assert!(buffer.is_dirty());
7541 assert_eq!(
7542 buffer.text(),
7543 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7544 )
7545 });
7546 buffer_2.update(cx, |buffer, _| {
7547 assert!(buffer.is_dirty());
7548 assert_eq!(
7549 buffer.text(),
7550 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7551 )
7552 });
7553 buffer_3.update(cx, |buffer, _| {
7554 assert!(!buffer.is_dirty());
7555 assert_eq!(buffer.text(), sample_text_3,)
7556 });
7557 cx.executor().run_until_parked();
7558
7559 cx.executor().start_waiting();
7560 let save = multi_buffer_editor
7561 .update_in(cx, |editor, window, cx| {
7562 editor.save(true, project.clone(), window, cx)
7563 })
7564 .unwrap();
7565
7566 let fake_server = fake_servers.next().await.unwrap();
7567 fake_server
7568 .server
7569 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7570 Ok(Some(vec![lsp::TextEdit::new(
7571 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7572 format!("[{} formatted]", params.text_document.uri),
7573 )]))
7574 })
7575 .detach();
7576 save.await;
7577
7578 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7579 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7580 assert_eq!(
7581 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7582 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}"),
7583 );
7584 buffer_1.update(cx, |buffer, _| {
7585 assert!(!buffer.is_dirty());
7586 assert_eq!(
7587 buffer.text(),
7588 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7589 )
7590 });
7591 buffer_2.update(cx, |buffer, _| {
7592 assert!(!buffer.is_dirty());
7593 assert_eq!(
7594 buffer.text(),
7595 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7596 )
7597 });
7598 buffer_3.update(cx, |buffer, _| {
7599 assert!(!buffer.is_dirty());
7600 assert_eq!(buffer.text(), sample_text_3,)
7601 });
7602}
7603
7604#[gpui::test]
7605async fn test_range_format_during_save(cx: &mut TestAppContext) {
7606 init_test(cx, |_| {});
7607
7608 let fs = FakeFs::new(cx.executor());
7609 fs.insert_file(path!("/file.rs"), Default::default()).await;
7610
7611 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7612
7613 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7614 language_registry.add(rust_lang());
7615 let mut fake_servers = language_registry.register_fake_lsp(
7616 "Rust",
7617 FakeLspAdapter {
7618 capabilities: lsp::ServerCapabilities {
7619 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7620 ..Default::default()
7621 },
7622 ..Default::default()
7623 },
7624 );
7625
7626 let buffer = project
7627 .update(cx, |project, cx| {
7628 project.open_local_buffer(path!("/file.rs"), cx)
7629 })
7630 .await
7631 .unwrap();
7632
7633 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7634 let (editor, cx) = cx.add_window_view(|window, cx| {
7635 build_editor_with_project(project.clone(), buffer, window, cx)
7636 });
7637 editor.update_in(cx, |editor, window, cx| {
7638 editor.set_text("one\ntwo\nthree\n", window, cx)
7639 });
7640 assert!(cx.read(|cx| editor.is_dirty(cx)));
7641
7642 cx.executor().start_waiting();
7643 let fake_server = fake_servers.next().await.unwrap();
7644
7645 let save = editor
7646 .update_in(cx, |editor, window, cx| {
7647 editor.save(true, project.clone(), window, cx)
7648 })
7649 .unwrap();
7650 fake_server
7651 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7652 assert_eq!(
7653 params.text_document.uri,
7654 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7655 );
7656 assert_eq!(params.options.tab_size, 4);
7657 Ok(Some(vec![lsp::TextEdit::new(
7658 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7659 ", ".to_string(),
7660 )]))
7661 })
7662 .next()
7663 .await;
7664 cx.executor().start_waiting();
7665 save.await;
7666 assert_eq!(
7667 editor.update(cx, |editor, cx| editor.text(cx)),
7668 "one, two\nthree\n"
7669 );
7670 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7671
7672 editor.update_in(cx, |editor, window, cx| {
7673 editor.set_text("one\ntwo\nthree\n", window, cx)
7674 });
7675 assert!(cx.read(|cx| editor.is_dirty(cx)));
7676
7677 // Ensure we can still save even if formatting hangs.
7678 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7679 move |params, _| async move {
7680 assert_eq!(
7681 params.text_document.uri,
7682 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7683 );
7684 futures::future::pending::<()>().await;
7685 unreachable!()
7686 },
7687 );
7688 let save = editor
7689 .update_in(cx, |editor, window, cx| {
7690 editor.save(true, project.clone(), window, cx)
7691 })
7692 .unwrap();
7693 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7694 cx.executor().start_waiting();
7695 save.await;
7696 assert_eq!(
7697 editor.update(cx, |editor, cx| editor.text(cx)),
7698 "one\ntwo\nthree\n"
7699 );
7700 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7701
7702 // For non-dirty buffer, no formatting request should be sent
7703 let save = editor
7704 .update_in(cx, |editor, window, cx| {
7705 editor.save(true, project.clone(), window, cx)
7706 })
7707 .unwrap();
7708 let _pending_format_request = fake_server
7709 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7710 panic!("Should not be invoked on non-dirty buffer");
7711 })
7712 .next();
7713 cx.executor().start_waiting();
7714 save.await;
7715
7716 // Set Rust language override and assert overridden tabsize is sent to language server
7717 update_test_language_settings(cx, |settings| {
7718 settings.languages.insert(
7719 "Rust".into(),
7720 LanguageSettingsContent {
7721 tab_size: NonZeroU32::new(8),
7722 ..Default::default()
7723 },
7724 );
7725 });
7726
7727 editor.update_in(cx, |editor, window, cx| {
7728 editor.set_text("somehting_new\n", window, cx)
7729 });
7730 assert!(cx.read(|cx| editor.is_dirty(cx)));
7731 let save = editor
7732 .update_in(cx, |editor, window, cx| {
7733 editor.save(true, project.clone(), window, cx)
7734 })
7735 .unwrap();
7736 fake_server
7737 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7738 assert_eq!(
7739 params.text_document.uri,
7740 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7741 );
7742 assert_eq!(params.options.tab_size, 8);
7743 Ok(Some(vec![]))
7744 })
7745 .next()
7746 .await;
7747 cx.executor().start_waiting();
7748 save.await;
7749}
7750
7751#[gpui::test]
7752async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7753 init_test(cx, |settings| {
7754 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7755 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7756 ))
7757 });
7758
7759 let fs = FakeFs::new(cx.executor());
7760 fs.insert_file(path!("/file.rs"), Default::default()).await;
7761
7762 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7763
7764 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7765 language_registry.add(Arc::new(Language::new(
7766 LanguageConfig {
7767 name: "Rust".into(),
7768 matcher: LanguageMatcher {
7769 path_suffixes: vec!["rs".to_string()],
7770 ..Default::default()
7771 },
7772 ..LanguageConfig::default()
7773 },
7774 Some(tree_sitter_rust::LANGUAGE.into()),
7775 )));
7776 update_test_language_settings(cx, |settings| {
7777 // Enable Prettier formatting for the same buffer, and ensure
7778 // LSP is called instead of Prettier.
7779 settings.defaults.prettier = Some(PrettierSettings {
7780 allowed: true,
7781 ..PrettierSettings::default()
7782 });
7783 });
7784 let mut fake_servers = language_registry.register_fake_lsp(
7785 "Rust",
7786 FakeLspAdapter {
7787 capabilities: lsp::ServerCapabilities {
7788 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7789 ..Default::default()
7790 },
7791 ..Default::default()
7792 },
7793 );
7794
7795 let buffer = project
7796 .update(cx, |project, cx| {
7797 project.open_local_buffer(path!("/file.rs"), cx)
7798 })
7799 .await
7800 .unwrap();
7801
7802 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7803 let (editor, cx) = cx.add_window_view(|window, cx| {
7804 build_editor_with_project(project.clone(), buffer, window, cx)
7805 });
7806 editor.update_in(cx, |editor, window, cx| {
7807 editor.set_text("one\ntwo\nthree\n", window, cx)
7808 });
7809
7810 cx.executor().start_waiting();
7811 let fake_server = fake_servers.next().await.unwrap();
7812
7813 let format = editor
7814 .update_in(cx, |editor, window, cx| {
7815 editor.perform_format(
7816 project.clone(),
7817 FormatTrigger::Manual,
7818 FormatTarget::Buffers,
7819 window,
7820 cx,
7821 )
7822 })
7823 .unwrap();
7824 fake_server
7825 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7826 assert_eq!(
7827 params.text_document.uri,
7828 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7829 );
7830 assert_eq!(params.options.tab_size, 4);
7831 Ok(Some(vec![lsp::TextEdit::new(
7832 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7833 ", ".to_string(),
7834 )]))
7835 })
7836 .next()
7837 .await;
7838 cx.executor().start_waiting();
7839 format.await;
7840 assert_eq!(
7841 editor.update(cx, |editor, cx| editor.text(cx)),
7842 "one, two\nthree\n"
7843 );
7844
7845 editor.update_in(cx, |editor, window, cx| {
7846 editor.set_text("one\ntwo\nthree\n", window, cx)
7847 });
7848 // Ensure we don't lock if formatting hangs.
7849 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7850 assert_eq!(
7851 params.text_document.uri,
7852 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7853 );
7854 futures::future::pending::<()>().await;
7855 unreachable!()
7856 });
7857 let format = editor
7858 .update_in(cx, |editor, window, cx| {
7859 editor.perform_format(
7860 project,
7861 FormatTrigger::Manual,
7862 FormatTarget::Buffers,
7863 window,
7864 cx,
7865 )
7866 })
7867 .unwrap();
7868 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7869 cx.executor().start_waiting();
7870 format.await;
7871 assert_eq!(
7872 editor.update(cx, |editor, cx| editor.text(cx)),
7873 "one\ntwo\nthree\n"
7874 );
7875}
7876
7877#[gpui::test]
7878async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
7879 init_test(cx, |_| {});
7880
7881 let mut cx = EditorLspTestContext::new_rust(
7882 lsp::ServerCapabilities {
7883 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7884 ..Default::default()
7885 },
7886 cx,
7887 )
7888 .await;
7889
7890 cx.set_state(indoc! {"
7891 one.twoˇ
7892 "});
7893
7894 // The format request takes a long time. When it completes, it inserts
7895 // a newline and an indent before the `.`
7896 cx.lsp
7897 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7898 let executor = cx.background_executor().clone();
7899 async move {
7900 executor.timer(Duration::from_millis(100)).await;
7901 Ok(Some(vec![lsp::TextEdit {
7902 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7903 new_text: "\n ".into(),
7904 }]))
7905 }
7906 });
7907
7908 // Submit a format request.
7909 let format_1 = cx
7910 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7911 .unwrap();
7912 cx.executor().run_until_parked();
7913
7914 // Submit a second format request.
7915 let format_2 = cx
7916 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7917 .unwrap();
7918 cx.executor().run_until_parked();
7919
7920 // Wait for both format requests to complete
7921 cx.executor().advance_clock(Duration::from_millis(200));
7922 cx.executor().start_waiting();
7923 format_1.await.unwrap();
7924 cx.executor().start_waiting();
7925 format_2.await.unwrap();
7926
7927 // The formatting edits only happens once.
7928 cx.assert_editor_state(indoc! {"
7929 one
7930 .twoˇ
7931 "});
7932}
7933
7934#[gpui::test]
7935async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
7936 init_test(cx, |settings| {
7937 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7938 });
7939
7940 let mut cx = EditorLspTestContext::new_rust(
7941 lsp::ServerCapabilities {
7942 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7943 ..Default::default()
7944 },
7945 cx,
7946 )
7947 .await;
7948
7949 // Set up a buffer white some trailing whitespace and no trailing newline.
7950 cx.set_state(
7951 &[
7952 "one ", //
7953 "twoˇ", //
7954 "three ", //
7955 "four", //
7956 ]
7957 .join("\n"),
7958 );
7959
7960 // Submit a format request.
7961 let format = cx
7962 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7963 .unwrap();
7964
7965 // Record which buffer changes have been sent to the language server
7966 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7967 cx.lsp
7968 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7969 let buffer_changes = buffer_changes.clone();
7970 move |params, _| {
7971 buffer_changes.lock().extend(
7972 params
7973 .content_changes
7974 .into_iter()
7975 .map(|e| (e.range.unwrap(), e.text)),
7976 );
7977 }
7978 });
7979
7980 // Handle formatting requests to the language server.
7981 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7982 let buffer_changes = buffer_changes.clone();
7983 move |_, _| {
7984 // When formatting is requested, trailing whitespace has already been stripped,
7985 // and the trailing newline has already been added.
7986 assert_eq!(
7987 &buffer_changes.lock()[1..],
7988 &[
7989 (
7990 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7991 "".into()
7992 ),
7993 (
7994 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7995 "".into()
7996 ),
7997 (
7998 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7999 "\n".into()
8000 ),
8001 ]
8002 );
8003
8004 // Insert blank lines between each line of the buffer.
8005 async move {
8006 Ok(Some(vec![
8007 lsp::TextEdit {
8008 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8009 new_text: "\n".into(),
8010 },
8011 lsp::TextEdit {
8012 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8013 new_text: "\n".into(),
8014 },
8015 ]))
8016 }
8017 }
8018 });
8019
8020 // After formatting the buffer, the trailing whitespace is stripped,
8021 // a newline is appended, and the edits provided by the language server
8022 // have been applied.
8023 format.await.unwrap();
8024 cx.assert_editor_state(
8025 &[
8026 "one", //
8027 "", //
8028 "twoˇ", //
8029 "", //
8030 "three", //
8031 "four", //
8032 "", //
8033 ]
8034 .join("\n"),
8035 );
8036
8037 // Undoing the formatting undoes the trailing whitespace removal, the
8038 // trailing newline, and the LSP edits.
8039 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8040 cx.assert_editor_state(
8041 &[
8042 "one ", //
8043 "twoˇ", //
8044 "three ", //
8045 "four", //
8046 ]
8047 .join("\n"),
8048 );
8049}
8050
8051#[gpui::test]
8052async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8053 cx: &mut TestAppContext,
8054) {
8055 init_test(cx, |_| {});
8056
8057 cx.update(|cx| {
8058 cx.update_global::<SettingsStore, _>(|settings, cx| {
8059 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8060 settings.auto_signature_help = Some(true);
8061 });
8062 });
8063 });
8064
8065 let mut cx = EditorLspTestContext::new_rust(
8066 lsp::ServerCapabilities {
8067 signature_help_provider: Some(lsp::SignatureHelpOptions {
8068 ..Default::default()
8069 }),
8070 ..Default::default()
8071 },
8072 cx,
8073 )
8074 .await;
8075
8076 let language = Language::new(
8077 LanguageConfig {
8078 name: "Rust".into(),
8079 brackets: BracketPairConfig {
8080 pairs: vec![
8081 BracketPair {
8082 start: "{".to_string(),
8083 end: "}".to_string(),
8084 close: true,
8085 surround: true,
8086 newline: true,
8087 },
8088 BracketPair {
8089 start: "(".to_string(),
8090 end: ")".to_string(),
8091 close: true,
8092 surround: true,
8093 newline: true,
8094 },
8095 BracketPair {
8096 start: "/*".to_string(),
8097 end: " */".to_string(),
8098 close: true,
8099 surround: true,
8100 newline: true,
8101 },
8102 BracketPair {
8103 start: "[".to_string(),
8104 end: "]".to_string(),
8105 close: false,
8106 surround: false,
8107 newline: true,
8108 },
8109 BracketPair {
8110 start: "\"".to_string(),
8111 end: "\"".to_string(),
8112 close: true,
8113 surround: true,
8114 newline: false,
8115 },
8116 BracketPair {
8117 start: "<".to_string(),
8118 end: ">".to_string(),
8119 close: false,
8120 surround: true,
8121 newline: true,
8122 },
8123 ],
8124 ..Default::default()
8125 },
8126 autoclose_before: "})]".to_string(),
8127 ..Default::default()
8128 },
8129 Some(tree_sitter_rust::LANGUAGE.into()),
8130 );
8131 let language = Arc::new(language);
8132
8133 cx.language_registry().add(language.clone());
8134 cx.update_buffer(|buffer, cx| {
8135 buffer.set_language(Some(language), cx);
8136 });
8137
8138 cx.set_state(
8139 &r#"
8140 fn main() {
8141 sampleˇ
8142 }
8143 "#
8144 .unindent(),
8145 );
8146
8147 cx.update_editor(|editor, window, cx| {
8148 editor.handle_input("(", window, cx);
8149 });
8150 cx.assert_editor_state(
8151 &"
8152 fn main() {
8153 sample(ˇ)
8154 }
8155 "
8156 .unindent(),
8157 );
8158
8159 let mocked_response = lsp::SignatureHelp {
8160 signatures: vec![lsp::SignatureInformation {
8161 label: "fn sample(param1: u8, param2: u8)".to_string(),
8162 documentation: None,
8163 parameters: Some(vec![
8164 lsp::ParameterInformation {
8165 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8166 documentation: None,
8167 },
8168 lsp::ParameterInformation {
8169 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8170 documentation: None,
8171 },
8172 ]),
8173 active_parameter: None,
8174 }],
8175 active_signature: Some(0),
8176 active_parameter: Some(0),
8177 };
8178 handle_signature_help_request(&mut cx, mocked_response).await;
8179
8180 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8181 .await;
8182
8183 cx.editor(|editor, _, _| {
8184 let signature_help_state = editor.signature_help_state.popover().cloned();
8185 assert_eq!(
8186 signature_help_state.unwrap().label,
8187 "param1: u8, param2: u8"
8188 );
8189 });
8190}
8191
8192#[gpui::test]
8193async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8194 init_test(cx, |_| {});
8195
8196 cx.update(|cx| {
8197 cx.update_global::<SettingsStore, _>(|settings, cx| {
8198 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8199 settings.auto_signature_help = Some(false);
8200 settings.show_signature_help_after_edits = Some(false);
8201 });
8202 });
8203 });
8204
8205 let mut cx = EditorLspTestContext::new_rust(
8206 lsp::ServerCapabilities {
8207 signature_help_provider: Some(lsp::SignatureHelpOptions {
8208 ..Default::default()
8209 }),
8210 ..Default::default()
8211 },
8212 cx,
8213 )
8214 .await;
8215
8216 let language = Language::new(
8217 LanguageConfig {
8218 name: "Rust".into(),
8219 brackets: BracketPairConfig {
8220 pairs: vec![
8221 BracketPair {
8222 start: "{".to_string(),
8223 end: "}".to_string(),
8224 close: true,
8225 surround: true,
8226 newline: true,
8227 },
8228 BracketPair {
8229 start: "(".to_string(),
8230 end: ")".to_string(),
8231 close: true,
8232 surround: true,
8233 newline: true,
8234 },
8235 BracketPair {
8236 start: "/*".to_string(),
8237 end: " */".to_string(),
8238 close: true,
8239 surround: true,
8240 newline: true,
8241 },
8242 BracketPair {
8243 start: "[".to_string(),
8244 end: "]".to_string(),
8245 close: false,
8246 surround: false,
8247 newline: true,
8248 },
8249 BracketPair {
8250 start: "\"".to_string(),
8251 end: "\"".to_string(),
8252 close: true,
8253 surround: true,
8254 newline: false,
8255 },
8256 BracketPair {
8257 start: "<".to_string(),
8258 end: ">".to_string(),
8259 close: false,
8260 surround: true,
8261 newline: true,
8262 },
8263 ],
8264 ..Default::default()
8265 },
8266 autoclose_before: "})]".to_string(),
8267 ..Default::default()
8268 },
8269 Some(tree_sitter_rust::LANGUAGE.into()),
8270 );
8271 let language = Arc::new(language);
8272
8273 cx.language_registry().add(language.clone());
8274 cx.update_buffer(|buffer, cx| {
8275 buffer.set_language(Some(language), cx);
8276 });
8277
8278 // Ensure that signature_help is not called when no signature help is enabled.
8279 cx.set_state(
8280 &r#"
8281 fn main() {
8282 sampleˇ
8283 }
8284 "#
8285 .unindent(),
8286 );
8287 cx.update_editor(|editor, window, cx| {
8288 editor.handle_input("(", window, cx);
8289 });
8290 cx.assert_editor_state(
8291 &"
8292 fn main() {
8293 sample(ˇ)
8294 }
8295 "
8296 .unindent(),
8297 );
8298 cx.editor(|editor, _, _| {
8299 assert!(editor.signature_help_state.task().is_none());
8300 });
8301
8302 let mocked_response = lsp::SignatureHelp {
8303 signatures: vec![lsp::SignatureInformation {
8304 label: "fn sample(param1: u8, param2: u8)".to_string(),
8305 documentation: None,
8306 parameters: Some(vec![
8307 lsp::ParameterInformation {
8308 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8309 documentation: None,
8310 },
8311 lsp::ParameterInformation {
8312 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8313 documentation: None,
8314 },
8315 ]),
8316 active_parameter: None,
8317 }],
8318 active_signature: Some(0),
8319 active_parameter: Some(0),
8320 };
8321
8322 // Ensure that signature_help is called when enabled afte edits
8323 cx.update(|_, cx| {
8324 cx.update_global::<SettingsStore, _>(|settings, cx| {
8325 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8326 settings.auto_signature_help = Some(false);
8327 settings.show_signature_help_after_edits = Some(true);
8328 });
8329 });
8330 });
8331 cx.set_state(
8332 &r#"
8333 fn main() {
8334 sampleˇ
8335 }
8336 "#
8337 .unindent(),
8338 );
8339 cx.update_editor(|editor, window, cx| {
8340 editor.handle_input("(", window, cx);
8341 });
8342 cx.assert_editor_state(
8343 &"
8344 fn main() {
8345 sample(ˇ)
8346 }
8347 "
8348 .unindent(),
8349 );
8350 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8351 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8352 .await;
8353 cx.update_editor(|editor, _, _| {
8354 let signature_help_state = editor.signature_help_state.popover().cloned();
8355 assert!(signature_help_state.is_some());
8356 assert_eq!(
8357 signature_help_state.unwrap().label,
8358 "param1: u8, param2: u8"
8359 );
8360 editor.signature_help_state = SignatureHelpState::default();
8361 });
8362
8363 // Ensure that signature_help is called when auto signature help override is enabled
8364 cx.update(|_, cx| {
8365 cx.update_global::<SettingsStore, _>(|settings, cx| {
8366 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8367 settings.auto_signature_help = Some(true);
8368 settings.show_signature_help_after_edits = Some(false);
8369 });
8370 });
8371 });
8372 cx.set_state(
8373 &r#"
8374 fn main() {
8375 sampleˇ
8376 }
8377 "#
8378 .unindent(),
8379 );
8380 cx.update_editor(|editor, window, cx| {
8381 editor.handle_input("(", window, cx);
8382 });
8383 cx.assert_editor_state(
8384 &"
8385 fn main() {
8386 sample(ˇ)
8387 }
8388 "
8389 .unindent(),
8390 );
8391 handle_signature_help_request(&mut cx, mocked_response).await;
8392 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8393 .await;
8394 cx.editor(|editor, _, _| {
8395 let signature_help_state = editor.signature_help_state.popover().cloned();
8396 assert!(signature_help_state.is_some());
8397 assert_eq!(
8398 signature_help_state.unwrap().label,
8399 "param1: u8, param2: u8"
8400 );
8401 });
8402}
8403
8404#[gpui::test]
8405async fn test_signature_help(cx: &mut TestAppContext) {
8406 init_test(cx, |_| {});
8407 cx.update(|cx| {
8408 cx.update_global::<SettingsStore, _>(|settings, cx| {
8409 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8410 settings.auto_signature_help = Some(true);
8411 });
8412 });
8413 });
8414
8415 let mut cx = EditorLspTestContext::new_rust(
8416 lsp::ServerCapabilities {
8417 signature_help_provider: Some(lsp::SignatureHelpOptions {
8418 ..Default::default()
8419 }),
8420 ..Default::default()
8421 },
8422 cx,
8423 )
8424 .await;
8425
8426 // A test that directly calls `show_signature_help`
8427 cx.update_editor(|editor, window, cx| {
8428 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8429 });
8430
8431 let mocked_response = lsp::SignatureHelp {
8432 signatures: vec![lsp::SignatureInformation {
8433 label: "fn sample(param1: u8, param2: u8)".to_string(),
8434 documentation: None,
8435 parameters: Some(vec![
8436 lsp::ParameterInformation {
8437 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8438 documentation: None,
8439 },
8440 lsp::ParameterInformation {
8441 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8442 documentation: None,
8443 },
8444 ]),
8445 active_parameter: None,
8446 }],
8447 active_signature: Some(0),
8448 active_parameter: Some(0),
8449 };
8450 handle_signature_help_request(&mut cx, mocked_response).await;
8451
8452 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8453 .await;
8454
8455 cx.editor(|editor, _, _| {
8456 let signature_help_state = editor.signature_help_state.popover().cloned();
8457 assert!(signature_help_state.is_some());
8458 assert_eq!(
8459 signature_help_state.unwrap().label,
8460 "param1: u8, param2: u8"
8461 );
8462 });
8463
8464 // When exiting outside from inside the brackets, `signature_help` is closed.
8465 cx.set_state(indoc! {"
8466 fn main() {
8467 sample(ˇ);
8468 }
8469
8470 fn sample(param1: u8, param2: u8) {}
8471 "});
8472
8473 cx.update_editor(|editor, window, cx| {
8474 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8475 });
8476
8477 let mocked_response = lsp::SignatureHelp {
8478 signatures: Vec::new(),
8479 active_signature: None,
8480 active_parameter: None,
8481 };
8482 handle_signature_help_request(&mut cx, mocked_response).await;
8483
8484 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8485 .await;
8486
8487 cx.editor(|editor, _, _| {
8488 assert!(!editor.signature_help_state.is_shown());
8489 });
8490
8491 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8492 cx.set_state(indoc! {"
8493 fn main() {
8494 sample(ˇ);
8495 }
8496
8497 fn sample(param1: u8, param2: u8) {}
8498 "});
8499
8500 let mocked_response = lsp::SignatureHelp {
8501 signatures: vec![lsp::SignatureInformation {
8502 label: "fn sample(param1: u8, param2: u8)".to_string(),
8503 documentation: None,
8504 parameters: Some(vec![
8505 lsp::ParameterInformation {
8506 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8507 documentation: None,
8508 },
8509 lsp::ParameterInformation {
8510 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8511 documentation: None,
8512 },
8513 ]),
8514 active_parameter: None,
8515 }],
8516 active_signature: Some(0),
8517 active_parameter: Some(0),
8518 };
8519 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8520 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8521 .await;
8522 cx.editor(|editor, _, _| {
8523 assert!(editor.signature_help_state.is_shown());
8524 });
8525
8526 // Restore the popover with more parameter input
8527 cx.set_state(indoc! {"
8528 fn main() {
8529 sample(param1, param2ˇ);
8530 }
8531
8532 fn sample(param1: u8, param2: u8) {}
8533 "});
8534
8535 let mocked_response = lsp::SignatureHelp {
8536 signatures: vec![lsp::SignatureInformation {
8537 label: "fn sample(param1: u8, param2: u8)".to_string(),
8538 documentation: None,
8539 parameters: Some(vec![
8540 lsp::ParameterInformation {
8541 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8542 documentation: None,
8543 },
8544 lsp::ParameterInformation {
8545 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8546 documentation: None,
8547 },
8548 ]),
8549 active_parameter: None,
8550 }],
8551 active_signature: Some(0),
8552 active_parameter: Some(1),
8553 };
8554 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8555 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8556 .await;
8557
8558 // When selecting a range, the popover is gone.
8559 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8560 cx.update_editor(|editor, window, cx| {
8561 editor.change_selections(None, window, cx, |s| {
8562 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8563 })
8564 });
8565 cx.assert_editor_state(indoc! {"
8566 fn main() {
8567 sample(param1, «ˇparam2»);
8568 }
8569
8570 fn sample(param1: u8, param2: u8) {}
8571 "});
8572 cx.editor(|editor, _, _| {
8573 assert!(!editor.signature_help_state.is_shown());
8574 });
8575
8576 // When unselecting again, the popover is back if within the brackets.
8577 cx.update_editor(|editor, window, cx| {
8578 editor.change_selections(None, window, cx, |s| {
8579 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8580 })
8581 });
8582 cx.assert_editor_state(indoc! {"
8583 fn main() {
8584 sample(param1, ˇparam2);
8585 }
8586
8587 fn sample(param1: u8, param2: u8) {}
8588 "});
8589 handle_signature_help_request(&mut cx, mocked_response).await;
8590 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8591 .await;
8592 cx.editor(|editor, _, _| {
8593 assert!(editor.signature_help_state.is_shown());
8594 });
8595
8596 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8597 cx.update_editor(|editor, window, cx| {
8598 editor.change_selections(None, window, cx, |s| {
8599 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8600 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8601 })
8602 });
8603 cx.assert_editor_state(indoc! {"
8604 fn main() {
8605 sample(param1, ˇparam2);
8606 }
8607
8608 fn sample(param1: u8, param2: u8) {}
8609 "});
8610
8611 let mocked_response = lsp::SignatureHelp {
8612 signatures: vec![lsp::SignatureInformation {
8613 label: "fn sample(param1: u8, param2: u8)".to_string(),
8614 documentation: None,
8615 parameters: Some(vec![
8616 lsp::ParameterInformation {
8617 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8618 documentation: None,
8619 },
8620 lsp::ParameterInformation {
8621 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8622 documentation: None,
8623 },
8624 ]),
8625 active_parameter: None,
8626 }],
8627 active_signature: Some(0),
8628 active_parameter: Some(1),
8629 };
8630 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8631 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8632 .await;
8633 cx.update_editor(|editor, _, cx| {
8634 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8635 });
8636 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8637 .await;
8638 cx.update_editor(|editor, window, cx| {
8639 editor.change_selections(None, window, cx, |s| {
8640 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8641 })
8642 });
8643 cx.assert_editor_state(indoc! {"
8644 fn main() {
8645 sample(param1, «ˇparam2»);
8646 }
8647
8648 fn sample(param1: u8, param2: u8) {}
8649 "});
8650 cx.update_editor(|editor, window, cx| {
8651 editor.change_selections(None, window, cx, |s| {
8652 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8653 })
8654 });
8655 cx.assert_editor_state(indoc! {"
8656 fn main() {
8657 sample(param1, ˇparam2);
8658 }
8659
8660 fn sample(param1: u8, param2: u8) {}
8661 "});
8662 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8663 .await;
8664}
8665
8666#[gpui::test]
8667async fn test_completion(cx: &mut TestAppContext) {
8668 init_test(cx, |_| {});
8669
8670 let mut cx = EditorLspTestContext::new_rust(
8671 lsp::ServerCapabilities {
8672 completion_provider: Some(lsp::CompletionOptions {
8673 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8674 resolve_provider: Some(true),
8675 ..Default::default()
8676 }),
8677 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8678 ..Default::default()
8679 },
8680 cx,
8681 )
8682 .await;
8683 let counter = Arc::new(AtomicUsize::new(0));
8684
8685 cx.set_state(indoc! {"
8686 oneˇ
8687 two
8688 three
8689 "});
8690 cx.simulate_keystroke(".");
8691 handle_completion_request(
8692 &mut cx,
8693 indoc! {"
8694 one.|<>
8695 two
8696 three
8697 "},
8698 vec!["first_completion", "second_completion"],
8699 counter.clone(),
8700 )
8701 .await;
8702 cx.condition(|editor, _| editor.context_menu_visible())
8703 .await;
8704 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8705
8706 let _handler = handle_signature_help_request(
8707 &mut cx,
8708 lsp::SignatureHelp {
8709 signatures: vec![lsp::SignatureInformation {
8710 label: "test signature".to_string(),
8711 documentation: None,
8712 parameters: Some(vec![lsp::ParameterInformation {
8713 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8714 documentation: None,
8715 }]),
8716 active_parameter: None,
8717 }],
8718 active_signature: None,
8719 active_parameter: None,
8720 },
8721 );
8722 cx.update_editor(|editor, window, cx| {
8723 assert!(
8724 !editor.signature_help_state.is_shown(),
8725 "No signature help was called for"
8726 );
8727 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8728 });
8729 cx.run_until_parked();
8730 cx.update_editor(|editor, _, _| {
8731 assert!(
8732 !editor.signature_help_state.is_shown(),
8733 "No signature help should be shown when completions menu is open"
8734 );
8735 });
8736
8737 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8738 editor.context_menu_next(&Default::default(), window, cx);
8739 editor
8740 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8741 .unwrap()
8742 });
8743 cx.assert_editor_state(indoc! {"
8744 one.second_completionˇ
8745 two
8746 three
8747 "});
8748
8749 handle_resolve_completion_request(
8750 &mut cx,
8751 Some(vec![
8752 (
8753 //This overlaps with the primary completion edit which is
8754 //misbehavior from the LSP spec, test that we filter it out
8755 indoc! {"
8756 one.second_ˇcompletion
8757 two
8758 threeˇ
8759 "},
8760 "overlapping additional edit",
8761 ),
8762 (
8763 indoc! {"
8764 one.second_completion
8765 two
8766 threeˇ
8767 "},
8768 "\nadditional edit",
8769 ),
8770 ]),
8771 )
8772 .await;
8773 apply_additional_edits.await.unwrap();
8774 cx.assert_editor_state(indoc! {"
8775 one.second_completionˇ
8776 two
8777 three
8778 additional edit
8779 "});
8780
8781 cx.set_state(indoc! {"
8782 one.second_completion
8783 twoˇ
8784 threeˇ
8785 additional edit
8786 "});
8787 cx.simulate_keystroke(" ");
8788 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8789 cx.simulate_keystroke("s");
8790 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8791
8792 cx.assert_editor_state(indoc! {"
8793 one.second_completion
8794 two sˇ
8795 three sˇ
8796 additional edit
8797 "});
8798 handle_completion_request(
8799 &mut cx,
8800 indoc! {"
8801 one.second_completion
8802 two s
8803 three <s|>
8804 additional edit
8805 "},
8806 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8807 counter.clone(),
8808 )
8809 .await;
8810 cx.condition(|editor, _| editor.context_menu_visible())
8811 .await;
8812 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8813
8814 cx.simulate_keystroke("i");
8815
8816 handle_completion_request(
8817 &mut cx,
8818 indoc! {"
8819 one.second_completion
8820 two si
8821 three <si|>
8822 additional edit
8823 "},
8824 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8825 counter.clone(),
8826 )
8827 .await;
8828 cx.condition(|editor, _| editor.context_menu_visible())
8829 .await;
8830 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8831
8832 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8833 editor
8834 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8835 .unwrap()
8836 });
8837 cx.assert_editor_state(indoc! {"
8838 one.second_completion
8839 two sixth_completionˇ
8840 three sixth_completionˇ
8841 additional edit
8842 "});
8843
8844 apply_additional_edits.await.unwrap();
8845
8846 update_test_language_settings(&mut cx, |settings| {
8847 settings.defaults.show_completions_on_input = Some(false);
8848 });
8849 cx.set_state("editorˇ");
8850 cx.simulate_keystroke(".");
8851 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8852 cx.simulate_keystroke("c");
8853 cx.simulate_keystroke("l");
8854 cx.simulate_keystroke("o");
8855 cx.assert_editor_state("editor.cloˇ");
8856 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8857 cx.update_editor(|editor, window, cx| {
8858 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8859 });
8860 handle_completion_request(
8861 &mut cx,
8862 "editor.<clo|>",
8863 vec!["close", "clobber"],
8864 counter.clone(),
8865 )
8866 .await;
8867 cx.condition(|editor, _| editor.context_menu_visible())
8868 .await;
8869 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8870
8871 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8872 editor
8873 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8874 .unwrap()
8875 });
8876 cx.assert_editor_state("editor.closeˇ");
8877 handle_resolve_completion_request(&mut cx, None).await;
8878 apply_additional_edits.await.unwrap();
8879}
8880
8881#[gpui::test]
8882async fn test_multiline_completion(cx: &mut TestAppContext) {
8883 init_test(cx, |_| {});
8884
8885 let fs = FakeFs::new(cx.executor());
8886 fs.insert_tree(
8887 path!("/a"),
8888 json!({
8889 "main.ts": "a",
8890 }),
8891 )
8892 .await;
8893
8894 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8895 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8896 let typescript_language = Arc::new(Language::new(
8897 LanguageConfig {
8898 name: "TypeScript".into(),
8899 matcher: LanguageMatcher {
8900 path_suffixes: vec!["ts".to_string()],
8901 ..LanguageMatcher::default()
8902 },
8903 line_comments: vec!["// ".into()],
8904 ..LanguageConfig::default()
8905 },
8906 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8907 ));
8908 language_registry.add(typescript_language.clone());
8909 let mut fake_servers = language_registry.register_fake_lsp(
8910 "TypeScript",
8911 FakeLspAdapter {
8912 capabilities: lsp::ServerCapabilities {
8913 completion_provider: Some(lsp::CompletionOptions {
8914 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8915 ..lsp::CompletionOptions::default()
8916 }),
8917 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8918 ..lsp::ServerCapabilities::default()
8919 },
8920 // Emulate vtsls label generation
8921 label_for_completion: Some(Box::new(|item, _| {
8922 let text = if let Some(description) = item
8923 .label_details
8924 .as_ref()
8925 .and_then(|label_details| label_details.description.as_ref())
8926 {
8927 format!("{} {}", item.label, description)
8928 } else if let Some(detail) = &item.detail {
8929 format!("{} {}", item.label, detail)
8930 } else {
8931 item.label.clone()
8932 };
8933 let len = text.len();
8934 Some(language::CodeLabel {
8935 text,
8936 runs: Vec::new(),
8937 filter_range: 0..len,
8938 })
8939 })),
8940 ..FakeLspAdapter::default()
8941 },
8942 );
8943 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8944 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8945 let worktree_id = workspace
8946 .update(cx, |workspace, _window, cx| {
8947 workspace.project().update(cx, |project, cx| {
8948 project.worktrees(cx).next().unwrap().read(cx).id()
8949 })
8950 })
8951 .unwrap();
8952 let _buffer = project
8953 .update(cx, |project, cx| {
8954 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
8955 })
8956 .await
8957 .unwrap();
8958 let editor = workspace
8959 .update(cx, |workspace, window, cx| {
8960 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8961 })
8962 .unwrap()
8963 .await
8964 .unwrap()
8965 .downcast::<Editor>()
8966 .unwrap();
8967 let fake_server = fake_servers.next().await.unwrap();
8968
8969 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8970 let multiline_label_2 = "a\nb\nc\n";
8971 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8972 let multiline_description = "d\ne\nf\n";
8973 let multiline_detail_2 = "g\nh\ni\n";
8974
8975 let mut completion_handle =
8976 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8977 Ok(Some(lsp::CompletionResponse::Array(vec![
8978 lsp::CompletionItem {
8979 label: multiline_label.to_string(),
8980 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8981 range: lsp::Range {
8982 start: lsp::Position {
8983 line: params.text_document_position.position.line,
8984 character: params.text_document_position.position.character,
8985 },
8986 end: lsp::Position {
8987 line: params.text_document_position.position.line,
8988 character: params.text_document_position.position.character,
8989 },
8990 },
8991 new_text: "new_text_1".to_string(),
8992 })),
8993 ..lsp::CompletionItem::default()
8994 },
8995 lsp::CompletionItem {
8996 label: "single line label 1".to_string(),
8997 detail: Some(multiline_detail.to_string()),
8998 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8999 range: lsp::Range {
9000 start: lsp::Position {
9001 line: params.text_document_position.position.line,
9002 character: params.text_document_position.position.character,
9003 },
9004 end: lsp::Position {
9005 line: params.text_document_position.position.line,
9006 character: params.text_document_position.position.character,
9007 },
9008 },
9009 new_text: "new_text_2".to_string(),
9010 })),
9011 ..lsp::CompletionItem::default()
9012 },
9013 lsp::CompletionItem {
9014 label: "single line label 2".to_string(),
9015 label_details: Some(lsp::CompletionItemLabelDetails {
9016 description: Some(multiline_description.to_string()),
9017 detail: None,
9018 }),
9019 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9020 range: lsp::Range {
9021 start: lsp::Position {
9022 line: params.text_document_position.position.line,
9023 character: params.text_document_position.position.character,
9024 },
9025 end: lsp::Position {
9026 line: params.text_document_position.position.line,
9027 character: params.text_document_position.position.character,
9028 },
9029 },
9030 new_text: "new_text_2".to_string(),
9031 })),
9032 ..lsp::CompletionItem::default()
9033 },
9034 lsp::CompletionItem {
9035 label: multiline_label_2.to_string(),
9036 detail: Some(multiline_detail_2.to_string()),
9037 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9038 range: lsp::Range {
9039 start: lsp::Position {
9040 line: params.text_document_position.position.line,
9041 character: params.text_document_position.position.character,
9042 },
9043 end: lsp::Position {
9044 line: params.text_document_position.position.line,
9045 character: params.text_document_position.position.character,
9046 },
9047 },
9048 new_text: "new_text_3".to_string(),
9049 })),
9050 ..lsp::CompletionItem::default()
9051 },
9052 lsp::CompletionItem {
9053 label: "Label with many spaces and \t but without newlines".to_string(),
9054 detail: Some(
9055 "Details with many spaces and \t but without newlines".to_string(),
9056 ),
9057 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9058 range: lsp::Range {
9059 start: lsp::Position {
9060 line: params.text_document_position.position.line,
9061 character: params.text_document_position.position.character,
9062 },
9063 end: lsp::Position {
9064 line: params.text_document_position.position.line,
9065 character: params.text_document_position.position.character,
9066 },
9067 },
9068 new_text: "new_text_4".to_string(),
9069 })),
9070 ..lsp::CompletionItem::default()
9071 },
9072 ])))
9073 });
9074
9075 editor.update_in(cx, |editor, window, cx| {
9076 cx.focus_self(window);
9077 editor.move_to_end(&MoveToEnd, window, cx);
9078 editor.handle_input(".", window, cx);
9079 });
9080 cx.run_until_parked();
9081 completion_handle.next().await.unwrap();
9082
9083 editor.update(cx, |editor, _| {
9084 assert!(editor.context_menu_visible());
9085 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9086 {
9087 let completion_labels = menu
9088 .completions
9089 .borrow()
9090 .iter()
9091 .map(|c| c.label.text.clone())
9092 .collect::<Vec<_>>();
9093 assert_eq!(
9094 completion_labels,
9095 &[
9096 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9097 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9098 "single line label 2 d e f ",
9099 "a b c g h i ",
9100 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9101 ],
9102 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9103 );
9104
9105 for completion in menu
9106 .completions
9107 .borrow()
9108 .iter() {
9109 assert_eq!(
9110 completion.label.filter_range,
9111 0..completion.label.text.len(),
9112 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9113 );
9114 }
9115
9116 } else {
9117 panic!("expected completion menu to be open");
9118 }
9119 });
9120}
9121
9122#[gpui::test]
9123async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9124 init_test(cx, |_| {});
9125 let mut cx = EditorLspTestContext::new_rust(
9126 lsp::ServerCapabilities {
9127 completion_provider: Some(lsp::CompletionOptions {
9128 trigger_characters: Some(vec![".".to_string()]),
9129 ..Default::default()
9130 }),
9131 ..Default::default()
9132 },
9133 cx,
9134 )
9135 .await;
9136 cx.lsp
9137 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9138 Ok(Some(lsp::CompletionResponse::Array(vec![
9139 lsp::CompletionItem {
9140 label: "first".into(),
9141 ..Default::default()
9142 },
9143 lsp::CompletionItem {
9144 label: "last".into(),
9145 ..Default::default()
9146 },
9147 ])))
9148 });
9149 cx.set_state("variableˇ");
9150 cx.simulate_keystroke(".");
9151 cx.executor().run_until_parked();
9152
9153 cx.update_editor(|editor, _, _| {
9154 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9155 {
9156 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9157 } else {
9158 panic!("expected completion menu to be open");
9159 }
9160 });
9161
9162 cx.update_editor(|editor, window, cx| {
9163 editor.move_page_down(&MovePageDown::default(), window, cx);
9164 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9165 {
9166 assert!(
9167 menu.selected_item == 1,
9168 "expected PageDown to select the last item from the context menu"
9169 );
9170 } else {
9171 panic!("expected completion menu to stay open after PageDown");
9172 }
9173 });
9174
9175 cx.update_editor(|editor, window, cx| {
9176 editor.move_page_up(&MovePageUp::default(), window, cx);
9177 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9178 {
9179 assert!(
9180 menu.selected_item == 0,
9181 "expected PageUp to select the first item from the context menu"
9182 );
9183 } else {
9184 panic!("expected completion menu to stay open after PageUp");
9185 }
9186 });
9187}
9188
9189#[gpui::test]
9190async fn test_completion_sort(cx: &mut TestAppContext) {
9191 init_test(cx, |_| {});
9192 let mut cx = EditorLspTestContext::new_rust(
9193 lsp::ServerCapabilities {
9194 completion_provider: Some(lsp::CompletionOptions {
9195 trigger_characters: Some(vec![".".to_string()]),
9196 ..Default::default()
9197 }),
9198 ..Default::default()
9199 },
9200 cx,
9201 )
9202 .await;
9203 cx.lsp
9204 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9205 Ok(Some(lsp::CompletionResponse::Array(vec![
9206 lsp::CompletionItem {
9207 label: "Range".into(),
9208 sort_text: Some("a".into()),
9209 ..Default::default()
9210 },
9211 lsp::CompletionItem {
9212 label: "r".into(),
9213 sort_text: Some("b".into()),
9214 ..Default::default()
9215 },
9216 lsp::CompletionItem {
9217 label: "ret".into(),
9218 sort_text: Some("c".into()),
9219 ..Default::default()
9220 },
9221 lsp::CompletionItem {
9222 label: "return".into(),
9223 sort_text: Some("d".into()),
9224 ..Default::default()
9225 },
9226 lsp::CompletionItem {
9227 label: "slice".into(),
9228 sort_text: Some("d".into()),
9229 ..Default::default()
9230 },
9231 ])))
9232 });
9233 cx.set_state("rˇ");
9234 cx.executor().run_until_parked();
9235 cx.update_editor(|editor, window, cx| {
9236 editor.show_completions(
9237 &ShowCompletions {
9238 trigger: Some("r".into()),
9239 },
9240 window,
9241 cx,
9242 );
9243 });
9244 cx.executor().run_until_parked();
9245
9246 cx.update_editor(|editor, _, _| {
9247 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9248 {
9249 assert_eq!(
9250 completion_menu_entries(&menu),
9251 &["r", "ret", "Range", "return"]
9252 );
9253 } else {
9254 panic!("expected completion menu to be open");
9255 }
9256 });
9257}
9258
9259#[gpui::test]
9260async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9261 init_test(cx, |_| {});
9262
9263 let mut cx = EditorLspTestContext::new_rust(
9264 lsp::ServerCapabilities {
9265 completion_provider: Some(lsp::CompletionOptions {
9266 trigger_characters: Some(vec![".".to_string()]),
9267 resolve_provider: Some(true),
9268 ..Default::default()
9269 }),
9270 ..Default::default()
9271 },
9272 cx,
9273 )
9274 .await;
9275
9276 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9277 cx.simulate_keystroke(".");
9278 let completion_item = lsp::CompletionItem {
9279 label: "Some".into(),
9280 kind: Some(lsp::CompletionItemKind::SNIPPET),
9281 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9282 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9283 kind: lsp::MarkupKind::Markdown,
9284 value: "```rust\nSome(2)\n```".to_string(),
9285 })),
9286 deprecated: Some(false),
9287 sort_text: Some("Some".to_string()),
9288 filter_text: Some("Some".to_string()),
9289 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9290 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9291 range: lsp::Range {
9292 start: lsp::Position {
9293 line: 0,
9294 character: 22,
9295 },
9296 end: lsp::Position {
9297 line: 0,
9298 character: 22,
9299 },
9300 },
9301 new_text: "Some(2)".to_string(),
9302 })),
9303 additional_text_edits: Some(vec![lsp::TextEdit {
9304 range: lsp::Range {
9305 start: lsp::Position {
9306 line: 0,
9307 character: 20,
9308 },
9309 end: lsp::Position {
9310 line: 0,
9311 character: 22,
9312 },
9313 },
9314 new_text: "".to_string(),
9315 }]),
9316 ..Default::default()
9317 };
9318
9319 let closure_completion_item = completion_item.clone();
9320 let counter = Arc::new(AtomicUsize::new(0));
9321 let counter_clone = counter.clone();
9322 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9323 let task_completion_item = closure_completion_item.clone();
9324 counter_clone.fetch_add(1, atomic::Ordering::Release);
9325 async move {
9326 Ok(Some(lsp::CompletionResponse::Array(vec![
9327 task_completion_item,
9328 ])))
9329 }
9330 });
9331
9332 cx.condition(|editor, _| editor.context_menu_visible())
9333 .await;
9334 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9335 assert!(request.next().await.is_some());
9336 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9337
9338 cx.simulate_keystroke("S");
9339 cx.simulate_keystroke("o");
9340 cx.simulate_keystroke("m");
9341 cx.condition(|editor, _| editor.context_menu_visible())
9342 .await;
9343 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9344 assert!(request.next().await.is_some());
9345 assert!(request.next().await.is_some());
9346 assert!(request.next().await.is_some());
9347 request.close();
9348 assert!(request.next().await.is_none());
9349 assert_eq!(
9350 counter.load(atomic::Ordering::Acquire),
9351 4,
9352 "With the completions menu open, only one LSP request should happen per input"
9353 );
9354}
9355
9356#[gpui::test]
9357async fn test_toggle_comment(cx: &mut TestAppContext) {
9358 init_test(cx, |_| {});
9359 let mut cx = EditorTestContext::new(cx).await;
9360 let language = Arc::new(Language::new(
9361 LanguageConfig {
9362 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9363 ..Default::default()
9364 },
9365 Some(tree_sitter_rust::LANGUAGE.into()),
9366 ));
9367 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9368
9369 // If multiple selections intersect a line, the line is only toggled once.
9370 cx.set_state(indoc! {"
9371 fn a() {
9372 «//b();
9373 ˇ»// «c();
9374 //ˇ» d();
9375 }
9376 "});
9377
9378 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9379
9380 cx.assert_editor_state(indoc! {"
9381 fn a() {
9382 «b();
9383 c();
9384 ˇ» d();
9385 }
9386 "});
9387
9388 // The comment prefix is inserted at the same column for every line in a
9389 // selection.
9390 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9391
9392 cx.assert_editor_state(indoc! {"
9393 fn a() {
9394 // «b();
9395 // c();
9396 ˇ»// d();
9397 }
9398 "});
9399
9400 // If a selection ends at the beginning of a line, that line is not toggled.
9401 cx.set_selections_state(indoc! {"
9402 fn a() {
9403 // b();
9404 «// c();
9405 ˇ» // d();
9406 }
9407 "});
9408
9409 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9410
9411 cx.assert_editor_state(indoc! {"
9412 fn a() {
9413 // b();
9414 «c();
9415 ˇ» // d();
9416 }
9417 "});
9418
9419 // If a selection span a single line and is empty, the line is toggled.
9420 cx.set_state(indoc! {"
9421 fn a() {
9422 a();
9423 b();
9424 ˇ
9425 }
9426 "});
9427
9428 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9429
9430 cx.assert_editor_state(indoc! {"
9431 fn a() {
9432 a();
9433 b();
9434 //•ˇ
9435 }
9436 "});
9437
9438 // If a selection span multiple lines, empty lines are not toggled.
9439 cx.set_state(indoc! {"
9440 fn a() {
9441 «a();
9442
9443 c();ˇ»
9444 }
9445 "});
9446
9447 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9448
9449 cx.assert_editor_state(indoc! {"
9450 fn a() {
9451 // «a();
9452
9453 // c();ˇ»
9454 }
9455 "});
9456
9457 // If a selection includes multiple comment prefixes, all lines are uncommented.
9458 cx.set_state(indoc! {"
9459 fn a() {
9460 «// a();
9461 /// b();
9462 //! c();ˇ»
9463 }
9464 "});
9465
9466 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9467
9468 cx.assert_editor_state(indoc! {"
9469 fn a() {
9470 «a();
9471 b();
9472 c();ˇ»
9473 }
9474 "});
9475}
9476
9477#[gpui::test]
9478async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9479 init_test(cx, |_| {});
9480 let mut cx = EditorTestContext::new(cx).await;
9481 let language = Arc::new(Language::new(
9482 LanguageConfig {
9483 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9484 ..Default::default()
9485 },
9486 Some(tree_sitter_rust::LANGUAGE.into()),
9487 ));
9488 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9489
9490 let toggle_comments = &ToggleComments {
9491 advance_downwards: false,
9492 ignore_indent: true,
9493 };
9494
9495 // If multiple selections intersect a line, the line is only toggled once.
9496 cx.set_state(indoc! {"
9497 fn a() {
9498 // «b();
9499 // c();
9500 // ˇ» d();
9501 }
9502 "});
9503
9504 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9505
9506 cx.assert_editor_state(indoc! {"
9507 fn a() {
9508 «b();
9509 c();
9510 ˇ» d();
9511 }
9512 "});
9513
9514 // The comment prefix is inserted at the beginning of each line
9515 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9516
9517 cx.assert_editor_state(indoc! {"
9518 fn a() {
9519 // «b();
9520 // c();
9521 // ˇ» d();
9522 }
9523 "});
9524
9525 // If a selection ends at the beginning of a line, that line is not toggled.
9526 cx.set_selections_state(indoc! {"
9527 fn a() {
9528 // b();
9529 // «c();
9530 ˇ»// d();
9531 }
9532 "});
9533
9534 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9535
9536 cx.assert_editor_state(indoc! {"
9537 fn a() {
9538 // b();
9539 «c();
9540 ˇ»// d();
9541 }
9542 "});
9543
9544 // If a selection span a single line and is empty, the line is toggled.
9545 cx.set_state(indoc! {"
9546 fn a() {
9547 a();
9548 b();
9549 ˇ
9550 }
9551 "});
9552
9553 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9554
9555 cx.assert_editor_state(indoc! {"
9556 fn a() {
9557 a();
9558 b();
9559 //ˇ
9560 }
9561 "});
9562
9563 // If a selection span multiple lines, empty lines are not toggled.
9564 cx.set_state(indoc! {"
9565 fn a() {
9566 «a();
9567
9568 c();ˇ»
9569 }
9570 "});
9571
9572 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9573
9574 cx.assert_editor_state(indoc! {"
9575 fn a() {
9576 // «a();
9577
9578 // c();ˇ»
9579 }
9580 "});
9581
9582 // If a selection includes multiple comment prefixes, all lines are uncommented.
9583 cx.set_state(indoc! {"
9584 fn a() {
9585 // «a();
9586 /// b();
9587 //! c();ˇ»
9588 }
9589 "});
9590
9591 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9592
9593 cx.assert_editor_state(indoc! {"
9594 fn a() {
9595 «a();
9596 b();
9597 c();ˇ»
9598 }
9599 "});
9600}
9601
9602#[gpui::test]
9603async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9604 init_test(cx, |_| {});
9605
9606 let language = Arc::new(Language::new(
9607 LanguageConfig {
9608 line_comments: vec!["// ".into()],
9609 ..Default::default()
9610 },
9611 Some(tree_sitter_rust::LANGUAGE.into()),
9612 ));
9613
9614 let mut cx = EditorTestContext::new(cx).await;
9615
9616 cx.language_registry().add(language.clone());
9617 cx.update_buffer(|buffer, cx| {
9618 buffer.set_language(Some(language), cx);
9619 });
9620
9621 let toggle_comments = &ToggleComments {
9622 advance_downwards: true,
9623 ignore_indent: false,
9624 };
9625
9626 // Single cursor on one line -> advance
9627 // Cursor moves horizontally 3 characters as well on non-blank line
9628 cx.set_state(indoc!(
9629 "fn a() {
9630 ˇdog();
9631 cat();
9632 }"
9633 ));
9634 cx.update_editor(|editor, window, cx| {
9635 editor.toggle_comments(toggle_comments, window, cx);
9636 });
9637 cx.assert_editor_state(indoc!(
9638 "fn a() {
9639 // dog();
9640 catˇ();
9641 }"
9642 ));
9643
9644 // Single selection on one line -> don't advance
9645 cx.set_state(indoc!(
9646 "fn a() {
9647 «dog()ˇ»;
9648 cat();
9649 }"
9650 ));
9651 cx.update_editor(|editor, window, cx| {
9652 editor.toggle_comments(toggle_comments, window, cx);
9653 });
9654 cx.assert_editor_state(indoc!(
9655 "fn a() {
9656 // «dog()ˇ»;
9657 cat();
9658 }"
9659 ));
9660
9661 // Multiple cursors on one line -> advance
9662 cx.set_state(indoc!(
9663 "fn a() {
9664 ˇdˇog();
9665 cat();
9666 }"
9667 ));
9668 cx.update_editor(|editor, window, cx| {
9669 editor.toggle_comments(toggle_comments, window, cx);
9670 });
9671 cx.assert_editor_state(indoc!(
9672 "fn a() {
9673 // dog();
9674 catˇ(ˇ);
9675 }"
9676 ));
9677
9678 // Multiple cursors on one line, with selection -> don't advance
9679 cx.set_state(indoc!(
9680 "fn a() {
9681 ˇdˇog«()ˇ»;
9682 cat();
9683 }"
9684 ));
9685 cx.update_editor(|editor, window, cx| {
9686 editor.toggle_comments(toggle_comments, window, cx);
9687 });
9688 cx.assert_editor_state(indoc!(
9689 "fn a() {
9690 // ˇdˇog«()ˇ»;
9691 cat();
9692 }"
9693 ));
9694
9695 // Single cursor on one line -> advance
9696 // Cursor moves to column 0 on blank line
9697 cx.set_state(indoc!(
9698 "fn a() {
9699 ˇdog();
9700
9701 cat();
9702 }"
9703 ));
9704 cx.update_editor(|editor, window, cx| {
9705 editor.toggle_comments(toggle_comments, window, cx);
9706 });
9707 cx.assert_editor_state(indoc!(
9708 "fn a() {
9709 // dog();
9710 ˇ
9711 cat();
9712 }"
9713 ));
9714
9715 // Single cursor on one line -> advance
9716 // Cursor starts and ends at column 0
9717 cx.set_state(indoc!(
9718 "fn a() {
9719 ˇ dog();
9720 cat();
9721 }"
9722 ));
9723 cx.update_editor(|editor, window, cx| {
9724 editor.toggle_comments(toggle_comments, window, cx);
9725 });
9726 cx.assert_editor_state(indoc!(
9727 "fn a() {
9728 // dog();
9729 ˇ cat();
9730 }"
9731 ));
9732}
9733
9734#[gpui::test]
9735async fn test_toggle_block_comment(cx: &mut TestAppContext) {
9736 init_test(cx, |_| {});
9737
9738 let mut cx = EditorTestContext::new(cx).await;
9739
9740 let html_language = Arc::new(
9741 Language::new(
9742 LanguageConfig {
9743 name: "HTML".into(),
9744 block_comment: Some(("<!-- ".into(), " -->".into())),
9745 ..Default::default()
9746 },
9747 Some(tree_sitter_html::LANGUAGE.into()),
9748 )
9749 .with_injection_query(
9750 r#"
9751 (script_element
9752 (raw_text) @injection.content
9753 (#set! injection.language "javascript"))
9754 "#,
9755 )
9756 .unwrap(),
9757 );
9758
9759 let javascript_language = Arc::new(Language::new(
9760 LanguageConfig {
9761 name: "JavaScript".into(),
9762 line_comments: vec!["// ".into()],
9763 ..Default::default()
9764 },
9765 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9766 ));
9767
9768 cx.language_registry().add(html_language.clone());
9769 cx.language_registry().add(javascript_language.clone());
9770 cx.update_buffer(|buffer, cx| {
9771 buffer.set_language(Some(html_language), cx);
9772 });
9773
9774 // Toggle comments for empty selections
9775 cx.set_state(
9776 &r#"
9777 <p>A</p>ˇ
9778 <p>B</p>ˇ
9779 <p>C</p>ˇ
9780 "#
9781 .unindent(),
9782 );
9783 cx.update_editor(|editor, window, cx| {
9784 editor.toggle_comments(&ToggleComments::default(), window, cx)
9785 });
9786 cx.assert_editor_state(
9787 &r#"
9788 <!-- <p>A</p>ˇ -->
9789 <!-- <p>B</p>ˇ -->
9790 <!-- <p>C</p>ˇ -->
9791 "#
9792 .unindent(),
9793 );
9794 cx.update_editor(|editor, window, cx| {
9795 editor.toggle_comments(&ToggleComments::default(), window, cx)
9796 });
9797 cx.assert_editor_state(
9798 &r#"
9799 <p>A</p>ˇ
9800 <p>B</p>ˇ
9801 <p>C</p>ˇ
9802 "#
9803 .unindent(),
9804 );
9805
9806 // Toggle comments for mixture of empty and non-empty selections, where
9807 // multiple selections occupy a given line.
9808 cx.set_state(
9809 &r#"
9810 <p>A«</p>
9811 <p>ˇ»B</p>ˇ
9812 <p>C«</p>
9813 <p>ˇ»D</p>ˇ
9814 "#
9815 .unindent(),
9816 );
9817
9818 cx.update_editor(|editor, window, cx| {
9819 editor.toggle_comments(&ToggleComments::default(), window, cx)
9820 });
9821 cx.assert_editor_state(
9822 &r#"
9823 <!-- <p>A«</p>
9824 <p>ˇ»B</p>ˇ -->
9825 <!-- <p>C«</p>
9826 <p>ˇ»D</p>ˇ -->
9827 "#
9828 .unindent(),
9829 );
9830 cx.update_editor(|editor, window, cx| {
9831 editor.toggle_comments(&ToggleComments::default(), window, cx)
9832 });
9833 cx.assert_editor_state(
9834 &r#"
9835 <p>A«</p>
9836 <p>ˇ»B</p>ˇ
9837 <p>C«</p>
9838 <p>ˇ»D</p>ˇ
9839 "#
9840 .unindent(),
9841 );
9842
9843 // Toggle comments when different languages are active for different
9844 // selections.
9845 cx.set_state(
9846 &r#"
9847 ˇ<script>
9848 ˇvar x = new Y();
9849 ˇ</script>
9850 "#
9851 .unindent(),
9852 );
9853 cx.executor().run_until_parked();
9854 cx.update_editor(|editor, window, cx| {
9855 editor.toggle_comments(&ToggleComments::default(), window, cx)
9856 });
9857 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9858 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9859 cx.assert_editor_state(
9860 &r#"
9861 <!-- ˇ<script> -->
9862 // ˇvar x = new Y();
9863 <!-- ˇ</script> -->
9864 "#
9865 .unindent(),
9866 );
9867}
9868
9869#[gpui::test]
9870fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9871 init_test(cx, |_| {});
9872
9873 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9874 let multibuffer = cx.new(|cx| {
9875 let mut multibuffer = MultiBuffer::new(ReadWrite);
9876 multibuffer.push_excerpts(
9877 buffer.clone(),
9878 [
9879 ExcerptRange {
9880 context: Point::new(0, 0)..Point::new(0, 4),
9881 primary: None,
9882 },
9883 ExcerptRange {
9884 context: Point::new(1, 0)..Point::new(1, 4),
9885 primary: None,
9886 },
9887 ],
9888 cx,
9889 );
9890 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9891 multibuffer
9892 });
9893
9894 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9895 editor.update_in(cx, |editor, window, cx| {
9896 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9897 editor.change_selections(None, window, cx, |s| {
9898 s.select_ranges([
9899 Point::new(0, 0)..Point::new(0, 0),
9900 Point::new(1, 0)..Point::new(1, 0),
9901 ])
9902 });
9903
9904 editor.handle_input("X", window, cx);
9905 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9906 assert_eq!(
9907 editor.selections.ranges(cx),
9908 [
9909 Point::new(0, 1)..Point::new(0, 1),
9910 Point::new(1, 1)..Point::new(1, 1),
9911 ]
9912 );
9913
9914 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9915 editor.change_selections(None, window, cx, |s| {
9916 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9917 });
9918 editor.backspace(&Default::default(), window, cx);
9919 assert_eq!(editor.text(cx), "Xa\nbbb");
9920 assert_eq!(
9921 editor.selections.ranges(cx),
9922 [Point::new(1, 0)..Point::new(1, 0)]
9923 );
9924
9925 editor.change_selections(None, window, cx, |s| {
9926 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9927 });
9928 editor.backspace(&Default::default(), window, cx);
9929 assert_eq!(editor.text(cx), "X\nbb");
9930 assert_eq!(
9931 editor.selections.ranges(cx),
9932 [Point::new(0, 1)..Point::new(0, 1)]
9933 );
9934 });
9935}
9936
9937#[gpui::test]
9938fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9939 init_test(cx, |_| {});
9940
9941 let markers = vec![('[', ']').into(), ('(', ')').into()];
9942 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9943 indoc! {"
9944 [aaaa
9945 (bbbb]
9946 cccc)",
9947 },
9948 markers.clone(),
9949 );
9950 let excerpt_ranges = markers.into_iter().map(|marker| {
9951 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9952 ExcerptRange {
9953 context,
9954 primary: None,
9955 }
9956 });
9957 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9958 let multibuffer = cx.new(|cx| {
9959 let mut multibuffer = MultiBuffer::new(ReadWrite);
9960 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9961 multibuffer
9962 });
9963
9964 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9965 editor.update_in(cx, |editor, window, cx| {
9966 let (expected_text, selection_ranges) = marked_text_ranges(
9967 indoc! {"
9968 aaaa
9969 bˇbbb
9970 bˇbbˇb
9971 cccc"
9972 },
9973 true,
9974 );
9975 assert_eq!(editor.text(cx), expected_text);
9976 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9977
9978 editor.handle_input("X", window, cx);
9979
9980 let (expected_text, expected_selections) = marked_text_ranges(
9981 indoc! {"
9982 aaaa
9983 bXˇbbXb
9984 bXˇbbXˇb
9985 cccc"
9986 },
9987 false,
9988 );
9989 assert_eq!(editor.text(cx), expected_text);
9990 assert_eq!(editor.selections.ranges(cx), expected_selections);
9991
9992 editor.newline(&Newline, window, cx);
9993 let (expected_text, expected_selections) = marked_text_ranges(
9994 indoc! {"
9995 aaaa
9996 bX
9997 ˇbbX
9998 b
9999 bX
10000 ˇbbX
10001 ˇb
10002 cccc"
10003 },
10004 false,
10005 );
10006 assert_eq!(editor.text(cx), expected_text);
10007 assert_eq!(editor.selections.ranges(cx), expected_selections);
10008 });
10009}
10010
10011#[gpui::test]
10012fn test_refresh_selections(cx: &mut TestAppContext) {
10013 init_test(cx, |_| {});
10014
10015 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10016 let mut excerpt1_id = None;
10017 let multibuffer = cx.new(|cx| {
10018 let mut multibuffer = MultiBuffer::new(ReadWrite);
10019 excerpt1_id = multibuffer
10020 .push_excerpts(
10021 buffer.clone(),
10022 [
10023 ExcerptRange {
10024 context: Point::new(0, 0)..Point::new(1, 4),
10025 primary: None,
10026 },
10027 ExcerptRange {
10028 context: Point::new(1, 0)..Point::new(2, 4),
10029 primary: None,
10030 },
10031 ],
10032 cx,
10033 )
10034 .into_iter()
10035 .next();
10036 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10037 multibuffer
10038 });
10039
10040 let editor = cx.add_window(|window, cx| {
10041 let mut editor = build_editor(multibuffer.clone(), window, cx);
10042 let snapshot = editor.snapshot(window, cx);
10043 editor.change_selections(None, window, cx, |s| {
10044 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10045 });
10046 editor.begin_selection(
10047 Point::new(2, 1).to_display_point(&snapshot),
10048 true,
10049 1,
10050 window,
10051 cx,
10052 );
10053 assert_eq!(
10054 editor.selections.ranges(cx),
10055 [
10056 Point::new(1, 3)..Point::new(1, 3),
10057 Point::new(2, 1)..Point::new(2, 1),
10058 ]
10059 );
10060 editor
10061 });
10062
10063 // Refreshing selections is a no-op when excerpts haven't changed.
10064 _ = editor.update(cx, |editor, window, cx| {
10065 editor.change_selections(None, window, cx, |s| s.refresh());
10066 assert_eq!(
10067 editor.selections.ranges(cx),
10068 [
10069 Point::new(1, 3)..Point::new(1, 3),
10070 Point::new(2, 1)..Point::new(2, 1),
10071 ]
10072 );
10073 });
10074
10075 multibuffer.update(cx, |multibuffer, cx| {
10076 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10077 });
10078 _ = editor.update(cx, |editor, window, cx| {
10079 // Removing an excerpt causes the first selection to become degenerate.
10080 assert_eq!(
10081 editor.selections.ranges(cx),
10082 [
10083 Point::new(0, 0)..Point::new(0, 0),
10084 Point::new(0, 1)..Point::new(0, 1)
10085 ]
10086 );
10087
10088 // Refreshing selections will relocate the first selection to the original buffer
10089 // location.
10090 editor.change_selections(None, window, cx, |s| s.refresh());
10091 assert_eq!(
10092 editor.selections.ranges(cx),
10093 [
10094 Point::new(0, 1)..Point::new(0, 1),
10095 Point::new(0, 3)..Point::new(0, 3)
10096 ]
10097 );
10098 assert!(editor.selections.pending_anchor().is_some());
10099 });
10100}
10101
10102#[gpui::test]
10103fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10104 init_test(cx, |_| {});
10105
10106 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10107 let mut excerpt1_id = None;
10108 let multibuffer = cx.new(|cx| {
10109 let mut multibuffer = MultiBuffer::new(ReadWrite);
10110 excerpt1_id = multibuffer
10111 .push_excerpts(
10112 buffer.clone(),
10113 [
10114 ExcerptRange {
10115 context: Point::new(0, 0)..Point::new(1, 4),
10116 primary: None,
10117 },
10118 ExcerptRange {
10119 context: Point::new(1, 0)..Point::new(2, 4),
10120 primary: None,
10121 },
10122 ],
10123 cx,
10124 )
10125 .into_iter()
10126 .next();
10127 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10128 multibuffer
10129 });
10130
10131 let editor = cx.add_window(|window, cx| {
10132 let mut editor = build_editor(multibuffer.clone(), window, cx);
10133 let snapshot = editor.snapshot(window, cx);
10134 editor.begin_selection(
10135 Point::new(1, 3).to_display_point(&snapshot),
10136 false,
10137 1,
10138 window,
10139 cx,
10140 );
10141 assert_eq!(
10142 editor.selections.ranges(cx),
10143 [Point::new(1, 3)..Point::new(1, 3)]
10144 );
10145 editor
10146 });
10147
10148 multibuffer.update(cx, |multibuffer, cx| {
10149 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10150 });
10151 _ = editor.update(cx, |editor, window, cx| {
10152 assert_eq!(
10153 editor.selections.ranges(cx),
10154 [Point::new(0, 0)..Point::new(0, 0)]
10155 );
10156
10157 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10158 editor.change_selections(None, window, cx, |s| s.refresh());
10159 assert_eq!(
10160 editor.selections.ranges(cx),
10161 [Point::new(0, 3)..Point::new(0, 3)]
10162 );
10163 assert!(editor.selections.pending_anchor().is_some());
10164 });
10165}
10166
10167#[gpui::test]
10168async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10169 init_test(cx, |_| {});
10170
10171 let language = Arc::new(
10172 Language::new(
10173 LanguageConfig {
10174 brackets: BracketPairConfig {
10175 pairs: vec![
10176 BracketPair {
10177 start: "{".to_string(),
10178 end: "}".to_string(),
10179 close: true,
10180 surround: true,
10181 newline: true,
10182 },
10183 BracketPair {
10184 start: "/* ".to_string(),
10185 end: " */".to_string(),
10186 close: true,
10187 surround: true,
10188 newline: true,
10189 },
10190 ],
10191 ..Default::default()
10192 },
10193 ..Default::default()
10194 },
10195 Some(tree_sitter_rust::LANGUAGE.into()),
10196 )
10197 .with_indents_query("")
10198 .unwrap(),
10199 );
10200
10201 let text = concat!(
10202 "{ }\n", //
10203 " x\n", //
10204 " /* */\n", //
10205 "x\n", //
10206 "{{} }\n", //
10207 );
10208
10209 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10210 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10211 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10212 editor
10213 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10214 .await;
10215
10216 editor.update_in(cx, |editor, window, cx| {
10217 editor.change_selections(None, window, cx, |s| {
10218 s.select_display_ranges([
10219 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10220 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10221 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10222 ])
10223 });
10224 editor.newline(&Newline, window, cx);
10225
10226 assert_eq!(
10227 editor.buffer().read(cx).read(cx).text(),
10228 concat!(
10229 "{ \n", // Suppress rustfmt
10230 "\n", //
10231 "}\n", //
10232 " x\n", //
10233 " /* \n", //
10234 " \n", //
10235 " */\n", //
10236 "x\n", //
10237 "{{} \n", //
10238 "}\n", //
10239 )
10240 );
10241 });
10242}
10243
10244#[gpui::test]
10245fn test_highlighted_ranges(cx: &mut TestAppContext) {
10246 init_test(cx, |_| {});
10247
10248 let editor = cx.add_window(|window, cx| {
10249 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10250 build_editor(buffer.clone(), window, cx)
10251 });
10252
10253 _ = editor.update(cx, |editor, window, cx| {
10254 struct Type1;
10255 struct Type2;
10256
10257 let buffer = editor.buffer.read(cx).snapshot(cx);
10258
10259 let anchor_range =
10260 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10261
10262 editor.highlight_background::<Type1>(
10263 &[
10264 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10265 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10266 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10267 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10268 ],
10269 |_| Hsla::red(),
10270 cx,
10271 );
10272 editor.highlight_background::<Type2>(
10273 &[
10274 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10275 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10276 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10277 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10278 ],
10279 |_| Hsla::green(),
10280 cx,
10281 );
10282
10283 let snapshot = editor.snapshot(window, cx);
10284 let mut highlighted_ranges = editor.background_highlights_in_range(
10285 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10286 &snapshot,
10287 cx.theme().colors(),
10288 );
10289 // Enforce a consistent ordering based on color without relying on the ordering of the
10290 // highlight's `TypeId` which is non-executor.
10291 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10292 assert_eq!(
10293 highlighted_ranges,
10294 &[
10295 (
10296 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10297 Hsla::red(),
10298 ),
10299 (
10300 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10301 Hsla::red(),
10302 ),
10303 (
10304 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10305 Hsla::green(),
10306 ),
10307 (
10308 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10309 Hsla::green(),
10310 ),
10311 ]
10312 );
10313 assert_eq!(
10314 editor.background_highlights_in_range(
10315 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10316 &snapshot,
10317 cx.theme().colors(),
10318 ),
10319 &[(
10320 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10321 Hsla::red(),
10322 )]
10323 );
10324 });
10325}
10326
10327#[gpui::test]
10328async fn test_following(cx: &mut TestAppContext) {
10329 init_test(cx, |_| {});
10330
10331 let fs = FakeFs::new(cx.executor());
10332 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10333
10334 let buffer = project.update(cx, |project, cx| {
10335 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10336 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10337 });
10338 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10339 let follower = cx.update(|cx| {
10340 cx.open_window(
10341 WindowOptions {
10342 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10343 gpui::Point::new(px(0.), px(0.)),
10344 gpui::Point::new(px(10.), px(80.)),
10345 ))),
10346 ..Default::default()
10347 },
10348 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10349 )
10350 .unwrap()
10351 });
10352
10353 let is_still_following = Rc::new(RefCell::new(true));
10354 let follower_edit_event_count = Rc::new(RefCell::new(0));
10355 let pending_update = Rc::new(RefCell::new(None));
10356 let leader_entity = leader.root(cx).unwrap();
10357 let follower_entity = follower.root(cx).unwrap();
10358 _ = follower.update(cx, {
10359 let update = pending_update.clone();
10360 let is_still_following = is_still_following.clone();
10361 let follower_edit_event_count = follower_edit_event_count.clone();
10362 |_, window, cx| {
10363 cx.subscribe_in(
10364 &leader_entity,
10365 window,
10366 move |_, leader, event, window, cx| {
10367 leader.read(cx).add_event_to_update_proto(
10368 event,
10369 &mut update.borrow_mut(),
10370 window,
10371 cx,
10372 );
10373 },
10374 )
10375 .detach();
10376
10377 cx.subscribe_in(
10378 &follower_entity,
10379 window,
10380 move |_, _, event: &EditorEvent, _window, _cx| {
10381 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10382 *is_still_following.borrow_mut() = false;
10383 }
10384
10385 if let EditorEvent::BufferEdited = event {
10386 *follower_edit_event_count.borrow_mut() += 1;
10387 }
10388 },
10389 )
10390 .detach();
10391 }
10392 });
10393
10394 // Update the selections only
10395 _ = leader.update(cx, |leader, window, cx| {
10396 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10397 });
10398 follower
10399 .update(cx, |follower, window, cx| {
10400 follower.apply_update_proto(
10401 &project,
10402 pending_update.borrow_mut().take().unwrap(),
10403 window,
10404 cx,
10405 )
10406 })
10407 .unwrap()
10408 .await
10409 .unwrap();
10410 _ = follower.update(cx, |follower, _, cx| {
10411 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10412 });
10413 assert!(*is_still_following.borrow());
10414 assert_eq!(*follower_edit_event_count.borrow(), 0);
10415
10416 // Update the scroll position only
10417 _ = leader.update(cx, |leader, window, cx| {
10418 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10419 });
10420 follower
10421 .update(cx, |follower, window, cx| {
10422 follower.apply_update_proto(
10423 &project,
10424 pending_update.borrow_mut().take().unwrap(),
10425 window,
10426 cx,
10427 )
10428 })
10429 .unwrap()
10430 .await
10431 .unwrap();
10432 assert_eq!(
10433 follower
10434 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10435 .unwrap(),
10436 gpui::Point::new(1.5, 3.5)
10437 );
10438 assert!(*is_still_following.borrow());
10439 assert_eq!(*follower_edit_event_count.borrow(), 0);
10440
10441 // Update the selections and scroll position. The follower's scroll position is updated
10442 // via autoscroll, not via the leader's exact scroll position.
10443 _ = leader.update(cx, |leader, window, cx| {
10444 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10445 leader.request_autoscroll(Autoscroll::newest(), cx);
10446 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10447 });
10448 follower
10449 .update(cx, |follower, window, cx| {
10450 follower.apply_update_proto(
10451 &project,
10452 pending_update.borrow_mut().take().unwrap(),
10453 window,
10454 cx,
10455 )
10456 })
10457 .unwrap()
10458 .await
10459 .unwrap();
10460 _ = follower.update(cx, |follower, _, cx| {
10461 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10462 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10463 });
10464 assert!(*is_still_following.borrow());
10465
10466 // Creating a pending selection that precedes another selection
10467 _ = leader.update(cx, |leader, window, cx| {
10468 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10469 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10470 });
10471 follower
10472 .update(cx, |follower, window, cx| {
10473 follower.apply_update_proto(
10474 &project,
10475 pending_update.borrow_mut().take().unwrap(),
10476 window,
10477 cx,
10478 )
10479 })
10480 .unwrap()
10481 .await
10482 .unwrap();
10483 _ = follower.update(cx, |follower, _, cx| {
10484 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10485 });
10486 assert!(*is_still_following.borrow());
10487
10488 // Extend the pending selection so that it surrounds another selection
10489 _ = leader.update(cx, |leader, window, cx| {
10490 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10491 });
10492 follower
10493 .update(cx, |follower, window, cx| {
10494 follower.apply_update_proto(
10495 &project,
10496 pending_update.borrow_mut().take().unwrap(),
10497 window,
10498 cx,
10499 )
10500 })
10501 .unwrap()
10502 .await
10503 .unwrap();
10504 _ = follower.update(cx, |follower, _, cx| {
10505 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10506 });
10507
10508 // Scrolling locally breaks the follow
10509 _ = follower.update(cx, |follower, window, cx| {
10510 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10511 follower.set_scroll_anchor(
10512 ScrollAnchor {
10513 anchor: top_anchor,
10514 offset: gpui::Point::new(0.0, 0.5),
10515 },
10516 window,
10517 cx,
10518 );
10519 });
10520 assert!(!(*is_still_following.borrow()));
10521}
10522
10523#[gpui::test]
10524async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10525 init_test(cx, |_| {});
10526
10527 let fs = FakeFs::new(cx.executor());
10528 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10529 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10530 let pane = workspace
10531 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10532 .unwrap();
10533
10534 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10535
10536 let leader = pane.update_in(cx, |_, window, cx| {
10537 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10538 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10539 });
10540
10541 // Start following the editor when it has no excerpts.
10542 let mut state_message =
10543 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10544 let workspace_entity = workspace.root(cx).unwrap();
10545 let follower_1 = cx
10546 .update_window(*workspace.deref(), |_, window, cx| {
10547 Editor::from_state_proto(
10548 workspace_entity,
10549 ViewId {
10550 creator: Default::default(),
10551 id: 0,
10552 },
10553 &mut state_message,
10554 window,
10555 cx,
10556 )
10557 })
10558 .unwrap()
10559 .unwrap()
10560 .await
10561 .unwrap();
10562
10563 let update_message = Rc::new(RefCell::new(None));
10564 follower_1.update_in(cx, {
10565 let update = update_message.clone();
10566 |_, window, cx| {
10567 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10568 leader.read(cx).add_event_to_update_proto(
10569 event,
10570 &mut update.borrow_mut(),
10571 window,
10572 cx,
10573 );
10574 })
10575 .detach();
10576 }
10577 });
10578
10579 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10580 (
10581 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10582 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10583 )
10584 });
10585
10586 // Insert some excerpts.
10587 leader.update(cx, |leader, cx| {
10588 leader.buffer.update(cx, |multibuffer, cx| {
10589 let excerpt_ids = multibuffer.push_excerpts(
10590 buffer_1.clone(),
10591 [
10592 ExcerptRange {
10593 context: 1..6,
10594 primary: None,
10595 },
10596 ExcerptRange {
10597 context: 12..15,
10598 primary: None,
10599 },
10600 ExcerptRange {
10601 context: 0..3,
10602 primary: None,
10603 },
10604 ],
10605 cx,
10606 );
10607 multibuffer.insert_excerpts_after(
10608 excerpt_ids[0],
10609 buffer_2.clone(),
10610 [
10611 ExcerptRange {
10612 context: 8..12,
10613 primary: None,
10614 },
10615 ExcerptRange {
10616 context: 0..6,
10617 primary: None,
10618 },
10619 ],
10620 cx,
10621 );
10622 });
10623 });
10624
10625 // Apply the update of adding the excerpts.
10626 follower_1
10627 .update_in(cx, |follower, window, cx| {
10628 follower.apply_update_proto(
10629 &project,
10630 update_message.borrow().clone().unwrap(),
10631 window,
10632 cx,
10633 )
10634 })
10635 .await
10636 .unwrap();
10637 assert_eq!(
10638 follower_1.update(cx, |editor, cx| editor.text(cx)),
10639 leader.update(cx, |editor, cx| editor.text(cx))
10640 );
10641 update_message.borrow_mut().take();
10642
10643 // Start following separately after it already has excerpts.
10644 let mut state_message =
10645 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10646 let workspace_entity = workspace.root(cx).unwrap();
10647 let follower_2 = cx
10648 .update_window(*workspace.deref(), |_, window, cx| {
10649 Editor::from_state_proto(
10650 workspace_entity,
10651 ViewId {
10652 creator: Default::default(),
10653 id: 0,
10654 },
10655 &mut state_message,
10656 window,
10657 cx,
10658 )
10659 })
10660 .unwrap()
10661 .unwrap()
10662 .await
10663 .unwrap();
10664 assert_eq!(
10665 follower_2.update(cx, |editor, cx| editor.text(cx)),
10666 leader.update(cx, |editor, cx| editor.text(cx))
10667 );
10668
10669 // Remove some excerpts.
10670 leader.update(cx, |leader, cx| {
10671 leader.buffer.update(cx, |multibuffer, cx| {
10672 let excerpt_ids = multibuffer.excerpt_ids();
10673 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10674 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10675 });
10676 });
10677
10678 // Apply the update of removing the excerpts.
10679 follower_1
10680 .update_in(cx, |follower, window, cx| {
10681 follower.apply_update_proto(
10682 &project,
10683 update_message.borrow().clone().unwrap(),
10684 window,
10685 cx,
10686 )
10687 })
10688 .await
10689 .unwrap();
10690 follower_2
10691 .update_in(cx, |follower, window, cx| {
10692 follower.apply_update_proto(
10693 &project,
10694 update_message.borrow().clone().unwrap(),
10695 window,
10696 cx,
10697 )
10698 })
10699 .await
10700 .unwrap();
10701 update_message.borrow_mut().take();
10702 assert_eq!(
10703 follower_1.update(cx, |editor, cx| editor.text(cx)),
10704 leader.update(cx, |editor, cx| editor.text(cx))
10705 );
10706}
10707
10708#[gpui::test]
10709async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
10710 init_test(cx, |_| {});
10711
10712 let mut cx = EditorTestContext::new(cx).await;
10713 let lsp_store =
10714 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10715
10716 cx.set_state(indoc! {"
10717 ˇfn func(abc def: i32) -> u32 {
10718 }
10719 "});
10720
10721 cx.update(|_, cx| {
10722 lsp_store.update(cx, |lsp_store, cx| {
10723 lsp_store
10724 .update_diagnostics(
10725 LanguageServerId(0),
10726 lsp::PublishDiagnosticsParams {
10727 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10728 version: None,
10729 diagnostics: vec![
10730 lsp::Diagnostic {
10731 range: lsp::Range::new(
10732 lsp::Position::new(0, 11),
10733 lsp::Position::new(0, 12),
10734 ),
10735 severity: Some(lsp::DiagnosticSeverity::ERROR),
10736 ..Default::default()
10737 },
10738 lsp::Diagnostic {
10739 range: lsp::Range::new(
10740 lsp::Position::new(0, 12),
10741 lsp::Position::new(0, 15),
10742 ),
10743 severity: Some(lsp::DiagnosticSeverity::ERROR),
10744 ..Default::default()
10745 },
10746 lsp::Diagnostic {
10747 range: lsp::Range::new(
10748 lsp::Position::new(0, 25),
10749 lsp::Position::new(0, 28),
10750 ),
10751 severity: Some(lsp::DiagnosticSeverity::ERROR),
10752 ..Default::default()
10753 },
10754 ],
10755 },
10756 &[],
10757 cx,
10758 )
10759 .unwrap()
10760 });
10761 });
10762
10763 executor.run_until_parked();
10764
10765 cx.update_editor(|editor, window, cx| {
10766 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10767 });
10768
10769 cx.assert_editor_state(indoc! {"
10770 fn func(abc def: i32) -> ˇu32 {
10771 }
10772 "});
10773
10774 cx.update_editor(|editor, window, cx| {
10775 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10776 });
10777
10778 cx.assert_editor_state(indoc! {"
10779 fn func(abc ˇdef: i32) -> u32 {
10780 }
10781 "});
10782
10783 cx.update_editor(|editor, window, cx| {
10784 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10785 });
10786
10787 cx.assert_editor_state(indoc! {"
10788 fn func(abcˇ def: i32) -> u32 {
10789 }
10790 "});
10791
10792 cx.update_editor(|editor, window, cx| {
10793 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10794 });
10795
10796 cx.assert_editor_state(indoc! {"
10797 fn func(abc def: i32) -> ˇu32 {
10798 }
10799 "});
10800}
10801
10802#[gpui::test]
10803async fn cycle_through_same_place_diagnostics(
10804 executor: BackgroundExecutor,
10805 cx: &mut TestAppContext,
10806) {
10807 init_test(cx, |_| {});
10808
10809 let mut cx = EditorTestContext::new(cx).await;
10810 let lsp_store =
10811 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10812
10813 cx.set_state(indoc! {"
10814 ˇfn func(abc def: i32) -> u32 {
10815 }
10816 "});
10817
10818 cx.update(|_, cx| {
10819 lsp_store.update(cx, |lsp_store, cx| {
10820 lsp_store
10821 .update_diagnostics(
10822 LanguageServerId(0),
10823 lsp::PublishDiagnosticsParams {
10824 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10825 version: None,
10826 diagnostics: vec![
10827 lsp::Diagnostic {
10828 range: lsp::Range::new(
10829 lsp::Position::new(0, 11),
10830 lsp::Position::new(0, 12),
10831 ),
10832 severity: Some(lsp::DiagnosticSeverity::ERROR),
10833 ..Default::default()
10834 },
10835 lsp::Diagnostic {
10836 range: lsp::Range::new(
10837 lsp::Position::new(0, 12),
10838 lsp::Position::new(0, 15),
10839 ),
10840 severity: Some(lsp::DiagnosticSeverity::ERROR),
10841 ..Default::default()
10842 },
10843 lsp::Diagnostic {
10844 range: lsp::Range::new(
10845 lsp::Position::new(0, 12),
10846 lsp::Position::new(0, 15),
10847 ),
10848 severity: Some(lsp::DiagnosticSeverity::ERROR),
10849 ..Default::default()
10850 },
10851 lsp::Diagnostic {
10852 range: lsp::Range::new(
10853 lsp::Position::new(0, 25),
10854 lsp::Position::new(0, 28),
10855 ),
10856 severity: Some(lsp::DiagnosticSeverity::ERROR),
10857 ..Default::default()
10858 },
10859 ],
10860 },
10861 &[],
10862 cx,
10863 )
10864 .unwrap()
10865 });
10866 });
10867 executor.run_until_parked();
10868
10869 //// Backward
10870
10871 // Fourth diagnostic
10872 cx.update_editor(|editor, window, cx| {
10873 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10874 });
10875 cx.assert_editor_state(indoc! {"
10876 fn func(abc def: i32) -> ˇu32 {
10877 }
10878 "});
10879
10880 // Third diagnostic
10881 cx.update_editor(|editor, window, cx| {
10882 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10883 });
10884 cx.assert_editor_state(indoc! {"
10885 fn func(abc ˇdef: i32) -> u32 {
10886 }
10887 "});
10888
10889 // Second diagnostic, same place
10890 cx.update_editor(|editor, window, cx| {
10891 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10892 });
10893 cx.assert_editor_state(indoc! {"
10894 fn func(abc ˇdef: i32) -> u32 {
10895 }
10896 "});
10897
10898 // First diagnostic
10899 cx.update_editor(|editor, window, cx| {
10900 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10901 });
10902 cx.assert_editor_state(indoc! {"
10903 fn func(abcˇ def: i32) -> u32 {
10904 }
10905 "});
10906
10907 // Wrapped over, fourth diagnostic
10908 cx.update_editor(|editor, window, cx| {
10909 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10910 });
10911 cx.assert_editor_state(indoc! {"
10912 fn func(abc def: i32) -> ˇu32 {
10913 }
10914 "});
10915
10916 cx.update_editor(|editor, window, cx| {
10917 editor.move_to_beginning(&MoveToBeginning, window, cx);
10918 });
10919 cx.assert_editor_state(indoc! {"
10920 ˇfn func(abc def: i32) -> u32 {
10921 }
10922 "});
10923
10924 //// Forward
10925
10926 // First diagnostic
10927 cx.update_editor(|editor, window, cx| {
10928 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10929 });
10930 cx.assert_editor_state(indoc! {"
10931 fn func(abcˇ def: i32) -> u32 {
10932 }
10933 "});
10934
10935 // Second diagnostic
10936 cx.update_editor(|editor, window, cx| {
10937 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10938 });
10939 cx.assert_editor_state(indoc! {"
10940 fn func(abc ˇdef: i32) -> u32 {
10941 }
10942 "});
10943
10944 // Third diagnostic, same place
10945 cx.update_editor(|editor, window, cx| {
10946 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10947 });
10948 cx.assert_editor_state(indoc! {"
10949 fn func(abc ˇdef: i32) -> u32 {
10950 }
10951 "});
10952
10953 // Fourth diagnostic
10954 cx.update_editor(|editor, window, cx| {
10955 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10956 });
10957 cx.assert_editor_state(indoc! {"
10958 fn func(abc def: i32) -> ˇu32 {
10959 }
10960 "});
10961
10962 // Wrapped around, first diagnostic
10963 cx.update_editor(|editor, window, cx| {
10964 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10965 });
10966 cx.assert_editor_state(indoc! {"
10967 fn func(abcˇ def: i32) -> u32 {
10968 }
10969 "});
10970}
10971
10972#[gpui::test]
10973async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10974 init_test(cx, |_| {});
10975
10976 let mut cx = EditorTestContext::new(cx).await;
10977
10978 cx.set_state(indoc! {"
10979 fn func(abˇc def: i32) -> u32 {
10980 }
10981 "});
10982 let lsp_store =
10983 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10984
10985 cx.update(|_, cx| {
10986 lsp_store.update(cx, |lsp_store, cx| {
10987 lsp_store.update_diagnostics(
10988 LanguageServerId(0),
10989 lsp::PublishDiagnosticsParams {
10990 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10991 version: None,
10992 diagnostics: vec![lsp::Diagnostic {
10993 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10994 severity: Some(lsp::DiagnosticSeverity::ERROR),
10995 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10996 ..Default::default()
10997 }],
10998 },
10999 &[],
11000 cx,
11001 )
11002 })
11003 }).unwrap();
11004 cx.run_until_parked();
11005 cx.update_editor(|editor, window, cx| {
11006 hover_popover::hover(editor, &Default::default(), window, cx)
11007 });
11008 cx.run_until_parked();
11009 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11010}
11011
11012#[gpui::test]
11013async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11014 init_test(cx, |_| {});
11015
11016 let mut cx = EditorTestContext::new(cx).await;
11017
11018 let diff_base = r#"
11019 use some::mod;
11020
11021 const A: u32 = 42;
11022
11023 fn main() {
11024 println!("hello");
11025
11026 println!("world");
11027 }
11028 "#
11029 .unindent();
11030
11031 // Edits are modified, removed, modified, added
11032 cx.set_state(
11033 &r#"
11034 use some::modified;
11035
11036 ˇ
11037 fn main() {
11038 println!("hello there");
11039
11040 println!("around the");
11041 println!("world");
11042 }
11043 "#
11044 .unindent(),
11045 );
11046
11047 cx.set_head_text(&diff_base);
11048 executor.run_until_parked();
11049
11050 cx.update_editor(|editor, window, cx| {
11051 //Wrap around the bottom of the buffer
11052 for _ in 0..3 {
11053 editor.go_to_next_hunk(&GoToHunk, window, cx);
11054 }
11055 });
11056
11057 cx.assert_editor_state(
11058 &r#"
11059 ˇuse some::modified;
11060
11061
11062 fn main() {
11063 println!("hello there");
11064
11065 println!("around the");
11066 println!("world");
11067 }
11068 "#
11069 .unindent(),
11070 );
11071
11072 cx.update_editor(|editor, window, cx| {
11073 //Wrap around the top of the buffer
11074 for _ in 0..2 {
11075 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11076 }
11077 });
11078
11079 cx.assert_editor_state(
11080 &r#"
11081 use some::modified;
11082
11083
11084 fn main() {
11085 ˇ println!("hello there");
11086
11087 println!("around the");
11088 println!("world");
11089 }
11090 "#
11091 .unindent(),
11092 );
11093
11094 cx.update_editor(|editor, window, cx| {
11095 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11096 });
11097
11098 cx.assert_editor_state(
11099 &r#"
11100 use some::modified;
11101
11102 ˇ
11103 fn main() {
11104 println!("hello there");
11105
11106 println!("around the");
11107 println!("world");
11108 }
11109 "#
11110 .unindent(),
11111 );
11112
11113 cx.update_editor(|editor, window, cx| {
11114 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11115 });
11116
11117 cx.assert_editor_state(
11118 &r#"
11119 ˇuse some::modified;
11120
11121
11122 fn main() {
11123 println!("hello there");
11124
11125 println!("around the");
11126 println!("world");
11127 }
11128 "#
11129 .unindent(),
11130 );
11131
11132 cx.update_editor(|editor, window, cx| {
11133 for _ in 0..2 {
11134 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11135 }
11136 });
11137
11138 cx.assert_editor_state(
11139 &r#"
11140 use some::modified;
11141
11142
11143 fn main() {
11144 ˇ println!("hello there");
11145
11146 println!("around the");
11147 println!("world");
11148 }
11149 "#
11150 .unindent(),
11151 );
11152
11153 cx.update_editor(|editor, window, cx| {
11154 editor.fold(&Fold, window, cx);
11155 });
11156
11157 cx.update_editor(|editor, window, cx| {
11158 editor.go_to_next_hunk(&GoToHunk, window, cx);
11159 });
11160
11161 cx.assert_editor_state(
11162 &r#"
11163 ˇuse some::modified;
11164
11165
11166 fn main() {
11167 println!("hello there");
11168
11169 println!("around the");
11170 println!("world");
11171 }
11172 "#
11173 .unindent(),
11174 );
11175}
11176
11177#[test]
11178fn test_split_words() {
11179 fn split(text: &str) -> Vec<&str> {
11180 split_words(text).collect()
11181 }
11182
11183 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11184 assert_eq!(split("hello_world"), &["hello_", "world"]);
11185 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11186 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11187 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11188 assert_eq!(split("helloworld"), &["helloworld"]);
11189
11190 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11191}
11192
11193#[gpui::test]
11194async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11195 init_test(cx, |_| {});
11196
11197 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11198 let mut assert = |before, after| {
11199 let _state_context = cx.set_state(before);
11200 cx.run_until_parked();
11201 cx.update_editor(|editor, window, cx| {
11202 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11203 });
11204 cx.assert_editor_state(after);
11205 };
11206
11207 // Outside bracket jumps to outside of matching bracket
11208 assert("console.logˇ(var);", "console.log(var)ˇ;");
11209 assert("console.log(var)ˇ;", "console.logˇ(var);");
11210
11211 // Inside bracket jumps to inside of matching bracket
11212 assert("console.log(ˇvar);", "console.log(varˇ);");
11213 assert("console.log(varˇ);", "console.log(ˇvar);");
11214
11215 // When outside a bracket and inside, favor jumping to the inside bracket
11216 assert(
11217 "console.log('foo', [1, 2, 3]ˇ);",
11218 "console.log(ˇ'foo', [1, 2, 3]);",
11219 );
11220 assert(
11221 "console.log(ˇ'foo', [1, 2, 3]);",
11222 "console.log('foo', [1, 2, 3]ˇ);",
11223 );
11224
11225 // Bias forward if two options are equally likely
11226 assert(
11227 "let result = curried_fun()ˇ();",
11228 "let result = curried_fun()()ˇ;",
11229 );
11230
11231 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11232 assert(
11233 indoc! {"
11234 function test() {
11235 console.log('test')ˇ
11236 }"},
11237 indoc! {"
11238 function test() {
11239 console.logˇ('test')
11240 }"},
11241 );
11242}
11243
11244#[gpui::test]
11245async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11246 init_test(cx, |_| {});
11247
11248 let fs = FakeFs::new(cx.executor());
11249 fs.insert_tree(
11250 path!("/a"),
11251 json!({
11252 "main.rs": "fn main() { let a = 5; }",
11253 "other.rs": "// Test file",
11254 }),
11255 )
11256 .await;
11257 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11258
11259 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11260 language_registry.add(Arc::new(Language::new(
11261 LanguageConfig {
11262 name: "Rust".into(),
11263 matcher: LanguageMatcher {
11264 path_suffixes: vec!["rs".to_string()],
11265 ..Default::default()
11266 },
11267 brackets: BracketPairConfig {
11268 pairs: vec![BracketPair {
11269 start: "{".to_string(),
11270 end: "}".to_string(),
11271 close: true,
11272 surround: true,
11273 newline: true,
11274 }],
11275 disabled_scopes_by_bracket_ix: Vec::new(),
11276 },
11277 ..Default::default()
11278 },
11279 Some(tree_sitter_rust::LANGUAGE.into()),
11280 )));
11281 let mut fake_servers = language_registry.register_fake_lsp(
11282 "Rust",
11283 FakeLspAdapter {
11284 capabilities: lsp::ServerCapabilities {
11285 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11286 first_trigger_character: "{".to_string(),
11287 more_trigger_character: None,
11288 }),
11289 ..Default::default()
11290 },
11291 ..Default::default()
11292 },
11293 );
11294
11295 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11296
11297 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11298
11299 let worktree_id = workspace
11300 .update(cx, |workspace, _, cx| {
11301 workspace.project().update(cx, |project, cx| {
11302 project.worktrees(cx).next().unwrap().read(cx).id()
11303 })
11304 })
11305 .unwrap();
11306
11307 let buffer = project
11308 .update(cx, |project, cx| {
11309 project.open_local_buffer(path!("/a/main.rs"), cx)
11310 })
11311 .await
11312 .unwrap();
11313 let editor_handle = workspace
11314 .update(cx, |workspace, window, cx| {
11315 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11316 })
11317 .unwrap()
11318 .await
11319 .unwrap()
11320 .downcast::<Editor>()
11321 .unwrap();
11322
11323 cx.executor().start_waiting();
11324 let fake_server = fake_servers.next().await.unwrap();
11325
11326 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11327 assert_eq!(
11328 params.text_document_position.text_document.uri,
11329 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11330 );
11331 assert_eq!(
11332 params.text_document_position.position,
11333 lsp::Position::new(0, 21),
11334 );
11335
11336 Ok(Some(vec![lsp::TextEdit {
11337 new_text: "]".to_string(),
11338 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11339 }]))
11340 });
11341
11342 editor_handle.update_in(cx, |editor, window, cx| {
11343 window.focus(&editor.focus_handle(cx));
11344 editor.change_selections(None, window, cx, |s| {
11345 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11346 });
11347 editor.handle_input("{", window, cx);
11348 });
11349
11350 cx.executor().run_until_parked();
11351
11352 buffer.update(cx, |buffer, _| {
11353 assert_eq!(
11354 buffer.text(),
11355 "fn main() { let a = {5}; }",
11356 "No extra braces from on type formatting should appear in the buffer"
11357 )
11358 });
11359}
11360
11361#[gpui::test]
11362async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11363 init_test(cx, |_| {});
11364
11365 let fs = FakeFs::new(cx.executor());
11366 fs.insert_tree(
11367 path!("/a"),
11368 json!({
11369 "main.rs": "fn main() { let a = 5; }",
11370 "other.rs": "// Test file",
11371 }),
11372 )
11373 .await;
11374
11375 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11376
11377 let server_restarts = Arc::new(AtomicUsize::new(0));
11378 let closure_restarts = Arc::clone(&server_restarts);
11379 let language_server_name = "test language server";
11380 let language_name: LanguageName = "Rust".into();
11381
11382 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11383 language_registry.add(Arc::new(Language::new(
11384 LanguageConfig {
11385 name: language_name.clone(),
11386 matcher: LanguageMatcher {
11387 path_suffixes: vec!["rs".to_string()],
11388 ..Default::default()
11389 },
11390 ..Default::default()
11391 },
11392 Some(tree_sitter_rust::LANGUAGE.into()),
11393 )));
11394 let mut fake_servers = language_registry.register_fake_lsp(
11395 "Rust",
11396 FakeLspAdapter {
11397 name: language_server_name,
11398 initialization_options: Some(json!({
11399 "testOptionValue": true
11400 })),
11401 initializer: Some(Box::new(move |fake_server| {
11402 let task_restarts = Arc::clone(&closure_restarts);
11403 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11404 task_restarts.fetch_add(1, atomic::Ordering::Release);
11405 futures::future::ready(Ok(()))
11406 });
11407 })),
11408 ..Default::default()
11409 },
11410 );
11411
11412 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11413 let _buffer = project
11414 .update(cx, |project, cx| {
11415 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11416 })
11417 .await
11418 .unwrap();
11419 let _fake_server = fake_servers.next().await.unwrap();
11420 update_test_language_settings(cx, |language_settings| {
11421 language_settings.languages.insert(
11422 language_name.clone(),
11423 LanguageSettingsContent {
11424 tab_size: NonZeroU32::new(8),
11425 ..Default::default()
11426 },
11427 );
11428 });
11429 cx.executor().run_until_parked();
11430 assert_eq!(
11431 server_restarts.load(atomic::Ordering::Acquire),
11432 0,
11433 "Should not restart LSP server on an unrelated change"
11434 );
11435
11436 update_test_project_settings(cx, |project_settings| {
11437 project_settings.lsp.insert(
11438 "Some other server name".into(),
11439 LspSettings {
11440 binary: None,
11441 settings: None,
11442 initialization_options: Some(json!({
11443 "some other init value": false
11444 })),
11445 },
11446 );
11447 });
11448 cx.executor().run_until_parked();
11449 assert_eq!(
11450 server_restarts.load(atomic::Ordering::Acquire),
11451 0,
11452 "Should not restart LSP server on an unrelated LSP settings change"
11453 );
11454
11455 update_test_project_settings(cx, |project_settings| {
11456 project_settings.lsp.insert(
11457 language_server_name.into(),
11458 LspSettings {
11459 binary: None,
11460 settings: None,
11461 initialization_options: Some(json!({
11462 "anotherInitValue": false
11463 })),
11464 },
11465 );
11466 });
11467 cx.executor().run_until_parked();
11468 assert_eq!(
11469 server_restarts.load(atomic::Ordering::Acquire),
11470 1,
11471 "Should restart LSP server on a related LSP settings change"
11472 );
11473
11474 update_test_project_settings(cx, |project_settings| {
11475 project_settings.lsp.insert(
11476 language_server_name.into(),
11477 LspSettings {
11478 binary: None,
11479 settings: None,
11480 initialization_options: Some(json!({
11481 "anotherInitValue": false
11482 })),
11483 },
11484 );
11485 });
11486 cx.executor().run_until_parked();
11487 assert_eq!(
11488 server_restarts.load(atomic::Ordering::Acquire),
11489 1,
11490 "Should not restart LSP server on a related LSP settings change that is the same"
11491 );
11492
11493 update_test_project_settings(cx, |project_settings| {
11494 project_settings.lsp.insert(
11495 language_server_name.into(),
11496 LspSettings {
11497 binary: None,
11498 settings: None,
11499 initialization_options: None,
11500 },
11501 );
11502 });
11503 cx.executor().run_until_parked();
11504 assert_eq!(
11505 server_restarts.load(atomic::Ordering::Acquire),
11506 2,
11507 "Should restart LSP server on another related LSP settings change"
11508 );
11509}
11510
11511#[gpui::test]
11512async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11513 init_test(cx, |_| {});
11514
11515 let mut cx = EditorLspTestContext::new_rust(
11516 lsp::ServerCapabilities {
11517 completion_provider: Some(lsp::CompletionOptions {
11518 trigger_characters: Some(vec![".".to_string()]),
11519 resolve_provider: Some(true),
11520 ..Default::default()
11521 }),
11522 ..Default::default()
11523 },
11524 cx,
11525 )
11526 .await;
11527
11528 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11529 cx.simulate_keystroke(".");
11530 let completion_item = lsp::CompletionItem {
11531 label: "some".into(),
11532 kind: Some(lsp::CompletionItemKind::SNIPPET),
11533 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11534 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11535 kind: lsp::MarkupKind::Markdown,
11536 value: "```rust\nSome(2)\n```".to_string(),
11537 })),
11538 deprecated: Some(false),
11539 sort_text: Some("fffffff2".to_string()),
11540 filter_text: Some("some".to_string()),
11541 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11542 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11543 range: lsp::Range {
11544 start: lsp::Position {
11545 line: 0,
11546 character: 22,
11547 },
11548 end: lsp::Position {
11549 line: 0,
11550 character: 22,
11551 },
11552 },
11553 new_text: "Some(2)".to_string(),
11554 })),
11555 additional_text_edits: Some(vec![lsp::TextEdit {
11556 range: lsp::Range {
11557 start: lsp::Position {
11558 line: 0,
11559 character: 20,
11560 },
11561 end: lsp::Position {
11562 line: 0,
11563 character: 22,
11564 },
11565 },
11566 new_text: "".to_string(),
11567 }]),
11568 ..Default::default()
11569 };
11570
11571 let closure_completion_item = completion_item.clone();
11572 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11573 let task_completion_item = closure_completion_item.clone();
11574 async move {
11575 Ok(Some(lsp::CompletionResponse::Array(vec![
11576 task_completion_item,
11577 ])))
11578 }
11579 });
11580
11581 request.next().await;
11582
11583 cx.condition(|editor, _| editor.context_menu_visible())
11584 .await;
11585 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11586 editor
11587 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11588 .unwrap()
11589 });
11590 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11591
11592 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11593 let task_completion_item = completion_item.clone();
11594 async move { Ok(task_completion_item) }
11595 })
11596 .next()
11597 .await
11598 .unwrap();
11599 apply_additional_edits.await.unwrap();
11600 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11601}
11602
11603#[gpui::test]
11604async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11605 init_test(cx, |_| {});
11606
11607 let mut cx = EditorLspTestContext::new_rust(
11608 lsp::ServerCapabilities {
11609 completion_provider: Some(lsp::CompletionOptions {
11610 trigger_characters: Some(vec![".".to_string()]),
11611 resolve_provider: Some(true),
11612 ..Default::default()
11613 }),
11614 ..Default::default()
11615 },
11616 cx,
11617 )
11618 .await;
11619
11620 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11621 cx.simulate_keystroke(".");
11622
11623 let item1 = lsp::CompletionItem {
11624 label: "method id()".to_string(),
11625 filter_text: Some("id".to_string()),
11626 detail: None,
11627 documentation: None,
11628 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11629 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11630 new_text: ".id".to_string(),
11631 })),
11632 ..lsp::CompletionItem::default()
11633 };
11634
11635 let item2 = lsp::CompletionItem {
11636 label: "other".to_string(),
11637 filter_text: Some("other".to_string()),
11638 detail: None,
11639 documentation: None,
11640 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11641 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11642 new_text: ".other".to_string(),
11643 })),
11644 ..lsp::CompletionItem::default()
11645 };
11646
11647 let item1 = item1.clone();
11648 cx.handle_request::<lsp::request::Completion, _, _>({
11649 let item1 = item1.clone();
11650 move |_, _, _| {
11651 let item1 = item1.clone();
11652 let item2 = item2.clone();
11653 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11654 }
11655 })
11656 .next()
11657 .await;
11658
11659 cx.condition(|editor, _| editor.context_menu_visible())
11660 .await;
11661 cx.update_editor(|editor, _, _| {
11662 let context_menu = editor.context_menu.borrow_mut();
11663 let context_menu = context_menu
11664 .as_ref()
11665 .expect("Should have the context menu deployed");
11666 match context_menu {
11667 CodeContextMenu::Completions(completions_menu) => {
11668 let completions = completions_menu.completions.borrow_mut();
11669 assert_eq!(
11670 completions
11671 .iter()
11672 .map(|completion| &completion.label.text)
11673 .collect::<Vec<_>>(),
11674 vec!["method id()", "other"]
11675 )
11676 }
11677 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11678 }
11679 });
11680
11681 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11682 let item1 = item1.clone();
11683 move |_, item_to_resolve, _| {
11684 let item1 = item1.clone();
11685 async move {
11686 if item1 == item_to_resolve {
11687 Ok(lsp::CompletionItem {
11688 label: "method id()".to_string(),
11689 filter_text: Some("id".to_string()),
11690 detail: Some("Now resolved!".to_string()),
11691 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11692 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11693 range: lsp::Range::new(
11694 lsp::Position::new(0, 22),
11695 lsp::Position::new(0, 22),
11696 ),
11697 new_text: ".id".to_string(),
11698 })),
11699 ..lsp::CompletionItem::default()
11700 })
11701 } else {
11702 Ok(item_to_resolve)
11703 }
11704 }
11705 }
11706 })
11707 .next()
11708 .await
11709 .unwrap();
11710 cx.run_until_parked();
11711
11712 cx.update_editor(|editor, window, cx| {
11713 editor.context_menu_next(&Default::default(), window, cx);
11714 });
11715
11716 cx.update_editor(|editor, _, _| {
11717 let context_menu = editor.context_menu.borrow_mut();
11718 let context_menu = context_menu
11719 .as_ref()
11720 .expect("Should have the context menu deployed");
11721 match context_menu {
11722 CodeContextMenu::Completions(completions_menu) => {
11723 let completions = completions_menu.completions.borrow_mut();
11724 assert_eq!(
11725 completions
11726 .iter()
11727 .map(|completion| &completion.label.text)
11728 .collect::<Vec<_>>(),
11729 vec!["method id() Now resolved!", "other"],
11730 "Should update first completion label, but not second as the filter text did not match."
11731 );
11732 }
11733 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11734 }
11735 });
11736}
11737
11738#[gpui::test]
11739async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
11740 init_test(cx, |_| {});
11741
11742 let mut cx = EditorLspTestContext::new_rust(
11743 lsp::ServerCapabilities {
11744 completion_provider: Some(lsp::CompletionOptions {
11745 trigger_characters: Some(vec![".".to_string()]),
11746 resolve_provider: Some(true),
11747 ..Default::default()
11748 }),
11749 ..Default::default()
11750 },
11751 cx,
11752 )
11753 .await;
11754
11755 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11756 cx.simulate_keystroke(".");
11757
11758 let unresolved_item_1 = lsp::CompletionItem {
11759 label: "id".to_string(),
11760 filter_text: Some("id".to_string()),
11761 detail: None,
11762 documentation: None,
11763 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11764 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11765 new_text: ".id".to_string(),
11766 })),
11767 ..lsp::CompletionItem::default()
11768 };
11769 let resolved_item_1 = lsp::CompletionItem {
11770 additional_text_edits: Some(vec![lsp::TextEdit {
11771 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11772 new_text: "!!".to_string(),
11773 }]),
11774 ..unresolved_item_1.clone()
11775 };
11776 let unresolved_item_2 = lsp::CompletionItem {
11777 label: "other".to_string(),
11778 filter_text: Some("other".to_string()),
11779 detail: None,
11780 documentation: None,
11781 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11782 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11783 new_text: ".other".to_string(),
11784 })),
11785 ..lsp::CompletionItem::default()
11786 };
11787 let resolved_item_2 = lsp::CompletionItem {
11788 additional_text_edits: Some(vec![lsp::TextEdit {
11789 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11790 new_text: "??".to_string(),
11791 }]),
11792 ..unresolved_item_2.clone()
11793 };
11794
11795 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11796 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11797 cx.lsp
11798 .server
11799 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11800 let unresolved_item_1 = unresolved_item_1.clone();
11801 let resolved_item_1 = resolved_item_1.clone();
11802 let unresolved_item_2 = unresolved_item_2.clone();
11803 let resolved_item_2 = resolved_item_2.clone();
11804 let resolve_requests_1 = resolve_requests_1.clone();
11805 let resolve_requests_2 = resolve_requests_2.clone();
11806 move |unresolved_request, _| {
11807 let unresolved_item_1 = unresolved_item_1.clone();
11808 let resolved_item_1 = resolved_item_1.clone();
11809 let unresolved_item_2 = unresolved_item_2.clone();
11810 let resolved_item_2 = resolved_item_2.clone();
11811 let resolve_requests_1 = resolve_requests_1.clone();
11812 let resolve_requests_2 = resolve_requests_2.clone();
11813 async move {
11814 if unresolved_request == unresolved_item_1 {
11815 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11816 Ok(resolved_item_1.clone())
11817 } else if unresolved_request == unresolved_item_2 {
11818 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11819 Ok(resolved_item_2.clone())
11820 } else {
11821 panic!("Unexpected completion item {unresolved_request:?}")
11822 }
11823 }
11824 }
11825 })
11826 .detach();
11827
11828 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11829 let unresolved_item_1 = unresolved_item_1.clone();
11830 let unresolved_item_2 = unresolved_item_2.clone();
11831 async move {
11832 Ok(Some(lsp::CompletionResponse::Array(vec![
11833 unresolved_item_1,
11834 unresolved_item_2,
11835 ])))
11836 }
11837 })
11838 .next()
11839 .await;
11840
11841 cx.condition(|editor, _| editor.context_menu_visible())
11842 .await;
11843 cx.update_editor(|editor, _, _| {
11844 let context_menu = editor.context_menu.borrow_mut();
11845 let context_menu = context_menu
11846 .as_ref()
11847 .expect("Should have the context menu deployed");
11848 match context_menu {
11849 CodeContextMenu::Completions(completions_menu) => {
11850 let completions = completions_menu.completions.borrow_mut();
11851 assert_eq!(
11852 completions
11853 .iter()
11854 .map(|completion| &completion.label.text)
11855 .collect::<Vec<_>>(),
11856 vec!["id", "other"]
11857 )
11858 }
11859 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11860 }
11861 });
11862 cx.run_until_parked();
11863
11864 cx.update_editor(|editor, window, cx| {
11865 editor.context_menu_next(&ContextMenuNext, window, cx);
11866 });
11867 cx.run_until_parked();
11868 cx.update_editor(|editor, window, cx| {
11869 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11870 });
11871 cx.run_until_parked();
11872 cx.update_editor(|editor, window, cx| {
11873 editor.context_menu_next(&ContextMenuNext, window, cx);
11874 });
11875 cx.run_until_parked();
11876 cx.update_editor(|editor, window, cx| {
11877 editor
11878 .compose_completion(&ComposeCompletion::default(), window, cx)
11879 .expect("No task returned")
11880 })
11881 .await
11882 .expect("Completion failed");
11883 cx.run_until_parked();
11884
11885 cx.update_editor(|editor, _, cx| {
11886 assert_eq!(
11887 resolve_requests_1.load(atomic::Ordering::Acquire),
11888 1,
11889 "Should always resolve once despite multiple selections"
11890 );
11891 assert_eq!(
11892 resolve_requests_2.load(atomic::Ordering::Acquire),
11893 1,
11894 "Should always resolve once after multiple selections and applying the completion"
11895 );
11896 assert_eq!(
11897 editor.text(cx),
11898 "fn main() { let a = ??.other; }",
11899 "Should use resolved data when applying the completion"
11900 );
11901 });
11902}
11903
11904#[gpui::test]
11905async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
11906 init_test(cx, |_| {});
11907
11908 let item_0 = lsp::CompletionItem {
11909 label: "abs".into(),
11910 insert_text: Some("abs".into()),
11911 data: Some(json!({ "very": "special"})),
11912 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11913 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11914 lsp::InsertReplaceEdit {
11915 new_text: "abs".to_string(),
11916 insert: lsp::Range::default(),
11917 replace: lsp::Range::default(),
11918 },
11919 )),
11920 ..lsp::CompletionItem::default()
11921 };
11922 let items = iter::once(item_0.clone())
11923 .chain((11..51).map(|i| lsp::CompletionItem {
11924 label: format!("item_{}", i),
11925 insert_text: Some(format!("item_{}", i)),
11926 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11927 ..lsp::CompletionItem::default()
11928 }))
11929 .collect::<Vec<_>>();
11930
11931 let default_commit_characters = vec!["?".to_string()];
11932 let default_data = json!({ "default": "data"});
11933 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11934 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11935 let default_edit_range = lsp::Range {
11936 start: lsp::Position {
11937 line: 0,
11938 character: 5,
11939 },
11940 end: lsp::Position {
11941 line: 0,
11942 character: 5,
11943 },
11944 };
11945
11946 let item_0_out = lsp::CompletionItem {
11947 commit_characters: Some(default_commit_characters.clone()),
11948 insert_text_format: Some(default_insert_text_format),
11949 ..item_0
11950 };
11951 let items_out = iter::once(item_0_out)
11952 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11953 commit_characters: Some(default_commit_characters.clone()),
11954 data: Some(default_data.clone()),
11955 insert_text_mode: Some(default_insert_text_mode),
11956 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11957 range: default_edit_range,
11958 new_text: item.label.clone(),
11959 })),
11960 ..item.clone()
11961 }))
11962 .collect::<Vec<lsp::CompletionItem>>();
11963
11964 let mut cx = EditorLspTestContext::new_rust(
11965 lsp::ServerCapabilities {
11966 completion_provider: Some(lsp::CompletionOptions {
11967 trigger_characters: Some(vec![".".to_string()]),
11968 resolve_provider: Some(true),
11969 ..Default::default()
11970 }),
11971 ..Default::default()
11972 },
11973 cx,
11974 )
11975 .await;
11976
11977 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11978 cx.simulate_keystroke(".");
11979
11980 let completion_data = default_data.clone();
11981 let completion_characters = default_commit_characters.clone();
11982 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11983 let default_data = completion_data.clone();
11984 let default_commit_characters = completion_characters.clone();
11985 let items = items.clone();
11986 async move {
11987 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11988 items,
11989 item_defaults: Some(lsp::CompletionListItemDefaults {
11990 data: Some(default_data.clone()),
11991 commit_characters: Some(default_commit_characters.clone()),
11992 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11993 default_edit_range,
11994 )),
11995 insert_text_format: Some(default_insert_text_format),
11996 insert_text_mode: Some(default_insert_text_mode),
11997 }),
11998 ..lsp::CompletionList::default()
11999 })))
12000 }
12001 })
12002 .next()
12003 .await;
12004
12005 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12006 cx.lsp
12007 .server
12008 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12009 let closure_resolved_items = resolved_items.clone();
12010 move |item_to_resolve, _| {
12011 let closure_resolved_items = closure_resolved_items.clone();
12012 async move {
12013 closure_resolved_items.lock().push(item_to_resolve.clone());
12014 Ok(item_to_resolve)
12015 }
12016 }
12017 })
12018 .detach();
12019
12020 cx.condition(|editor, _| editor.context_menu_visible())
12021 .await;
12022 cx.run_until_parked();
12023 cx.update_editor(|editor, _, _| {
12024 let menu = editor.context_menu.borrow_mut();
12025 match menu.as_ref().expect("should have the completions menu") {
12026 CodeContextMenu::Completions(completions_menu) => {
12027 assert_eq!(
12028 completions_menu
12029 .entries
12030 .borrow()
12031 .iter()
12032 .map(|mat| mat.string.clone())
12033 .collect::<Vec<String>>(),
12034 items_out
12035 .iter()
12036 .map(|completion| completion.label.clone())
12037 .collect::<Vec<String>>()
12038 );
12039 }
12040 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12041 }
12042 });
12043 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12044 // with 4 from the end.
12045 assert_eq!(
12046 *resolved_items.lock(),
12047 [
12048 &items_out[0..16],
12049 &items_out[items_out.len() - 4..items_out.len()]
12050 ]
12051 .concat()
12052 .iter()
12053 .cloned()
12054 .collect::<Vec<lsp::CompletionItem>>()
12055 );
12056 resolved_items.lock().clear();
12057
12058 cx.update_editor(|editor, window, cx| {
12059 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12060 });
12061 cx.run_until_parked();
12062 // Completions that have already been resolved are skipped.
12063 assert_eq!(
12064 *resolved_items.lock(),
12065 items_out[items_out.len() - 16..items_out.len() - 4]
12066 .iter()
12067 .cloned()
12068 .collect::<Vec<lsp::CompletionItem>>()
12069 );
12070 resolved_items.lock().clear();
12071}
12072
12073#[gpui::test]
12074async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12075 init_test(cx, |_| {});
12076
12077 let mut cx = EditorLspTestContext::new(
12078 Language::new(
12079 LanguageConfig {
12080 matcher: LanguageMatcher {
12081 path_suffixes: vec!["jsx".into()],
12082 ..Default::default()
12083 },
12084 overrides: [(
12085 "element".into(),
12086 LanguageConfigOverride {
12087 word_characters: Override::Set(['-'].into_iter().collect()),
12088 ..Default::default()
12089 },
12090 )]
12091 .into_iter()
12092 .collect(),
12093 ..Default::default()
12094 },
12095 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12096 )
12097 .with_override_query("(jsx_self_closing_element) @element")
12098 .unwrap(),
12099 lsp::ServerCapabilities {
12100 completion_provider: Some(lsp::CompletionOptions {
12101 trigger_characters: Some(vec![":".to_string()]),
12102 ..Default::default()
12103 }),
12104 ..Default::default()
12105 },
12106 cx,
12107 )
12108 .await;
12109
12110 cx.lsp
12111 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12112 Ok(Some(lsp::CompletionResponse::Array(vec![
12113 lsp::CompletionItem {
12114 label: "bg-blue".into(),
12115 ..Default::default()
12116 },
12117 lsp::CompletionItem {
12118 label: "bg-red".into(),
12119 ..Default::default()
12120 },
12121 lsp::CompletionItem {
12122 label: "bg-yellow".into(),
12123 ..Default::default()
12124 },
12125 ])))
12126 });
12127
12128 cx.set_state(r#"<p class="bgˇ" />"#);
12129
12130 // Trigger completion when typing a dash, because the dash is an extra
12131 // word character in the 'element' scope, which contains the cursor.
12132 cx.simulate_keystroke("-");
12133 cx.executor().run_until_parked();
12134 cx.update_editor(|editor, _, _| {
12135 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12136 {
12137 assert_eq!(
12138 completion_menu_entries(&menu),
12139 &["bg-red", "bg-blue", "bg-yellow"]
12140 );
12141 } else {
12142 panic!("expected completion menu to be open");
12143 }
12144 });
12145
12146 cx.simulate_keystroke("l");
12147 cx.executor().run_until_parked();
12148 cx.update_editor(|editor, _, _| {
12149 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12150 {
12151 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12152 } else {
12153 panic!("expected completion menu to be open");
12154 }
12155 });
12156
12157 // When filtering completions, consider the character after the '-' to
12158 // be the start of a subword.
12159 cx.set_state(r#"<p class="yelˇ" />"#);
12160 cx.simulate_keystroke("l");
12161 cx.executor().run_until_parked();
12162 cx.update_editor(|editor, _, _| {
12163 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12164 {
12165 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12166 } else {
12167 panic!("expected completion menu to be open");
12168 }
12169 });
12170}
12171
12172fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12173 let entries = menu.entries.borrow();
12174 entries.iter().map(|mat| mat.string.clone()).collect()
12175}
12176
12177#[gpui::test]
12178async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12179 init_test(cx, |settings| {
12180 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12181 FormatterList(vec![Formatter::Prettier].into()),
12182 ))
12183 });
12184
12185 let fs = FakeFs::new(cx.executor());
12186 fs.insert_file(path!("/file.ts"), Default::default()).await;
12187
12188 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12189 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12190
12191 language_registry.add(Arc::new(Language::new(
12192 LanguageConfig {
12193 name: "TypeScript".into(),
12194 matcher: LanguageMatcher {
12195 path_suffixes: vec!["ts".to_string()],
12196 ..Default::default()
12197 },
12198 ..Default::default()
12199 },
12200 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12201 )));
12202 update_test_language_settings(cx, |settings| {
12203 settings.defaults.prettier = Some(PrettierSettings {
12204 allowed: true,
12205 ..PrettierSettings::default()
12206 });
12207 });
12208
12209 let test_plugin = "test_plugin";
12210 let _ = language_registry.register_fake_lsp(
12211 "TypeScript",
12212 FakeLspAdapter {
12213 prettier_plugins: vec![test_plugin],
12214 ..Default::default()
12215 },
12216 );
12217
12218 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12219 let buffer = project
12220 .update(cx, |project, cx| {
12221 project.open_local_buffer(path!("/file.ts"), cx)
12222 })
12223 .await
12224 .unwrap();
12225
12226 let buffer_text = "one\ntwo\nthree\n";
12227 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12228 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12229 editor.update_in(cx, |editor, window, cx| {
12230 editor.set_text(buffer_text, window, cx)
12231 });
12232
12233 editor
12234 .update_in(cx, |editor, window, cx| {
12235 editor.perform_format(
12236 project.clone(),
12237 FormatTrigger::Manual,
12238 FormatTarget::Buffers,
12239 window,
12240 cx,
12241 )
12242 })
12243 .unwrap()
12244 .await;
12245 assert_eq!(
12246 editor.update(cx, |editor, cx| editor.text(cx)),
12247 buffer_text.to_string() + prettier_format_suffix,
12248 "Test prettier formatting was not applied to the original buffer text",
12249 );
12250
12251 update_test_language_settings(cx, |settings| {
12252 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12253 });
12254 let format = editor.update_in(cx, |editor, window, cx| {
12255 editor.perform_format(
12256 project.clone(),
12257 FormatTrigger::Manual,
12258 FormatTarget::Buffers,
12259 window,
12260 cx,
12261 )
12262 });
12263 format.await.unwrap();
12264 assert_eq!(
12265 editor.update(cx, |editor, cx| editor.text(cx)),
12266 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12267 "Autoformatting (via test prettier) was not applied to the original buffer text",
12268 );
12269}
12270
12271#[gpui::test]
12272async fn test_addition_reverts(cx: &mut TestAppContext) {
12273 init_test(cx, |_| {});
12274 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12275 let base_text = indoc! {r#"
12276 struct Row;
12277 struct Row1;
12278 struct Row2;
12279
12280 struct Row4;
12281 struct Row5;
12282 struct Row6;
12283
12284 struct Row8;
12285 struct Row9;
12286 struct Row10;"#};
12287
12288 // When addition hunks are not adjacent to carets, no hunk revert is performed
12289 assert_hunk_revert(
12290 indoc! {r#"struct Row;
12291 struct Row1;
12292 struct Row1.1;
12293 struct Row1.2;
12294 struct Row2;ˇ
12295
12296 struct Row4;
12297 struct Row5;
12298 struct Row6;
12299
12300 struct Row8;
12301 ˇstruct Row9;
12302 struct Row9.1;
12303 struct Row9.2;
12304 struct Row9.3;
12305 struct Row10;"#},
12306 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12307 indoc! {r#"struct Row;
12308 struct Row1;
12309 struct Row1.1;
12310 struct Row1.2;
12311 struct Row2;ˇ
12312
12313 struct Row4;
12314 struct Row5;
12315 struct Row6;
12316
12317 struct Row8;
12318 ˇstruct Row9;
12319 struct Row9.1;
12320 struct Row9.2;
12321 struct Row9.3;
12322 struct Row10;"#},
12323 base_text,
12324 &mut cx,
12325 );
12326 // Same for selections
12327 assert_hunk_revert(
12328 indoc! {r#"struct Row;
12329 struct Row1;
12330 struct Row2;
12331 struct Row2.1;
12332 struct Row2.2;
12333 «ˇ
12334 struct Row4;
12335 struct» Row5;
12336 «struct Row6;
12337 ˇ»
12338 struct Row9.1;
12339 struct Row9.2;
12340 struct Row9.3;
12341 struct Row8;
12342 struct Row9;
12343 struct Row10;"#},
12344 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12345 indoc! {r#"struct Row;
12346 struct Row1;
12347 struct Row2;
12348 struct Row2.1;
12349 struct Row2.2;
12350 «ˇ
12351 struct Row4;
12352 struct» Row5;
12353 «struct Row6;
12354 ˇ»
12355 struct Row9.1;
12356 struct Row9.2;
12357 struct Row9.3;
12358 struct Row8;
12359 struct Row9;
12360 struct Row10;"#},
12361 base_text,
12362 &mut cx,
12363 );
12364
12365 // When carets and selections intersect the addition hunks, those are reverted.
12366 // Adjacent carets got merged.
12367 assert_hunk_revert(
12368 indoc! {r#"struct Row;
12369 ˇ// something on the top
12370 struct Row1;
12371 struct Row2;
12372 struct Roˇw3.1;
12373 struct Row2.2;
12374 struct Row2.3;ˇ
12375
12376 struct Row4;
12377 struct ˇRow5.1;
12378 struct Row5.2;
12379 struct «Rowˇ»5.3;
12380 struct Row5;
12381 struct Row6;
12382 ˇ
12383 struct Row9.1;
12384 struct «Rowˇ»9.2;
12385 struct «ˇRow»9.3;
12386 struct Row8;
12387 struct Row9;
12388 «ˇ// something on bottom»
12389 struct Row10;"#},
12390 vec![
12391 DiffHunkStatus::added_none(),
12392 DiffHunkStatus::added_none(),
12393 DiffHunkStatus::added_none(),
12394 DiffHunkStatus::added_none(),
12395 DiffHunkStatus::added_none(),
12396 ],
12397 indoc! {r#"struct Row;
12398 ˇstruct Row1;
12399 struct Row2;
12400 ˇ
12401 struct Row4;
12402 ˇstruct Row5;
12403 struct Row6;
12404 ˇ
12405 ˇstruct Row8;
12406 struct Row9;
12407 ˇstruct Row10;"#},
12408 base_text,
12409 &mut cx,
12410 );
12411}
12412
12413#[gpui::test]
12414async fn test_modification_reverts(cx: &mut TestAppContext) {
12415 init_test(cx, |_| {});
12416 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12417 let base_text = indoc! {r#"
12418 struct Row;
12419 struct Row1;
12420 struct Row2;
12421
12422 struct Row4;
12423 struct Row5;
12424 struct Row6;
12425
12426 struct Row8;
12427 struct Row9;
12428 struct Row10;"#};
12429
12430 // Modification hunks behave the same as the addition ones.
12431 assert_hunk_revert(
12432 indoc! {r#"struct Row;
12433 struct Row1;
12434 struct Row33;
12435 ˇ
12436 struct Row4;
12437 struct Row5;
12438 struct Row6;
12439 ˇ
12440 struct Row99;
12441 struct Row9;
12442 struct Row10;"#},
12443 vec![
12444 DiffHunkStatus::modified_none(),
12445 DiffHunkStatus::modified_none(),
12446 ],
12447 indoc! {r#"struct Row;
12448 struct Row1;
12449 struct Row33;
12450 ˇ
12451 struct Row4;
12452 struct Row5;
12453 struct Row6;
12454 ˇ
12455 struct Row99;
12456 struct Row9;
12457 struct Row10;"#},
12458 base_text,
12459 &mut cx,
12460 );
12461 assert_hunk_revert(
12462 indoc! {r#"struct Row;
12463 struct Row1;
12464 struct Row33;
12465 «ˇ
12466 struct Row4;
12467 struct» Row5;
12468 «struct Row6;
12469 ˇ»
12470 struct Row99;
12471 struct Row9;
12472 struct Row10;"#},
12473 vec![
12474 DiffHunkStatus::modified_none(),
12475 DiffHunkStatus::modified_none(),
12476 ],
12477 indoc! {r#"struct Row;
12478 struct Row1;
12479 struct Row33;
12480 «ˇ
12481 struct Row4;
12482 struct» Row5;
12483 «struct Row6;
12484 ˇ»
12485 struct Row99;
12486 struct Row9;
12487 struct Row10;"#},
12488 base_text,
12489 &mut cx,
12490 );
12491
12492 assert_hunk_revert(
12493 indoc! {r#"ˇstruct Row1.1;
12494 struct Row1;
12495 «ˇstr»uct Row22;
12496
12497 struct ˇRow44;
12498 struct Row5;
12499 struct «Rˇ»ow66;ˇ
12500
12501 «struˇ»ct Row88;
12502 struct Row9;
12503 struct Row1011;ˇ"#},
12504 vec![
12505 DiffHunkStatus::modified_none(),
12506 DiffHunkStatus::modified_none(),
12507 DiffHunkStatus::modified_none(),
12508 DiffHunkStatus::modified_none(),
12509 DiffHunkStatus::modified_none(),
12510 DiffHunkStatus::modified_none(),
12511 ],
12512 indoc! {r#"struct Row;
12513 ˇstruct Row1;
12514 struct Row2;
12515 ˇ
12516 struct Row4;
12517 ˇstruct Row5;
12518 struct Row6;
12519 ˇ
12520 struct Row8;
12521 ˇstruct Row9;
12522 struct Row10;ˇ"#},
12523 base_text,
12524 &mut cx,
12525 );
12526}
12527
12528#[gpui::test]
12529async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12530 init_test(cx, |_| {});
12531 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12532 let base_text = indoc! {r#"
12533 one
12534
12535 two
12536 three
12537 "#};
12538
12539 cx.set_head_text(base_text);
12540 cx.set_state("\nˇ\n");
12541 cx.executor().run_until_parked();
12542 cx.update_editor(|editor, _window, cx| {
12543 editor.expand_selected_diff_hunks(cx);
12544 });
12545 cx.executor().run_until_parked();
12546 cx.update_editor(|editor, window, cx| {
12547 editor.backspace(&Default::default(), window, cx);
12548 });
12549 cx.run_until_parked();
12550 cx.assert_state_with_diff(
12551 indoc! {r#"
12552
12553 - two
12554 - threeˇ
12555 +
12556 "#}
12557 .to_string(),
12558 );
12559}
12560
12561#[gpui::test]
12562async fn test_deletion_reverts(cx: &mut TestAppContext) {
12563 init_test(cx, |_| {});
12564 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12565 let base_text = indoc! {r#"struct Row;
12566struct Row1;
12567struct Row2;
12568
12569struct Row4;
12570struct Row5;
12571struct Row6;
12572
12573struct Row8;
12574struct Row9;
12575struct Row10;"#};
12576
12577 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12578 assert_hunk_revert(
12579 indoc! {r#"struct Row;
12580 struct Row2;
12581
12582 ˇstruct Row4;
12583 struct Row5;
12584 struct Row6;
12585 ˇ
12586 struct Row8;
12587 struct Row10;"#},
12588 vec![
12589 DiffHunkStatus::deleted_none(),
12590 DiffHunkStatus::deleted_none(),
12591 ],
12592 indoc! {r#"struct Row;
12593 struct Row2;
12594
12595 ˇstruct Row4;
12596 struct Row5;
12597 struct Row6;
12598 ˇ
12599 struct Row8;
12600 struct Row10;"#},
12601 base_text,
12602 &mut cx,
12603 );
12604 assert_hunk_revert(
12605 indoc! {r#"struct Row;
12606 struct Row2;
12607
12608 «ˇstruct Row4;
12609 struct» Row5;
12610 «struct Row6;
12611 ˇ»
12612 struct Row8;
12613 struct Row10;"#},
12614 vec![
12615 DiffHunkStatus::deleted_none(),
12616 DiffHunkStatus::deleted_none(),
12617 ],
12618 indoc! {r#"struct Row;
12619 struct Row2;
12620
12621 «ˇstruct Row4;
12622 struct» Row5;
12623 «struct Row6;
12624 ˇ»
12625 struct Row8;
12626 struct Row10;"#},
12627 base_text,
12628 &mut cx,
12629 );
12630
12631 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12632 assert_hunk_revert(
12633 indoc! {r#"struct Row;
12634 ˇstruct Row2;
12635
12636 struct Row4;
12637 struct Row5;
12638 struct Row6;
12639
12640 struct Row8;ˇ
12641 struct Row10;"#},
12642 vec![
12643 DiffHunkStatus::deleted_none(),
12644 DiffHunkStatus::deleted_none(),
12645 ],
12646 indoc! {r#"struct Row;
12647 struct Row1;
12648 ˇstruct Row2;
12649
12650 struct Row4;
12651 struct Row5;
12652 struct Row6;
12653
12654 struct Row8;ˇ
12655 struct Row9;
12656 struct Row10;"#},
12657 base_text,
12658 &mut cx,
12659 );
12660 assert_hunk_revert(
12661 indoc! {r#"struct Row;
12662 struct Row2«ˇ;
12663 struct Row4;
12664 struct» Row5;
12665 «struct Row6;
12666
12667 struct Row8;ˇ»
12668 struct Row10;"#},
12669 vec![
12670 DiffHunkStatus::deleted_none(),
12671 DiffHunkStatus::deleted_none(),
12672 DiffHunkStatus::deleted_none(),
12673 ],
12674 indoc! {r#"struct Row;
12675 struct Row1;
12676 struct Row2«ˇ;
12677
12678 struct Row4;
12679 struct» Row5;
12680 «struct Row6;
12681
12682 struct Row8;ˇ»
12683 struct Row9;
12684 struct Row10;"#},
12685 base_text,
12686 &mut cx,
12687 );
12688}
12689
12690#[gpui::test]
12691async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
12692 init_test(cx, |_| {});
12693
12694 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12695 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12696 let base_text_3 =
12697 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12698
12699 let text_1 = edit_first_char_of_every_line(base_text_1);
12700 let text_2 = edit_first_char_of_every_line(base_text_2);
12701 let text_3 = edit_first_char_of_every_line(base_text_3);
12702
12703 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12704 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12705 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12706
12707 let multibuffer = cx.new(|cx| {
12708 let mut multibuffer = MultiBuffer::new(ReadWrite);
12709 multibuffer.push_excerpts(
12710 buffer_1.clone(),
12711 [
12712 ExcerptRange {
12713 context: Point::new(0, 0)..Point::new(3, 0),
12714 primary: None,
12715 },
12716 ExcerptRange {
12717 context: Point::new(5, 0)..Point::new(7, 0),
12718 primary: None,
12719 },
12720 ExcerptRange {
12721 context: Point::new(9, 0)..Point::new(10, 4),
12722 primary: None,
12723 },
12724 ],
12725 cx,
12726 );
12727 multibuffer.push_excerpts(
12728 buffer_2.clone(),
12729 [
12730 ExcerptRange {
12731 context: Point::new(0, 0)..Point::new(3, 0),
12732 primary: None,
12733 },
12734 ExcerptRange {
12735 context: Point::new(5, 0)..Point::new(7, 0),
12736 primary: None,
12737 },
12738 ExcerptRange {
12739 context: Point::new(9, 0)..Point::new(10, 4),
12740 primary: None,
12741 },
12742 ],
12743 cx,
12744 );
12745 multibuffer.push_excerpts(
12746 buffer_3.clone(),
12747 [
12748 ExcerptRange {
12749 context: Point::new(0, 0)..Point::new(3, 0),
12750 primary: None,
12751 },
12752 ExcerptRange {
12753 context: Point::new(5, 0)..Point::new(7, 0),
12754 primary: None,
12755 },
12756 ExcerptRange {
12757 context: Point::new(9, 0)..Point::new(10, 4),
12758 primary: None,
12759 },
12760 ],
12761 cx,
12762 );
12763 multibuffer
12764 });
12765
12766 let fs = FakeFs::new(cx.executor());
12767 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12768 let (editor, cx) = cx
12769 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
12770 editor.update_in(cx, |editor, _window, cx| {
12771 for (buffer, diff_base) in [
12772 (buffer_1.clone(), base_text_1),
12773 (buffer_2.clone(), base_text_2),
12774 (buffer_3.clone(), base_text_3),
12775 ] {
12776 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
12777 editor
12778 .buffer
12779 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
12780 }
12781 });
12782 cx.executor().run_until_parked();
12783
12784 editor.update_in(cx, |editor, window, cx| {
12785 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}");
12786 editor.select_all(&SelectAll, window, cx);
12787 editor.git_restore(&Default::default(), window, cx);
12788 });
12789 cx.executor().run_until_parked();
12790
12791 // When all ranges are selected, all buffer hunks are reverted.
12792 editor.update(cx, |editor, cx| {
12793 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");
12794 });
12795 buffer_1.update(cx, |buffer, _| {
12796 assert_eq!(buffer.text(), base_text_1);
12797 });
12798 buffer_2.update(cx, |buffer, _| {
12799 assert_eq!(buffer.text(), base_text_2);
12800 });
12801 buffer_3.update(cx, |buffer, _| {
12802 assert_eq!(buffer.text(), base_text_3);
12803 });
12804
12805 editor.update_in(cx, |editor, window, cx| {
12806 editor.undo(&Default::default(), window, cx);
12807 });
12808
12809 editor.update_in(cx, |editor, window, cx| {
12810 editor.change_selections(None, window, cx, |s| {
12811 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12812 });
12813 editor.git_restore(&Default::default(), window, cx);
12814 });
12815
12816 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12817 // but not affect buffer_2 and its related excerpts.
12818 editor.update(cx, |editor, cx| {
12819 assert_eq!(
12820 editor.text(cx),
12821 "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}"
12822 );
12823 });
12824 buffer_1.update(cx, |buffer, _| {
12825 assert_eq!(buffer.text(), base_text_1);
12826 });
12827 buffer_2.update(cx, |buffer, _| {
12828 assert_eq!(
12829 buffer.text(),
12830 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12831 );
12832 });
12833 buffer_3.update(cx, |buffer, _| {
12834 assert_eq!(
12835 buffer.text(),
12836 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12837 );
12838 });
12839
12840 fn edit_first_char_of_every_line(text: &str) -> String {
12841 text.split('\n')
12842 .map(|line| format!("X{}", &line[1..]))
12843 .collect::<Vec<_>>()
12844 .join("\n")
12845 }
12846}
12847
12848#[gpui::test]
12849async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
12850 init_test(cx, |_| {});
12851
12852 let cols = 4;
12853 let rows = 10;
12854 let sample_text_1 = sample_text(rows, cols, 'a');
12855 assert_eq!(
12856 sample_text_1,
12857 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12858 );
12859 let sample_text_2 = sample_text(rows, cols, 'l');
12860 assert_eq!(
12861 sample_text_2,
12862 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12863 );
12864 let sample_text_3 = sample_text(rows, cols, 'v');
12865 assert_eq!(
12866 sample_text_3,
12867 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12868 );
12869
12870 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12871 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12872 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12873
12874 let multi_buffer = cx.new(|cx| {
12875 let mut multibuffer = MultiBuffer::new(ReadWrite);
12876 multibuffer.push_excerpts(
12877 buffer_1.clone(),
12878 [
12879 ExcerptRange {
12880 context: Point::new(0, 0)..Point::new(3, 0),
12881 primary: None,
12882 },
12883 ExcerptRange {
12884 context: Point::new(5, 0)..Point::new(7, 0),
12885 primary: None,
12886 },
12887 ExcerptRange {
12888 context: Point::new(9, 0)..Point::new(10, 4),
12889 primary: None,
12890 },
12891 ],
12892 cx,
12893 );
12894 multibuffer.push_excerpts(
12895 buffer_2.clone(),
12896 [
12897 ExcerptRange {
12898 context: Point::new(0, 0)..Point::new(3, 0),
12899 primary: None,
12900 },
12901 ExcerptRange {
12902 context: Point::new(5, 0)..Point::new(7, 0),
12903 primary: None,
12904 },
12905 ExcerptRange {
12906 context: Point::new(9, 0)..Point::new(10, 4),
12907 primary: None,
12908 },
12909 ],
12910 cx,
12911 );
12912 multibuffer.push_excerpts(
12913 buffer_3.clone(),
12914 [
12915 ExcerptRange {
12916 context: Point::new(0, 0)..Point::new(3, 0),
12917 primary: None,
12918 },
12919 ExcerptRange {
12920 context: Point::new(5, 0)..Point::new(7, 0),
12921 primary: None,
12922 },
12923 ExcerptRange {
12924 context: Point::new(9, 0)..Point::new(10, 4),
12925 primary: None,
12926 },
12927 ],
12928 cx,
12929 );
12930 multibuffer
12931 });
12932
12933 let fs = FakeFs::new(cx.executor());
12934 fs.insert_tree(
12935 "/a",
12936 json!({
12937 "main.rs": sample_text_1,
12938 "other.rs": sample_text_2,
12939 "lib.rs": sample_text_3,
12940 }),
12941 )
12942 .await;
12943 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12944 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12945 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12946 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12947 Editor::new(
12948 EditorMode::Full,
12949 multi_buffer,
12950 Some(project.clone()),
12951 true,
12952 window,
12953 cx,
12954 )
12955 });
12956 let multibuffer_item_id = workspace
12957 .update(cx, |workspace, window, cx| {
12958 assert!(
12959 workspace.active_item(cx).is_none(),
12960 "active item should be None before the first item is added"
12961 );
12962 workspace.add_item_to_active_pane(
12963 Box::new(multi_buffer_editor.clone()),
12964 None,
12965 true,
12966 window,
12967 cx,
12968 );
12969 let active_item = workspace
12970 .active_item(cx)
12971 .expect("should have an active item after adding the multi buffer");
12972 assert!(
12973 !active_item.is_singleton(cx),
12974 "A multi buffer was expected to active after adding"
12975 );
12976 active_item.item_id()
12977 })
12978 .unwrap();
12979 cx.executor().run_until_parked();
12980
12981 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12982 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12983 s.select_ranges(Some(1..2))
12984 });
12985 editor.open_excerpts(&OpenExcerpts, window, cx);
12986 });
12987 cx.executor().run_until_parked();
12988 let first_item_id = workspace
12989 .update(cx, |workspace, window, cx| {
12990 let active_item = workspace
12991 .active_item(cx)
12992 .expect("should have an active item after navigating into the 1st buffer");
12993 let first_item_id = active_item.item_id();
12994 assert_ne!(
12995 first_item_id, multibuffer_item_id,
12996 "Should navigate into the 1st buffer and activate it"
12997 );
12998 assert!(
12999 active_item.is_singleton(cx),
13000 "New active item should be a singleton buffer"
13001 );
13002 assert_eq!(
13003 active_item
13004 .act_as::<Editor>(cx)
13005 .expect("should have navigated into an editor for the 1st buffer")
13006 .read(cx)
13007 .text(cx),
13008 sample_text_1
13009 );
13010
13011 workspace
13012 .go_back(workspace.active_pane().downgrade(), window, cx)
13013 .detach_and_log_err(cx);
13014
13015 first_item_id
13016 })
13017 .unwrap();
13018 cx.executor().run_until_parked();
13019 workspace
13020 .update(cx, |workspace, _, cx| {
13021 let active_item = workspace
13022 .active_item(cx)
13023 .expect("should have an active item after navigating back");
13024 assert_eq!(
13025 active_item.item_id(),
13026 multibuffer_item_id,
13027 "Should navigate back to the multi buffer"
13028 );
13029 assert!(!active_item.is_singleton(cx));
13030 })
13031 .unwrap();
13032
13033 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13034 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13035 s.select_ranges(Some(39..40))
13036 });
13037 editor.open_excerpts(&OpenExcerpts, window, cx);
13038 });
13039 cx.executor().run_until_parked();
13040 let second_item_id = workspace
13041 .update(cx, |workspace, window, cx| {
13042 let active_item = workspace
13043 .active_item(cx)
13044 .expect("should have an active item after navigating into the 2nd buffer");
13045 let second_item_id = active_item.item_id();
13046 assert_ne!(
13047 second_item_id, multibuffer_item_id,
13048 "Should navigate away from the multibuffer"
13049 );
13050 assert_ne!(
13051 second_item_id, first_item_id,
13052 "Should navigate into the 2nd buffer and activate it"
13053 );
13054 assert!(
13055 active_item.is_singleton(cx),
13056 "New active item should be a singleton buffer"
13057 );
13058 assert_eq!(
13059 active_item
13060 .act_as::<Editor>(cx)
13061 .expect("should have navigated into an editor")
13062 .read(cx)
13063 .text(cx),
13064 sample_text_2
13065 );
13066
13067 workspace
13068 .go_back(workspace.active_pane().downgrade(), window, cx)
13069 .detach_and_log_err(cx);
13070
13071 second_item_id
13072 })
13073 .unwrap();
13074 cx.executor().run_until_parked();
13075 workspace
13076 .update(cx, |workspace, _, cx| {
13077 let active_item = workspace
13078 .active_item(cx)
13079 .expect("should have an active item after navigating back from the 2nd buffer");
13080 assert_eq!(
13081 active_item.item_id(),
13082 multibuffer_item_id,
13083 "Should navigate back from the 2nd buffer to the multi buffer"
13084 );
13085 assert!(!active_item.is_singleton(cx));
13086 })
13087 .unwrap();
13088
13089 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13090 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13091 s.select_ranges(Some(70..70))
13092 });
13093 editor.open_excerpts(&OpenExcerpts, window, cx);
13094 });
13095 cx.executor().run_until_parked();
13096 workspace
13097 .update(cx, |workspace, window, cx| {
13098 let active_item = workspace
13099 .active_item(cx)
13100 .expect("should have an active item after navigating into the 3rd buffer");
13101 let third_item_id = active_item.item_id();
13102 assert_ne!(
13103 third_item_id, multibuffer_item_id,
13104 "Should navigate into the 3rd buffer and activate it"
13105 );
13106 assert_ne!(third_item_id, first_item_id);
13107 assert_ne!(third_item_id, second_item_id);
13108 assert!(
13109 active_item.is_singleton(cx),
13110 "New active item should be a singleton buffer"
13111 );
13112 assert_eq!(
13113 active_item
13114 .act_as::<Editor>(cx)
13115 .expect("should have navigated into an editor")
13116 .read(cx)
13117 .text(cx),
13118 sample_text_3
13119 );
13120
13121 workspace
13122 .go_back(workspace.active_pane().downgrade(), window, cx)
13123 .detach_and_log_err(cx);
13124 })
13125 .unwrap();
13126 cx.executor().run_until_parked();
13127 workspace
13128 .update(cx, |workspace, _, cx| {
13129 let active_item = workspace
13130 .active_item(cx)
13131 .expect("should have an active item after navigating back from the 3rd buffer");
13132 assert_eq!(
13133 active_item.item_id(),
13134 multibuffer_item_id,
13135 "Should navigate back from the 3rd buffer to the multi buffer"
13136 );
13137 assert!(!active_item.is_singleton(cx));
13138 })
13139 .unwrap();
13140}
13141
13142#[gpui::test]
13143async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13144 init_test(cx, |_| {});
13145
13146 let mut cx = EditorTestContext::new(cx).await;
13147
13148 let diff_base = r#"
13149 use some::mod;
13150
13151 const A: u32 = 42;
13152
13153 fn main() {
13154 println!("hello");
13155
13156 println!("world");
13157 }
13158 "#
13159 .unindent();
13160
13161 cx.set_state(
13162 &r#"
13163 use some::modified;
13164
13165 ˇ
13166 fn main() {
13167 println!("hello there");
13168
13169 println!("around the");
13170 println!("world");
13171 }
13172 "#
13173 .unindent(),
13174 );
13175
13176 cx.set_head_text(&diff_base);
13177 executor.run_until_parked();
13178
13179 cx.update_editor(|editor, window, cx| {
13180 editor.go_to_next_hunk(&GoToHunk, window, cx);
13181 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13182 });
13183 executor.run_until_parked();
13184 cx.assert_state_with_diff(
13185 r#"
13186 use some::modified;
13187
13188
13189 fn main() {
13190 - println!("hello");
13191 + ˇ println!("hello there");
13192
13193 println!("around the");
13194 println!("world");
13195 }
13196 "#
13197 .unindent(),
13198 );
13199
13200 cx.update_editor(|editor, window, cx| {
13201 for _ in 0..2 {
13202 editor.go_to_next_hunk(&GoToHunk, window, cx);
13203 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13204 }
13205 });
13206 executor.run_until_parked();
13207 cx.assert_state_with_diff(
13208 r#"
13209 - use some::mod;
13210 + ˇuse some::modified;
13211
13212
13213 fn main() {
13214 - println!("hello");
13215 + println!("hello there");
13216
13217 + println!("around the");
13218 println!("world");
13219 }
13220 "#
13221 .unindent(),
13222 );
13223
13224 cx.update_editor(|editor, window, cx| {
13225 editor.go_to_next_hunk(&GoToHunk, window, cx);
13226 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13227 });
13228 executor.run_until_parked();
13229 cx.assert_state_with_diff(
13230 r#"
13231 - use some::mod;
13232 + use some::modified;
13233
13234 - const A: u32 = 42;
13235 ˇ
13236 fn main() {
13237 - println!("hello");
13238 + println!("hello there");
13239
13240 + println!("around the");
13241 println!("world");
13242 }
13243 "#
13244 .unindent(),
13245 );
13246
13247 cx.update_editor(|editor, window, cx| {
13248 editor.cancel(&Cancel, window, cx);
13249 });
13250
13251 cx.assert_state_with_diff(
13252 r#"
13253 use some::modified;
13254
13255 ˇ
13256 fn main() {
13257 println!("hello there");
13258
13259 println!("around the");
13260 println!("world");
13261 }
13262 "#
13263 .unindent(),
13264 );
13265}
13266
13267#[gpui::test]
13268async fn test_diff_base_change_with_expanded_diff_hunks(
13269 executor: BackgroundExecutor,
13270 cx: &mut TestAppContext,
13271) {
13272 init_test(cx, |_| {});
13273
13274 let mut cx = EditorTestContext::new(cx).await;
13275
13276 let diff_base = r#"
13277 use some::mod1;
13278 use some::mod2;
13279
13280 const A: u32 = 42;
13281 const B: u32 = 42;
13282 const C: u32 = 42;
13283
13284 fn main() {
13285 println!("hello");
13286
13287 println!("world");
13288 }
13289 "#
13290 .unindent();
13291
13292 cx.set_state(
13293 &r#"
13294 use some::mod2;
13295
13296 const A: u32 = 42;
13297 const C: u32 = 42;
13298
13299 fn main(ˇ) {
13300 //println!("hello");
13301
13302 println!("world");
13303 //
13304 //
13305 }
13306 "#
13307 .unindent(),
13308 );
13309
13310 cx.set_head_text(&diff_base);
13311 executor.run_until_parked();
13312
13313 cx.update_editor(|editor, window, cx| {
13314 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13315 });
13316 executor.run_until_parked();
13317 cx.assert_state_with_diff(
13318 r#"
13319 - use some::mod1;
13320 use some::mod2;
13321
13322 const A: u32 = 42;
13323 - const B: u32 = 42;
13324 const C: u32 = 42;
13325
13326 fn main(ˇ) {
13327 - println!("hello");
13328 + //println!("hello");
13329
13330 println!("world");
13331 + //
13332 + //
13333 }
13334 "#
13335 .unindent(),
13336 );
13337
13338 cx.set_head_text("new diff base!");
13339 executor.run_until_parked();
13340 cx.assert_state_with_diff(
13341 r#"
13342 - new diff base!
13343 + use some::mod2;
13344 +
13345 + const A: u32 = 42;
13346 + const C: u32 = 42;
13347 +
13348 + fn main(ˇ) {
13349 + //println!("hello");
13350 +
13351 + println!("world");
13352 + //
13353 + //
13354 + }
13355 "#
13356 .unindent(),
13357 );
13358}
13359
13360#[gpui::test]
13361async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13362 init_test(cx, |_| {});
13363
13364 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13365 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13366 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13367 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13368 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13369 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13370
13371 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13372 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13373 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13374
13375 let multi_buffer = cx.new(|cx| {
13376 let mut multibuffer = MultiBuffer::new(ReadWrite);
13377 multibuffer.push_excerpts(
13378 buffer_1.clone(),
13379 [
13380 ExcerptRange {
13381 context: Point::new(0, 0)..Point::new(3, 0),
13382 primary: None,
13383 },
13384 ExcerptRange {
13385 context: Point::new(5, 0)..Point::new(7, 0),
13386 primary: None,
13387 },
13388 ExcerptRange {
13389 context: Point::new(9, 0)..Point::new(10, 3),
13390 primary: None,
13391 },
13392 ],
13393 cx,
13394 );
13395 multibuffer.push_excerpts(
13396 buffer_2.clone(),
13397 [
13398 ExcerptRange {
13399 context: Point::new(0, 0)..Point::new(3, 0),
13400 primary: None,
13401 },
13402 ExcerptRange {
13403 context: Point::new(5, 0)..Point::new(7, 0),
13404 primary: None,
13405 },
13406 ExcerptRange {
13407 context: Point::new(9, 0)..Point::new(10, 3),
13408 primary: None,
13409 },
13410 ],
13411 cx,
13412 );
13413 multibuffer.push_excerpts(
13414 buffer_3.clone(),
13415 [
13416 ExcerptRange {
13417 context: Point::new(0, 0)..Point::new(3, 0),
13418 primary: None,
13419 },
13420 ExcerptRange {
13421 context: Point::new(5, 0)..Point::new(7, 0),
13422 primary: None,
13423 },
13424 ExcerptRange {
13425 context: Point::new(9, 0)..Point::new(10, 3),
13426 primary: None,
13427 },
13428 ],
13429 cx,
13430 );
13431 multibuffer
13432 });
13433
13434 let editor = cx.add_window(|window, cx| {
13435 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13436 });
13437 editor
13438 .update(cx, |editor, _window, cx| {
13439 for (buffer, diff_base) in [
13440 (buffer_1.clone(), file_1_old),
13441 (buffer_2.clone(), file_2_old),
13442 (buffer_3.clone(), file_3_old),
13443 ] {
13444 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13445 editor
13446 .buffer
13447 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13448 }
13449 })
13450 .unwrap();
13451
13452 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13453 cx.run_until_parked();
13454
13455 cx.assert_editor_state(
13456 &"
13457 ˇaaa
13458 ccc
13459 ddd
13460
13461 ggg
13462 hhh
13463
13464
13465 lll
13466 mmm
13467 NNN
13468
13469 qqq
13470 rrr
13471
13472 uuu
13473 111
13474 222
13475 333
13476
13477 666
13478 777
13479
13480 000
13481 !!!"
13482 .unindent(),
13483 );
13484
13485 cx.update_editor(|editor, window, cx| {
13486 editor.select_all(&SelectAll, window, cx);
13487 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13488 });
13489 cx.executor().run_until_parked();
13490
13491 cx.assert_state_with_diff(
13492 "
13493 «aaa
13494 - bbb
13495 ccc
13496 ddd
13497
13498 ggg
13499 hhh
13500
13501
13502 lll
13503 mmm
13504 - nnn
13505 + NNN
13506
13507 qqq
13508 rrr
13509
13510 uuu
13511 111
13512 222
13513 333
13514
13515 + 666
13516 777
13517
13518 000
13519 !!!ˇ»"
13520 .unindent(),
13521 );
13522}
13523
13524#[gpui::test]
13525async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13526 init_test(cx, |_| {});
13527
13528 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13529 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13530
13531 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13532 let multi_buffer = cx.new(|cx| {
13533 let mut multibuffer = MultiBuffer::new(ReadWrite);
13534 multibuffer.push_excerpts(
13535 buffer.clone(),
13536 [
13537 ExcerptRange {
13538 context: Point::new(0, 0)..Point::new(2, 0),
13539 primary: None,
13540 },
13541 ExcerptRange {
13542 context: Point::new(4, 0)..Point::new(7, 0),
13543 primary: None,
13544 },
13545 ExcerptRange {
13546 context: Point::new(9, 0)..Point::new(10, 0),
13547 primary: None,
13548 },
13549 ],
13550 cx,
13551 );
13552 multibuffer
13553 });
13554
13555 let editor = cx.add_window(|window, cx| {
13556 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13557 });
13558 editor
13559 .update(cx, |editor, _window, cx| {
13560 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13561 editor
13562 .buffer
13563 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13564 })
13565 .unwrap();
13566
13567 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13568 cx.run_until_parked();
13569
13570 cx.update_editor(|editor, window, cx| {
13571 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13572 });
13573 cx.executor().run_until_parked();
13574
13575 // When the start of a hunk coincides with the start of its excerpt,
13576 // the hunk is expanded. When the start of a a hunk is earlier than
13577 // the start of its excerpt, the hunk is not expanded.
13578 cx.assert_state_with_diff(
13579 "
13580 ˇaaa
13581 - bbb
13582 + BBB
13583
13584 - ddd
13585 - eee
13586 + DDD
13587 + EEE
13588 fff
13589
13590 iii
13591 "
13592 .unindent(),
13593 );
13594}
13595
13596#[gpui::test]
13597async fn test_edits_around_expanded_insertion_hunks(
13598 executor: BackgroundExecutor,
13599 cx: &mut TestAppContext,
13600) {
13601 init_test(cx, |_| {});
13602
13603 let mut cx = EditorTestContext::new(cx).await;
13604
13605 let diff_base = r#"
13606 use some::mod1;
13607 use some::mod2;
13608
13609 const A: u32 = 42;
13610
13611 fn main() {
13612 println!("hello");
13613
13614 println!("world");
13615 }
13616 "#
13617 .unindent();
13618 executor.run_until_parked();
13619 cx.set_state(
13620 &r#"
13621 use some::mod1;
13622 use some::mod2;
13623
13624 const A: u32 = 42;
13625 const B: u32 = 42;
13626 const C: u32 = 42;
13627 ˇ
13628
13629 fn main() {
13630 println!("hello");
13631
13632 println!("world");
13633 }
13634 "#
13635 .unindent(),
13636 );
13637
13638 cx.set_head_text(&diff_base);
13639 executor.run_until_parked();
13640
13641 cx.update_editor(|editor, window, cx| {
13642 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13643 });
13644 executor.run_until_parked();
13645
13646 cx.assert_state_with_diff(
13647 r#"
13648 use some::mod1;
13649 use some::mod2;
13650
13651 const A: u32 = 42;
13652 + const B: u32 = 42;
13653 + const C: u32 = 42;
13654 + ˇ
13655
13656 fn main() {
13657 println!("hello");
13658
13659 println!("world");
13660 }
13661 "#
13662 .unindent(),
13663 );
13664
13665 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13666 executor.run_until_parked();
13667
13668 cx.assert_state_with_diff(
13669 r#"
13670 use some::mod1;
13671 use some::mod2;
13672
13673 const A: u32 = 42;
13674 + const B: u32 = 42;
13675 + const C: u32 = 42;
13676 + const D: u32 = 42;
13677 + ˇ
13678
13679 fn main() {
13680 println!("hello");
13681
13682 println!("world");
13683 }
13684 "#
13685 .unindent(),
13686 );
13687
13688 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13689 executor.run_until_parked();
13690
13691 cx.assert_state_with_diff(
13692 r#"
13693 use some::mod1;
13694 use some::mod2;
13695
13696 const A: u32 = 42;
13697 + const B: u32 = 42;
13698 + const C: u32 = 42;
13699 + const D: u32 = 42;
13700 + const E: u32 = 42;
13701 + ˇ
13702
13703 fn main() {
13704 println!("hello");
13705
13706 println!("world");
13707 }
13708 "#
13709 .unindent(),
13710 );
13711
13712 cx.update_editor(|editor, window, cx| {
13713 editor.delete_line(&DeleteLine, window, cx);
13714 });
13715 executor.run_until_parked();
13716
13717 cx.assert_state_with_diff(
13718 r#"
13719 use some::mod1;
13720 use some::mod2;
13721
13722 const A: u32 = 42;
13723 + const B: u32 = 42;
13724 + const C: u32 = 42;
13725 + const D: u32 = 42;
13726 + const E: u32 = 42;
13727 ˇ
13728 fn main() {
13729 println!("hello");
13730
13731 println!("world");
13732 }
13733 "#
13734 .unindent(),
13735 );
13736
13737 cx.update_editor(|editor, window, cx| {
13738 editor.move_up(&MoveUp, window, cx);
13739 editor.delete_line(&DeleteLine, window, cx);
13740 editor.move_up(&MoveUp, window, cx);
13741 editor.delete_line(&DeleteLine, window, cx);
13742 editor.move_up(&MoveUp, window, cx);
13743 editor.delete_line(&DeleteLine, window, cx);
13744 });
13745 executor.run_until_parked();
13746 cx.assert_state_with_diff(
13747 r#"
13748 use some::mod1;
13749 use some::mod2;
13750
13751 const A: u32 = 42;
13752 + const B: u32 = 42;
13753 ˇ
13754 fn main() {
13755 println!("hello");
13756
13757 println!("world");
13758 }
13759 "#
13760 .unindent(),
13761 );
13762
13763 cx.update_editor(|editor, window, cx| {
13764 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13765 editor.delete_line(&DeleteLine, window, cx);
13766 });
13767 executor.run_until_parked();
13768 cx.assert_state_with_diff(
13769 r#"
13770 ˇ
13771 fn main() {
13772 println!("hello");
13773
13774 println!("world");
13775 }
13776 "#
13777 .unindent(),
13778 );
13779}
13780
13781#[gpui::test]
13782async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13783 init_test(cx, |_| {});
13784
13785 let mut cx = EditorTestContext::new(cx).await;
13786 cx.set_head_text(indoc! { "
13787 one
13788 two
13789 three
13790 four
13791 five
13792 "
13793 });
13794 cx.set_state(indoc! { "
13795 one
13796 ˇthree
13797 five
13798 "});
13799 cx.run_until_parked();
13800 cx.update_editor(|editor, window, cx| {
13801 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13802 });
13803 cx.assert_state_with_diff(
13804 indoc! { "
13805 one
13806 - two
13807 ˇthree
13808 - four
13809 five
13810 "}
13811 .to_string(),
13812 );
13813 cx.update_editor(|editor, window, cx| {
13814 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13815 });
13816
13817 cx.assert_state_with_diff(
13818 indoc! { "
13819 one
13820 ˇthree
13821 five
13822 "}
13823 .to_string(),
13824 );
13825
13826 cx.set_state(indoc! { "
13827 one
13828 ˇTWO
13829 three
13830 four
13831 five
13832 "});
13833 cx.run_until_parked();
13834 cx.update_editor(|editor, window, cx| {
13835 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13836 });
13837
13838 cx.assert_state_with_diff(
13839 indoc! { "
13840 one
13841 - two
13842 + ˇTWO
13843 three
13844 four
13845 five
13846 "}
13847 .to_string(),
13848 );
13849 cx.update_editor(|editor, window, cx| {
13850 editor.move_up(&Default::default(), window, cx);
13851 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13852 });
13853 cx.assert_state_with_diff(
13854 indoc! { "
13855 one
13856 ˇTWO
13857 three
13858 four
13859 five
13860 "}
13861 .to_string(),
13862 );
13863}
13864
13865#[gpui::test]
13866async fn test_edits_around_expanded_deletion_hunks(
13867 executor: BackgroundExecutor,
13868 cx: &mut TestAppContext,
13869) {
13870 init_test(cx, |_| {});
13871
13872 let mut cx = EditorTestContext::new(cx).await;
13873
13874 let diff_base = r#"
13875 use some::mod1;
13876 use some::mod2;
13877
13878 const A: u32 = 42;
13879 const B: u32 = 42;
13880 const C: u32 = 42;
13881
13882
13883 fn main() {
13884 println!("hello");
13885
13886 println!("world");
13887 }
13888 "#
13889 .unindent();
13890 executor.run_until_parked();
13891 cx.set_state(
13892 &r#"
13893 use some::mod1;
13894 use some::mod2;
13895
13896 ˇconst B: u32 = 42;
13897 const C: u32 = 42;
13898
13899
13900 fn main() {
13901 println!("hello");
13902
13903 println!("world");
13904 }
13905 "#
13906 .unindent(),
13907 );
13908
13909 cx.set_head_text(&diff_base);
13910 executor.run_until_parked();
13911
13912 cx.update_editor(|editor, window, cx| {
13913 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13914 });
13915 executor.run_until_parked();
13916
13917 cx.assert_state_with_diff(
13918 r#"
13919 use some::mod1;
13920 use some::mod2;
13921
13922 - const A: u32 = 42;
13923 ˇconst B: u32 = 42;
13924 const C: u32 = 42;
13925
13926
13927 fn main() {
13928 println!("hello");
13929
13930 println!("world");
13931 }
13932 "#
13933 .unindent(),
13934 );
13935
13936 cx.update_editor(|editor, window, cx| {
13937 editor.delete_line(&DeleteLine, window, cx);
13938 });
13939 executor.run_until_parked();
13940 cx.assert_state_with_diff(
13941 r#"
13942 use some::mod1;
13943 use some::mod2;
13944
13945 - const A: u32 = 42;
13946 - const B: u32 = 42;
13947 ˇconst C: u32 = 42;
13948
13949
13950 fn main() {
13951 println!("hello");
13952
13953 println!("world");
13954 }
13955 "#
13956 .unindent(),
13957 );
13958
13959 cx.update_editor(|editor, window, cx| {
13960 editor.delete_line(&DeleteLine, window, cx);
13961 });
13962 executor.run_until_parked();
13963 cx.assert_state_with_diff(
13964 r#"
13965 use some::mod1;
13966 use some::mod2;
13967
13968 - const A: u32 = 42;
13969 - const B: u32 = 42;
13970 - const C: u32 = 42;
13971 ˇ
13972
13973 fn main() {
13974 println!("hello");
13975
13976 println!("world");
13977 }
13978 "#
13979 .unindent(),
13980 );
13981
13982 cx.update_editor(|editor, window, cx| {
13983 editor.handle_input("replacement", window, cx);
13984 });
13985 executor.run_until_parked();
13986 cx.assert_state_with_diff(
13987 r#"
13988 use some::mod1;
13989 use some::mod2;
13990
13991 - const A: u32 = 42;
13992 - const B: u32 = 42;
13993 - const C: u32 = 42;
13994 -
13995 + replacementˇ
13996
13997 fn main() {
13998 println!("hello");
13999
14000 println!("world");
14001 }
14002 "#
14003 .unindent(),
14004 );
14005}
14006
14007#[gpui::test]
14008async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14009 init_test(cx, |_| {});
14010
14011 let mut cx = EditorTestContext::new(cx).await;
14012
14013 let base_text = r#"
14014 one
14015 two
14016 three
14017 four
14018 five
14019 "#
14020 .unindent();
14021 executor.run_until_parked();
14022 cx.set_state(
14023 &r#"
14024 one
14025 two
14026 fˇour
14027 five
14028 "#
14029 .unindent(),
14030 );
14031
14032 cx.set_head_text(&base_text);
14033 executor.run_until_parked();
14034
14035 cx.update_editor(|editor, window, cx| {
14036 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14037 });
14038 executor.run_until_parked();
14039
14040 cx.assert_state_with_diff(
14041 r#"
14042 one
14043 two
14044 - three
14045 fˇour
14046 five
14047 "#
14048 .unindent(),
14049 );
14050
14051 cx.update_editor(|editor, window, cx| {
14052 editor.backspace(&Backspace, window, cx);
14053 editor.backspace(&Backspace, window, cx);
14054 });
14055 executor.run_until_parked();
14056 cx.assert_state_with_diff(
14057 r#"
14058 one
14059 two
14060 - threeˇ
14061 - four
14062 + our
14063 five
14064 "#
14065 .unindent(),
14066 );
14067}
14068
14069#[gpui::test]
14070async fn test_edit_after_expanded_modification_hunk(
14071 executor: BackgroundExecutor,
14072 cx: &mut TestAppContext,
14073) {
14074 init_test(cx, |_| {});
14075
14076 let mut cx = EditorTestContext::new(cx).await;
14077
14078 let diff_base = r#"
14079 use some::mod1;
14080 use some::mod2;
14081
14082 const A: u32 = 42;
14083 const B: u32 = 42;
14084 const C: u32 = 42;
14085 const D: u32 = 42;
14086
14087
14088 fn main() {
14089 println!("hello");
14090
14091 println!("world");
14092 }"#
14093 .unindent();
14094
14095 cx.set_state(
14096 &r#"
14097 use some::mod1;
14098 use some::mod2;
14099
14100 const A: u32 = 42;
14101 const B: u32 = 42;
14102 const C: u32 = 43ˇ
14103 const D: u32 = 42;
14104
14105
14106 fn main() {
14107 println!("hello");
14108
14109 println!("world");
14110 }"#
14111 .unindent(),
14112 );
14113
14114 cx.set_head_text(&diff_base);
14115 executor.run_until_parked();
14116 cx.update_editor(|editor, window, cx| {
14117 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14118 });
14119 executor.run_until_parked();
14120
14121 cx.assert_state_with_diff(
14122 r#"
14123 use some::mod1;
14124 use some::mod2;
14125
14126 const A: u32 = 42;
14127 const B: u32 = 42;
14128 - const C: u32 = 42;
14129 + const C: u32 = 43ˇ
14130 const D: u32 = 42;
14131
14132
14133 fn main() {
14134 println!("hello");
14135
14136 println!("world");
14137 }"#
14138 .unindent(),
14139 );
14140
14141 cx.update_editor(|editor, window, cx| {
14142 editor.handle_input("\nnew_line\n", window, cx);
14143 });
14144 executor.run_until_parked();
14145
14146 cx.assert_state_with_diff(
14147 r#"
14148 use some::mod1;
14149 use some::mod2;
14150
14151 const A: u32 = 42;
14152 const B: u32 = 42;
14153 - const C: u32 = 42;
14154 + const C: u32 = 43
14155 + new_line
14156 + ˇ
14157 const D: u32 = 42;
14158
14159
14160 fn main() {
14161 println!("hello");
14162
14163 println!("world");
14164 }"#
14165 .unindent(),
14166 );
14167}
14168
14169#[gpui::test]
14170async fn test_stage_and_unstage_added_file_hunk(
14171 executor: BackgroundExecutor,
14172 cx: &mut TestAppContext,
14173) {
14174 init_test(cx, |_| {});
14175
14176 let mut cx = EditorTestContext::new(cx).await;
14177 cx.update_editor(|editor, _, cx| {
14178 editor.set_expand_all_diff_hunks(cx);
14179 });
14180
14181 let working_copy = r#"
14182 ˇfn main() {
14183 println!("hello, world!");
14184 }
14185 "#
14186 .unindent();
14187
14188 cx.set_state(&working_copy);
14189 executor.run_until_parked();
14190
14191 cx.assert_state_with_diff(
14192 r#"
14193 + ˇfn main() {
14194 + println!("hello, world!");
14195 + }
14196 "#
14197 .unindent(),
14198 );
14199 cx.assert_index_text(None);
14200
14201 cx.update_editor(|editor, window, cx| {
14202 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14203 });
14204 executor.run_until_parked();
14205 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14206 cx.assert_state_with_diff(
14207 r#"
14208 + ˇfn main() {
14209 + println!("hello, world!");
14210 + }
14211 "#
14212 .unindent(),
14213 );
14214
14215 cx.update_editor(|editor, window, cx| {
14216 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14217 });
14218 executor.run_until_parked();
14219 cx.assert_index_text(None);
14220}
14221
14222async fn setup_indent_guides_editor(
14223 text: &str,
14224 cx: &mut TestAppContext,
14225) -> (BufferId, EditorTestContext) {
14226 init_test(cx, |_| {});
14227
14228 let mut cx = EditorTestContext::new(cx).await;
14229
14230 let buffer_id = cx.update_editor(|editor, window, cx| {
14231 editor.set_text(text, window, cx);
14232 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14233
14234 buffer_ids[0]
14235 });
14236
14237 (buffer_id, cx)
14238}
14239
14240fn assert_indent_guides(
14241 range: Range<u32>,
14242 expected: Vec<IndentGuide>,
14243 active_indices: Option<Vec<usize>>,
14244 cx: &mut EditorTestContext,
14245) {
14246 let indent_guides = cx.update_editor(|editor, window, cx| {
14247 let snapshot = editor.snapshot(window, cx).display_snapshot;
14248 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14249 editor,
14250 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14251 true,
14252 &snapshot,
14253 cx,
14254 );
14255
14256 indent_guides.sort_by(|a, b| {
14257 a.depth.cmp(&b.depth).then(
14258 a.start_row
14259 .cmp(&b.start_row)
14260 .then(a.end_row.cmp(&b.end_row)),
14261 )
14262 });
14263 indent_guides
14264 });
14265
14266 if let Some(expected) = active_indices {
14267 let active_indices = cx.update_editor(|editor, window, cx| {
14268 let snapshot = editor.snapshot(window, cx).display_snapshot;
14269 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14270 });
14271
14272 assert_eq!(
14273 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14274 expected,
14275 "Active indent guide indices do not match"
14276 );
14277 }
14278
14279 assert_eq!(indent_guides, expected, "Indent guides do not match");
14280}
14281
14282fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14283 IndentGuide {
14284 buffer_id,
14285 start_row: MultiBufferRow(start_row),
14286 end_row: MultiBufferRow(end_row),
14287 depth,
14288 tab_size: 4,
14289 settings: IndentGuideSettings {
14290 enabled: true,
14291 line_width: 1,
14292 active_line_width: 1,
14293 ..Default::default()
14294 },
14295 }
14296}
14297
14298#[gpui::test]
14299async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14300 let (buffer_id, mut cx) = setup_indent_guides_editor(
14301 &"
14302 fn main() {
14303 let a = 1;
14304 }"
14305 .unindent(),
14306 cx,
14307 )
14308 .await;
14309
14310 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14311}
14312
14313#[gpui::test]
14314async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14315 let (buffer_id, mut cx) = setup_indent_guides_editor(
14316 &"
14317 fn main() {
14318 let a = 1;
14319 let b = 2;
14320 }"
14321 .unindent(),
14322 cx,
14323 )
14324 .await;
14325
14326 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14327}
14328
14329#[gpui::test]
14330async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14331 let (buffer_id, mut cx) = setup_indent_guides_editor(
14332 &"
14333 fn main() {
14334 let a = 1;
14335 if a == 3 {
14336 let b = 2;
14337 } else {
14338 let c = 3;
14339 }
14340 }"
14341 .unindent(),
14342 cx,
14343 )
14344 .await;
14345
14346 assert_indent_guides(
14347 0..8,
14348 vec![
14349 indent_guide(buffer_id, 1, 6, 0),
14350 indent_guide(buffer_id, 3, 3, 1),
14351 indent_guide(buffer_id, 5, 5, 1),
14352 ],
14353 None,
14354 &mut cx,
14355 );
14356}
14357
14358#[gpui::test]
14359async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14360 let (buffer_id, mut cx) = setup_indent_guides_editor(
14361 &"
14362 fn main() {
14363 let a = 1;
14364 let b = 2;
14365 let c = 3;
14366 }"
14367 .unindent(),
14368 cx,
14369 )
14370 .await;
14371
14372 assert_indent_guides(
14373 0..5,
14374 vec![
14375 indent_guide(buffer_id, 1, 3, 0),
14376 indent_guide(buffer_id, 2, 2, 1),
14377 ],
14378 None,
14379 &mut cx,
14380 );
14381}
14382
14383#[gpui::test]
14384async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14385 let (buffer_id, mut cx) = setup_indent_guides_editor(
14386 &"
14387 fn main() {
14388 let a = 1;
14389
14390 let c = 3;
14391 }"
14392 .unindent(),
14393 cx,
14394 )
14395 .await;
14396
14397 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14398}
14399
14400#[gpui::test]
14401async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14402 let (buffer_id, mut cx) = setup_indent_guides_editor(
14403 &"
14404 fn main() {
14405 let a = 1;
14406
14407 let c = 3;
14408
14409 if a == 3 {
14410 let b = 2;
14411 } else {
14412 let c = 3;
14413 }
14414 }"
14415 .unindent(),
14416 cx,
14417 )
14418 .await;
14419
14420 assert_indent_guides(
14421 0..11,
14422 vec![
14423 indent_guide(buffer_id, 1, 9, 0),
14424 indent_guide(buffer_id, 6, 6, 1),
14425 indent_guide(buffer_id, 8, 8, 1),
14426 ],
14427 None,
14428 &mut cx,
14429 );
14430}
14431
14432#[gpui::test]
14433async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14434 let (buffer_id, mut cx) = setup_indent_guides_editor(
14435 &"
14436 fn main() {
14437 let a = 1;
14438
14439 let c = 3;
14440
14441 if a == 3 {
14442 let b = 2;
14443 } else {
14444 let c = 3;
14445 }
14446 }"
14447 .unindent(),
14448 cx,
14449 )
14450 .await;
14451
14452 assert_indent_guides(
14453 1..11,
14454 vec![
14455 indent_guide(buffer_id, 1, 9, 0),
14456 indent_guide(buffer_id, 6, 6, 1),
14457 indent_guide(buffer_id, 8, 8, 1),
14458 ],
14459 None,
14460 &mut cx,
14461 );
14462}
14463
14464#[gpui::test]
14465async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14466 let (buffer_id, mut cx) = setup_indent_guides_editor(
14467 &"
14468 fn main() {
14469 let a = 1;
14470
14471 let c = 3;
14472
14473 if a == 3 {
14474 let b = 2;
14475 } else {
14476 let c = 3;
14477 }
14478 }"
14479 .unindent(),
14480 cx,
14481 )
14482 .await;
14483
14484 assert_indent_guides(
14485 1..10,
14486 vec![
14487 indent_guide(buffer_id, 1, 9, 0),
14488 indent_guide(buffer_id, 6, 6, 1),
14489 indent_guide(buffer_id, 8, 8, 1),
14490 ],
14491 None,
14492 &mut cx,
14493 );
14494}
14495
14496#[gpui::test]
14497async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14498 let (buffer_id, mut cx) = setup_indent_guides_editor(
14499 &"
14500 block1
14501 block2
14502 block3
14503 block4
14504 block2
14505 block1
14506 block1"
14507 .unindent(),
14508 cx,
14509 )
14510 .await;
14511
14512 assert_indent_guides(
14513 1..10,
14514 vec![
14515 indent_guide(buffer_id, 1, 4, 0),
14516 indent_guide(buffer_id, 2, 3, 1),
14517 indent_guide(buffer_id, 3, 3, 2),
14518 ],
14519 None,
14520 &mut cx,
14521 );
14522}
14523
14524#[gpui::test]
14525async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14526 let (buffer_id, mut cx) = setup_indent_guides_editor(
14527 &"
14528 block1
14529 block2
14530 block3
14531
14532 block1
14533 block1"
14534 .unindent(),
14535 cx,
14536 )
14537 .await;
14538
14539 assert_indent_guides(
14540 0..6,
14541 vec![
14542 indent_guide(buffer_id, 1, 2, 0),
14543 indent_guide(buffer_id, 2, 2, 1),
14544 ],
14545 None,
14546 &mut cx,
14547 );
14548}
14549
14550#[gpui::test]
14551async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14552 let (buffer_id, mut cx) = setup_indent_guides_editor(
14553 &"
14554 block1
14555
14556
14557
14558 block2
14559 "
14560 .unindent(),
14561 cx,
14562 )
14563 .await;
14564
14565 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14566}
14567
14568#[gpui::test]
14569async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14570 let (buffer_id, mut cx) = setup_indent_guides_editor(
14571 &"
14572 def a:
14573 \tb = 3
14574 \tif True:
14575 \t\tc = 4
14576 \t\td = 5
14577 \tprint(b)
14578 "
14579 .unindent(),
14580 cx,
14581 )
14582 .await;
14583
14584 assert_indent_guides(
14585 0..6,
14586 vec![
14587 indent_guide(buffer_id, 1, 6, 0),
14588 indent_guide(buffer_id, 3, 4, 1),
14589 ],
14590 None,
14591 &mut cx,
14592 );
14593}
14594
14595#[gpui::test]
14596async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14597 let (buffer_id, mut cx) = setup_indent_guides_editor(
14598 &"
14599 fn main() {
14600 let a = 1;
14601 }"
14602 .unindent(),
14603 cx,
14604 )
14605 .await;
14606
14607 cx.update_editor(|editor, window, cx| {
14608 editor.change_selections(None, window, cx, |s| {
14609 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14610 });
14611 });
14612
14613 assert_indent_guides(
14614 0..3,
14615 vec![indent_guide(buffer_id, 1, 1, 0)],
14616 Some(vec![0]),
14617 &mut cx,
14618 );
14619}
14620
14621#[gpui::test]
14622async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14623 let (buffer_id, mut cx) = setup_indent_guides_editor(
14624 &"
14625 fn main() {
14626 if 1 == 2 {
14627 let a = 1;
14628 }
14629 }"
14630 .unindent(),
14631 cx,
14632 )
14633 .await;
14634
14635 cx.update_editor(|editor, window, cx| {
14636 editor.change_selections(None, window, cx, |s| {
14637 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14638 });
14639 });
14640
14641 assert_indent_guides(
14642 0..4,
14643 vec![
14644 indent_guide(buffer_id, 1, 3, 0),
14645 indent_guide(buffer_id, 2, 2, 1),
14646 ],
14647 Some(vec![1]),
14648 &mut cx,
14649 );
14650
14651 cx.update_editor(|editor, window, cx| {
14652 editor.change_selections(None, window, cx, |s| {
14653 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14654 });
14655 });
14656
14657 assert_indent_guides(
14658 0..4,
14659 vec![
14660 indent_guide(buffer_id, 1, 3, 0),
14661 indent_guide(buffer_id, 2, 2, 1),
14662 ],
14663 Some(vec![1]),
14664 &mut cx,
14665 );
14666
14667 cx.update_editor(|editor, window, cx| {
14668 editor.change_selections(None, window, cx, |s| {
14669 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14670 });
14671 });
14672
14673 assert_indent_guides(
14674 0..4,
14675 vec![
14676 indent_guide(buffer_id, 1, 3, 0),
14677 indent_guide(buffer_id, 2, 2, 1),
14678 ],
14679 Some(vec![0]),
14680 &mut cx,
14681 );
14682}
14683
14684#[gpui::test]
14685async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
14686 let (buffer_id, mut cx) = setup_indent_guides_editor(
14687 &"
14688 fn main() {
14689 let a = 1;
14690
14691 let b = 2;
14692 }"
14693 .unindent(),
14694 cx,
14695 )
14696 .await;
14697
14698 cx.update_editor(|editor, window, cx| {
14699 editor.change_selections(None, window, cx, |s| {
14700 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14701 });
14702 });
14703
14704 assert_indent_guides(
14705 0..5,
14706 vec![indent_guide(buffer_id, 1, 3, 0)],
14707 Some(vec![0]),
14708 &mut cx,
14709 );
14710}
14711
14712#[gpui::test]
14713async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
14714 let (buffer_id, mut cx) = setup_indent_guides_editor(
14715 &"
14716 def m:
14717 a = 1
14718 pass"
14719 .unindent(),
14720 cx,
14721 )
14722 .await;
14723
14724 cx.update_editor(|editor, window, cx| {
14725 editor.change_selections(None, window, cx, |s| {
14726 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14727 });
14728 });
14729
14730 assert_indent_guides(
14731 0..3,
14732 vec![indent_guide(buffer_id, 1, 2, 0)],
14733 Some(vec![0]),
14734 &mut cx,
14735 );
14736}
14737
14738#[gpui::test]
14739async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
14740 init_test(cx, |_| {});
14741 let mut cx = EditorTestContext::new(cx).await;
14742 let text = indoc! {
14743 "
14744 impl A {
14745 fn b() {
14746 0;
14747 3;
14748 5;
14749 6;
14750 7;
14751 }
14752 }
14753 "
14754 };
14755 let base_text = indoc! {
14756 "
14757 impl A {
14758 fn b() {
14759 0;
14760 1;
14761 2;
14762 3;
14763 4;
14764 }
14765 fn c() {
14766 5;
14767 6;
14768 7;
14769 }
14770 }
14771 "
14772 };
14773
14774 cx.update_editor(|editor, window, cx| {
14775 editor.set_text(text, window, cx);
14776
14777 editor.buffer().update(cx, |multibuffer, cx| {
14778 let buffer = multibuffer.as_singleton().unwrap();
14779 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
14780
14781 multibuffer.set_all_diff_hunks_expanded(cx);
14782 multibuffer.add_diff(diff, cx);
14783
14784 buffer.read(cx).remote_id()
14785 })
14786 });
14787 cx.run_until_parked();
14788
14789 cx.assert_state_with_diff(
14790 indoc! { "
14791 impl A {
14792 fn b() {
14793 0;
14794 - 1;
14795 - 2;
14796 3;
14797 - 4;
14798 - }
14799 - fn c() {
14800 5;
14801 6;
14802 7;
14803 }
14804 }
14805 ˇ"
14806 }
14807 .to_string(),
14808 );
14809
14810 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14811 editor
14812 .snapshot(window, cx)
14813 .buffer_snapshot
14814 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14815 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14816 .collect::<Vec<_>>()
14817 });
14818 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14819 assert_eq!(
14820 actual_guides,
14821 vec![
14822 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14823 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14824 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14825 ]
14826 );
14827}
14828
14829#[gpui::test]
14830async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14831 init_test(cx, |_| {});
14832 let mut cx = EditorTestContext::new(cx).await;
14833
14834 let diff_base = r#"
14835 a
14836 b
14837 c
14838 "#
14839 .unindent();
14840
14841 cx.set_state(
14842 &r#"
14843 ˇA
14844 b
14845 C
14846 "#
14847 .unindent(),
14848 );
14849 cx.set_head_text(&diff_base);
14850 cx.update_editor(|editor, window, cx| {
14851 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14852 });
14853 executor.run_until_parked();
14854
14855 let both_hunks_expanded = r#"
14856 - a
14857 + ˇA
14858 b
14859 - c
14860 + C
14861 "#
14862 .unindent();
14863
14864 cx.assert_state_with_diff(both_hunks_expanded.clone());
14865
14866 let hunk_ranges = cx.update_editor(|editor, window, cx| {
14867 let snapshot = editor.snapshot(window, cx);
14868 let hunks = editor
14869 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
14870 .collect::<Vec<_>>();
14871 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
14872 let buffer_id = hunks[0].buffer_id;
14873 hunks
14874 .into_iter()
14875 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
14876 .collect::<Vec<_>>()
14877 });
14878 assert_eq!(hunk_ranges.len(), 2);
14879
14880 cx.update_editor(|editor, _, cx| {
14881 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
14882 });
14883 executor.run_until_parked();
14884
14885 let second_hunk_expanded = r#"
14886 ˇA
14887 b
14888 - c
14889 + C
14890 "#
14891 .unindent();
14892
14893 cx.assert_state_with_diff(second_hunk_expanded);
14894
14895 cx.update_editor(|editor, _, cx| {
14896 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
14897 });
14898 executor.run_until_parked();
14899
14900 cx.assert_state_with_diff(both_hunks_expanded.clone());
14901
14902 cx.update_editor(|editor, _, cx| {
14903 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
14904 });
14905 executor.run_until_parked();
14906
14907 let first_hunk_expanded = r#"
14908 - a
14909 + ˇA
14910 b
14911 C
14912 "#
14913 .unindent();
14914
14915 cx.assert_state_with_diff(first_hunk_expanded);
14916
14917 cx.update_editor(|editor, _, cx| {
14918 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
14919 });
14920 executor.run_until_parked();
14921
14922 cx.assert_state_with_diff(both_hunks_expanded);
14923
14924 cx.set_state(
14925 &r#"
14926 ˇA
14927 b
14928 "#
14929 .unindent(),
14930 );
14931 cx.run_until_parked();
14932
14933 // TODO this cursor position seems bad
14934 cx.assert_state_with_diff(
14935 r#"
14936 - ˇa
14937 + A
14938 b
14939 "#
14940 .unindent(),
14941 );
14942
14943 cx.update_editor(|editor, window, cx| {
14944 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14945 });
14946
14947 cx.assert_state_with_diff(
14948 r#"
14949 - ˇa
14950 + A
14951 b
14952 - c
14953 "#
14954 .unindent(),
14955 );
14956
14957 let hunk_ranges = cx.update_editor(|editor, window, cx| {
14958 let snapshot = editor.snapshot(window, cx);
14959 let hunks = editor
14960 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
14961 .collect::<Vec<_>>();
14962 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
14963 let buffer_id = hunks[0].buffer_id;
14964 hunks
14965 .into_iter()
14966 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
14967 .collect::<Vec<_>>()
14968 });
14969 assert_eq!(hunk_ranges.len(), 2);
14970
14971 cx.update_editor(|editor, _, cx| {
14972 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
14973 });
14974 executor.run_until_parked();
14975
14976 cx.assert_state_with_diff(
14977 r#"
14978 - ˇa
14979 + A
14980 b
14981 "#
14982 .unindent(),
14983 );
14984}
14985
14986#[gpui::test]
14987async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
14988 init_test(cx, |_| {});
14989
14990 let mut cx = EditorTestContext::new(cx).await;
14991 cx.set_head_text(indoc! { "
14992 one
14993 two
14994 three
14995 four
14996 five
14997 "
14998 });
14999 cx.set_index_text(indoc! { "
15000 one
15001 two
15002 three
15003 four
15004 five
15005 "
15006 });
15007 cx.set_state(indoc! {"
15008 one
15009 TWO
15010 ˇTHREE
15011 FOUR
15012 five
15013 "});
15014 cx.run_until_parked();
15015 cx.update_editor(|editor, window, cx| {
15016 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15017 });
15018 cx.run_until_parked();
15019 cx.assert_index_text(Some(indoc! {"
15020 one
15021 TWO
15022 THREE
15023 FOUR
15024 five
15025 "}));
15026 cx.set_state(indoc! { "
15027 one
15028 TWO
15029 ˇTHREE-HUNDRED
15030 FOUR
15031 five
15032 "});
15033 cx.run_until_parked();
15034 cx.update_editor(|editor, window, cx| {
15035 let snapshot = editor.snapshot(window, cx);
15036 let hunks = editor
15037 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15038 .collect::<Vec<_>>();
15039 assert_eq!(hunks.len(), 1);
15040 assert_eq!(
15041 hunks[0].status(),
15042 DiffHunkStatus {
15043 kind: DiffHunkStatusKind::Modified,
15044 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15045 }
15046 );
15047
15048 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15049 });
15050 cx.run_until_parked();
15051 cx.assert_index_text(Some(indoc! {"
15052 one
15053 TWO
15054 THREE-HUNDRED
15055 FOUR
15056 five
15057 "}));
15058}
15059
15060#[gpui::test]
15061fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15062 init_test(cx, |_| {});
15063
15064 let editor = cx.add_window(|window, cx| {
15065 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15066 build_editor(buffer, window, cx)
15067 });
15068
15069 let render_args = Arc::new(Mutex::new(None));
15070 let snapshot = editor
15071 .update(cx, |editor, window, cx| {
15072 let snapshot = editor.buffer().read(cx).snapshot(cx);
15073 let range =
15074 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15075
15076 struct RenderArgs {
15077 row: MultiBufferRow,
15078 folded: bool,
15079 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15080 }
15081
15082 let crease = Crease::inline(
15083 range,
15084 FoldPlaceholder::test(),
15085 {
15086 let toggle_callback = render_args.clone();
15087 move |row, folded, callback, _window, _cx| {
15088 *toggle_callback.lock() = Some(RenderArgs {
15089 row,
15090 folded,
15091 callback,
15092 });
15093 div()
15094 }
15095 },
15096 |_row, _folded, _window, _cx| div(),
15097 );
15098
15099 editor.insert_creases(Some(crease), cx);
15100 let snapshot = editor.snapshot(window, cx);
15101 let _div = snapshot.render_crease_toggle(
15102 MultiBufferRow(1),
15103 false,
15104 cx.entity().clone(),
15105 window,
15106 cx,
15107 );
15108 snapshot
15109 })
15110 .unwrap();
15111
15112 let render_args = render_args.lock().take().unwrap();
15113 assert_eq!(render_args.row, MultiBufferRow(1));
15114 assert!(!render_args.folded);
15115 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15116
15117 cx.update_window(*editor, |_, window, cx| {
15118 (render_args.callback)(true, window, cx)
15119 })
15120 .unwrap();
15121 let snapshot = editor
15122 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15123 .unwrap();
15124 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15125
15126 cx.update_window(*editor, |_, window, cx| {
15127 (render_args.callback)(false, window, cx)
15128 })
15129 .unwrap();
15130 let snapshot = editor
15131 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15132 .unwrap();
15133 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15134}
15135
15136#[gpui::test]
15137async fn test_input_text(cx: &mut TestAppContext) {
15138 init_test(cx, |_| {});
15139 let mut cx = EditorTestContext::new(cx).await;
15140
15141 cx.set_state(
15142 &r#"ˇone
15143 two
15144
15145 three
15146 fourˇ
15147 five
15148
15149 siˇx"#
15150 .unindent(),
15151 );
15152
15153 cx.dispatch_action(HandleInput(String::new()));
15154 cx.assert_editor_state(
15155 &r#"ˇone
15156 two
15157
15158 three
15159 fourˇ
15160 five
15161
15162 siˇx"#
15163 .unindent(),
15164 );
15165
15166 cx.dispatch_action(HandleInput("AAAA".to_string()));
15167 cx.assert_editor_state(
15168 &r#"AAAAˇone
15169 two
15170
15171 three
15172 fourAAAAˇ
15173 five
15174
15175 siAAAAˇx"#
15176 .unindent(),
15177 );
15178}
15179
15180#[gpui::test]
15181async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15182 init_test(cx, |_| {});
15183
15184 let mut cx = EditorTestContext::new(cx).await;
15185 cx.set_state(
15186 r#"let foo = 1;
15187let foo = 2;
15188let foo = 3;
15189let fooˇ = 4;
15190let foo = 5;
15191let foo = 6;
15192let foo = 7;
15193let foo = 8;
15194let foo = 9;
15195let foo = 10;
15196let foo = 11;
15197let foo = 12;
15198let foo = 13;
15199let foo = 14;
15200let foo = 15;"#,
15201 );
15202
15203 cx.update_editor(|e, window, cx| {
15204 assert_eq!(
15205 e.next_scroll_position,
15206 NextScrollCursorCenterTopBottom::Center,
15207 "Default next scroll direction is center",
15208 );
15209
15210 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15211 assert_eq!(
15212 e.next_scroll_position,
15213 NextScrollCursorCenterTopBottom::Top,
15214 "After center, next scroll direction should be top",
15215 );
15216
15217 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15218 assert_eq!(
15219 e.next_scroll_position,
15220 NextScrollCursorCenterTopBottom::Bottom,
15221 "After top, next scroll direction should be bottom",
15222 );
15223
15224 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15225 assert_eq!(
15226 e.next_scroll_position,
15227 NextScrollCursorCenterTopBottom::Center,
15228 "After bottom, scrolling should start over",
15229 );
15230
15231 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15232 assert_eq!(
15233 e.next_scroll_position,
15234 NextScrollCursorCenterTopBottom::Top,
15235 "Scrolling continues if retriggered fast enough"
15236 );
15237 });
15238
15239 cx.executor()
15240 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15241 cx.executor().run_until_parked();
15242 cx.update_editor(|e, _, _| {
15243 assert_eq!(
15244 e.next_scroll_position,
15245 NextScrollCursorCenterTopBottom::Center,
15246 "If scrolling is not triggered fast enough, it should reset"
15247 );
15248 });
15249}
15250
15251#[gpui::test]
15252async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15253 init_test(cx, |_| {});
15254 let mut cx = EditorLspTestContext::new_rust(
15255 lsp::ServerCapabilities {
15256 definition_provider: Some(lsp::OneOf::Left(true)),
15257 references_provider: Some(lsp::OneOf::Left(true)),
15258 ..lsp::ServerCapabilities::default()
15259 },
15260 cx,
15261 )
15262 .await;
15263
15264 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15265 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15266 move |params, _| async move {
15267 if empty_go_to_definition {
15268 Ok(None)
15269 } else {
15270 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15271 uri: params.text_document_position_params.text_document.uri,
15272 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15273 })))
15274 }
15275 },
15276 );
15277 let references =
15278 cx.lsp
15279 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15280 Ok(Some(vec![lsp::Location {
15281 uri: params.text_document_position.text_document.uri,
15282 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15283 }]))
15284 });
15285 (go_to_definition, references)
15286 };
15287
15288 cx.set_state(
15289 &r#"fn one() {
15290 let mut a = ˇtwo();
15291 }
15292
15293 fn two() {}"#
15294 .unindent(),
15295 );
15296 set_up_lsp_handlers(false, &mut cx);
15297 let navigated = cx
15298 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15299 .await
15300 .expect("Failed to navigate to definition");
15301 assert_eq!(
15302 navigated,
15303 Navigated::Yes,
15304 "Should have navigated to definition from the GetDefinition response"
15305 );
15306 cx.assert_editor_state(
15307 &r#"fn one() {
15308 let mut a = two();
15309 }
15310
15311 fn «twoˇ»() {}"#
15312 .unindent(),
15313 );
15314
15315 let editors = cx.update_workspace(|workspace, _, cx| {
15316 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15317 });
15318 cx.update_editor(|_, _, test_editor_cx| {
15319 assert_eq!(
15320 editors.len(),
15321 1,
15322 "Initially, only one, test, editor should be open in the workspace"
15323 );
15324 assert_eq!(
15325 test_editor_cx.entity(),
15326 editors.last().expect("Asserted len is 1").clone()
15327 );
15328 });
15329
15330 set_up_lsp_handlers(true, &mut cx);
15331 let navigated = cx
15332 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15333 .await
15334 .expect("Failed to navigate to lookup references");
15335 assert_eq!(
15336 navigated,
15337 Navigated::Yes,
15338 "Should have navigated to references as a fallback after empty GoToDefinition response"
15339 );
15340 // We should not change the selections in the existing file,
15341 // if opening another milti buffer with the references
15342 cx.assert_editor_state(
15343 &r#"fn one() {
15344 let mut a = two();
15345 }
15346
15347 fn «twoˇ»() {}"#
15348 .unindent(),
15349 );
15350 let editors = cx.update_workspace(|workspace, _, cx| {
15351 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15352 });
15353 cx.update_editor(|_, _, test_editor_cx| {
15354 assert_eq!(
15355 editors.len(),
15356 2,
15357 "After falling back to references search, we open a new editor with the results"
15358 );
15359 let references_fallback_text = editors
15360 .into_iter()
15361 .find(|new_editor| *new_editor != test_editor_cx.entity())
15362 .expect("Should have one non-test editor now")
15363 .read(test_editor_cx)
15364 .text(test_editor_cx);
15365 assert_eq!(
15366 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15367 "Should use the range from the references response and not the GoToDefinition one"
15368 );
15369 });
15370}
15371
15372#[gpui::test]
15373async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15374 init_test(cx, |_| {});
15375
15376 let language = Arc::new(Language::new(
15377 LanguageConfig::default(),
15378 Some(tree_sitter_rust::LANGUAGE.into()),
15379 ));
15380
15381 let text = r#"
15382 #[cfg(test)]
15383 mod tests() {
15384 #[test]
15385 fn runnable_1() {
15386 let a = 1;
15387 }
15388
15389 #[test]
15390 fn runnable_2() {
15391 let a = 1;
15392 let b = 2;
15393 }
15394 }
15395 "#
15396 .unindent();
15397
15398 let fs = FakeFs::new(cx.executor());
15399 fs.insert_file("/file.rs", Default::default()).await;
15400
15401 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15402 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15403 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15404 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15405 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15406
15407 let editor = cx.new_window_entity(|window, cx| {
15408 Editor::new(
15409 EditorMode::Full,
15410 multi_buffer,
15411 Some(project.clone()),
15412 true,
15413 window,
15414 cx,
15415 )
15416 });
15417
15418 editor.update_in(cx, |editor, window, cx| {
15419 let snapshot = editor.buffer().read(cx).snapshot(cx);
15420 editor.tasks.insert(
15421 (buffer.read(cx).remote_id(), 3),
15422 RunnableTasks {
15423 templates: vec![],
15424 offset: snapshot.anchor_before(43),
15425 column: 0,
15426 extra_variables: HashMap::default(),
15427 context_range: BufferOffset(43)..BufferOffset(85),
15428 },
15429 );
15430 editor.tasks.insert(
15431 (buffer.read(cx).remote_id(), 8),
15432 RunnableTasks {
15433 templates: vec![],
15434 offset: snapshot.anchor_before(86),
15435 column: 0,
15436 extra_variables: HashMap::default(),
15437 context_range: BufferOffset(86)..BufferOffset(191),
15438 },
15439 );
15440
15441 // Test finding task when cursor is inside function body
15442 editor.change_selections(None, window, cx, |s| {
15443 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15444 });
15445 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15446 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15447
15448 // Test finding task when cursor is on function name
15449 editor.change_selections(None, window, cx, |s| {
15450 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15451 });
15452 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15453 assert_eq!(row, 8, "Should find task when cursor is on function name");
15454 });
15455}
15456
15457#[gpui::test]
15458async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
15459 init_test(cx, |_| {});
15460
15461 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15462 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15463 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15464
15465 let fs = FakeFs::new(cx.executor());
15466 fs.insert_tree(
15467 path!("/a"),
15468 json!({
15469 "first.rs": sample_text_1,
15470 "second.rs": sample_text_2,
15471 "third.rs": sample_text_3,
15472 }),
15473 )
15474 .await;
15475 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15476 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15477 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15478 let worktree = project.update(cx, |project, cx| {
15479 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15480 assert_eq!(worktrees.len(), 1);
15481 worktrees.pop().unwrap()
15482 });
15483 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15484
15485 let buffer_1 = project
15486 .update(cx, |project, cx| {
15487 project.open_buffer((worktree_id, "first.rs"), cx)
15488 })
15489 .await
15490 .unwrap();
15491 let buffer_2 = project
15492 .update(cx, |project, cx| {
15493 project.open_buffer((worktree_id, "second.rs"), cx)
15494 })
15495 .await
15496 .unwrap();
15497 let buffer_3 = project
15498 .update(cx, |project, cx| {
15499 project.open_buffer((worktree_id, "third.rs"), cx)
15500 })
15501 .await
15502 .unwrap();
15503
15504 let multi_buffer = cx.new(|cx| {
15505 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15506 multi_buffer.push_excerpts(
15507 buffer_1.clone(),
15508 [
15509 ExcerptRange {
15510 context: Point::new(0, 0)..Point::new(3, 0),
15511 primary: None,
15512 },
15513 ExcerptRange {
15514 context: Point::new(5, 0)..Point::new(7, 0),
15515 primary: None,
15516 },
15517 ExcerptRange {
15518 context: Point::new(9, 0)..Point::new(10, 4),
15519 primary: None,
15520 },
15521 ],
15522 cx,
15523 );
15524 multi_buffer.push_excerpts(
15525 buffer_2.clone(),
15526 [
15527 ExcerptRange {
15528 context: Point::new(0, 0)..Point::new(3, 0),
15529 primary: None,
15530 },
15531 ExcerptRange {
15532 context: Point::new(5, 0)..Point::new(7, 0),
15533 primary: None,
15534 },
15535 ExcerptRange {
15536 context: Point::new(9, 0)..Point::new(10, 4),
15537 primary: None,
15538 },
15539 ],
15540 cx,
15541 );
15542 multi_buffer.push_excerpts(
15543 buffer_3.clone(),
15544 [
15545 ExcerptRange {
15546 context: Point::new(0, 0)..Point::new(3, 0),
15547 primary: None,
15548 },
15549 ExcerptRange {
15550 context: Point::new(5, 0)..Point::new(7, 0),
15551 primary: None,
15552 },
15553 ExcerptRange {
15554 context: Point::new(9, 0)..Point::new(10, 4),
15555 primary: None,
15556 },
15557 ],
15558 cx,
15559 );
15560 multi_buffer
15561 });
15562 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15563 Editor::new(
15564 EditorMode::Full,
15565 multi_buffer,
15566 Some(project.clone()),
15567 true,
15568 window,
15569 cx,
15570 )
15571 });
15572
15573 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";
15574 assert_eq!(
15575 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15576 full_text,
15577 );
15578
15579 multi_buffer_editor.update(cx, |editor, cx| {
15580 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15581 });
15582 assert_eq!(
15583 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15584 "\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",
15585 "After folding the first buffer, its text should not be displayed"
15586 );
15587
15588 multi_buffer_editor.update(cx, |editor, cx| {
15589 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15590 });
15591 assert_eq!(
15592 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15593 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15594 "After folding the second buffer, its text should not be displayed"
15595 );
15596
15597 multi_buffer_editor.update(cx, |editor, cx| {
15598 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15599 });
15600 assert_eq!(
15601 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15602 "\n\n\n\n\n",
15603 "After folding the third buffer, its text should not be displayed"
15604 );
15605
15606 // Emulate selection inside the fold logic, that should work
15607 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15608 editor
15609 .snapshot(window, cx)
15610 .next_line_boundary(Point::new(0, 4));
15611 });
15612
15613 multi_buffer_editor.update(cx, |editor, cx| {
15614 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15615 });
15616 assert_eq!(
15617 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15618 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15619 "After unfolding the second buffer, its text should be displayed"
15620 );
15621
15622 multi_buffer_editor.update(cx, |editor, cx| {
15623 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15624 });
15625 assert_eq!(
15626 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15627 "\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",
15628 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15629 );
15630
15631 multi_buffer_editor.update(cx, |editor, cx| {
15632 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15633 });
15634 assert_eq!(
15635 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15636 full_text,
15637 "After unfolding the all buffers, all original text should be displayed"
15638 );
15639}
15640
15641#[gpui::test]
15642async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
15643 init_test(cx, |_| {});
15644
15645 let sample_text_1 = "1111\n2222\n3333".to_string();
15646 let sample_text_2 = "4444\n5555\n6666".to_string();
15647 let sample_text_3 = "7777\n8888\n9999".to_string();
15648
15649 let fs = FakeFs::new(cx.executor());
15650 fs.insert_tree(
15651 path!("/a"),
15652 json!({
15653 "first.rs": sample_text_1,
15654 "second.rs": sample_text_2,
15655 "third.rs": sample_text_3,
15656 }),
15657 )
15658 .await;
15659 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15660 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15661 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15662 let worktree = project.update(cx, |project, cx| {
15663 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15664 assert_eq!(worktrees.len(), 1);
15665 worktrees.pop().unwrap()
15666 });
15667 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15668
15669 let buffer_1 = project
15670 .update(cx, |project, cx| {
15671 project.open_buffer((worktree_id, "first.rs"), cx)
15672 })
15673 .await
15674 .unwrap();
15675 let buffer_2 = project
15676 .update(cx, |project, cx| {
15677 project.open_buffer((worktree_id, "second.rs"), cx)
15678 })
15679 .await
15680 .unwrap();
15681 let buffer_3 = project
15682 .update(cx, |project, cx| {
15683 project.open_buffer((worktree_id, "third.rs"), cx)
15684 })
15685 .await
15686 .unwrap();
15687
15688 let multi_buffer = cx.new(|cx| {
15689 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15690 multi_buffer.push_excerpts(
15691 buffer_1.clone(),
15692 [ExcerptRange {
15693 context: Point::new(0, 0)..Point::new(3, 0),
15694 primary: None,
15695 }],
15696 cx,
15697 );
15698 multi_buffer.push_excerpts(
15699 buffer_2.clone(),
15700 [ExcerptRange {
15701 context: Point::new(0, 0)..Point::new(3, 0),
15702 primary: None,
15703 }],
15704 cx,
15705 );
15706 multi_buffer.push_excerpts(
15707 buffer_3.clone(),
15708 [ExcerptRange {
15709 context: Point::new(0, 0)..Point::new(3, 0),
15710 primary: None,
15711 }],
15712 cx,
15713 );
15714 multi_buffer
15715 });
15716
15717 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15718 Editor::new(
15719 EditorMode::Full,
15720 multi_buffer,
15721 Some(project.clone()),
15722 true,
15723 window,
15724 cx,
15725 )
15726 });
15727
15728 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
15729 assert_eq!(
15730 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15731 full_text,
15732 );
15733
15734 multi_buffer_editor.update(cx, |editor, cx| {
15735 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15736 });
15737 assert_eq!(
15738 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15739 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
15740 "After folding the first buffer, its text should not be displayed"
15741 );
15742
15743 multi_buffer_editor.update(cx, |editor, cx| {
15744 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15745 });
15746
15747 assert_eq!(
15748 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15749 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
15750 "After folding the second buffer, its text should not be displayed"
15751 );
15752
15753 multi_buffer_editor.update(cx, |editor, cx| {
15754 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15755 });
15756 assert_eq!(
15757 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15758 "\n\n\n\n\n",
15759 "After folding the third buffer, its text should not be displayed"
15760 );
15761
15762 multi_buffer_editor.update(cx, |editor, cx| {
15763 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15764 });
15765 assert_eq!(
15766 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15767 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
15768 "After unfolding the second buffer, its text should be displayed"
15769 );
15770
15771 multi_buffer_editor.update(cx, |editor, cx| {
15772 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15773 });
15774 assert_eq!(
15775 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15776 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
15777 "After unfolding the first buffer, its text should be displayed"
15778 );
15779
15780 multi_buffer_editor.update(cx, |editor, cx| {
15781 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15782 });
15783 assert_eq!(
15784 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15785 full_text,
15786 "After unfolding all buffers, all original text should be displayed"
15787 );
15788}
15789
15790#[gpui::test]
15791async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) {
15792 init_test(cx, |_| {});
15793
15794 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15795
15796 let fs = FakeFs::new(cx.executor());
15797 fs.insert_tree(
15798 path!("/a"),
15799 json!({
15800 "main.rs": sample_text,
15801 }),
15802 )
15803 .await;
15804 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15805 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15806 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15807 let worktree = project.update(cx, |project, cx| {
15808 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15809 assert_eq!(worktrees.len(), 1);
15810 worktrees.pop().unwrap()
15811 });
15812 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15813
15814 let buffer_1 = project
15815 .update(cx, |project, cx| {
15816 project.open_buffer((worktree_id, "main.rs"), cx)
15817 })
15818 .await
15819 .unwrap();
15820
15821 let multi_buffer = cx.new(|cx| {
15822 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15823 multi_buffer.push_excerpts(
15824 buffer_1.clone(),
15825 [ExcerptRange {
15826 context: Point::new(0, 0)
15827 ..Point::new(
15828 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
15829 0,
15830 ),
15831 primary: None,
15832 }],
15833 cx,
15834 );
15835 multi_buffer
15836 });
15837 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15838 Editor::new(
15839 EditorMode::Full,
15840 multi_buffer,
15841 Some(project.clone()),
15842 true,
15843 window,
15844 cx,
15845 )
15846 });
15847
15848 let selection_range = Point::new(1, 0)..Point::new(2, 0);
15849 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15850 enum TestHighlight {}
15851 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
15852 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
15853 editor.highlight_text::<TestHighlight>(
15854 vec![highlight_range.clone()],
15855 HighlightStyle::color(Hsla::green()),
15856 cx,
15857 );
15858 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
15859 });
15860
15861 let full_text = format!("\n\n\n{sample_text}\n");
15862 assert_eq!(
15863 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15864 full_text,
15865 );
15866}
15867
15868#[gpui::test]
15869async fn test_inline_completion_text(cx: &mut TestAppContext) {
15870 init_test(cx, |_| {});
15871
15872 // Simple insertion
15873 assert_highlighted_edits(
15874 "Hello, world!",
15875 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
15876 true,
15877 cx,
15878 |highlighted_edits, cx| {
15879 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
15880 assert_eq!(highlighted_edits.highlights.len(), 1);
15881 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
15882 assert_eq!(
15883 highlighted_edits.highlights[0].1.background_color,
15884 Some(cx.theme().status().created_background)
15885 );
15886 },
15887 )
15888 .await;
15889
15890 // Replacement
15891 assert_highlighted_edits(
15892 "This is a test.",
15893 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
15894 false,
15895 cx,
15896 |highlighted_edits, cx| {
15897 assert_eq!(highlighted_edits.text, "That is a test.");
15898 assert_eq!(highlighted_edits.highlights.len(), 1);
15899 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
15900 assert_eq!(
15901 highlighted_edits.highlights[0].1.background_color,
15902 Some(cx.theme().status().created_background)
15903 );
15904 },
15905 )
15906 .await;
15907
15908 // Multiple edits
15909 assert_highlighted_edits(
15910 "Hello, world!",
15911 vec![
15912 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
15913 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
15914 ],
15915 false,
15916 cx,
15917 |highlighted_edits, cx| {
15918 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
15919 assert_eq!(highlighted_edits.highlights.len(), 2);
15920 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
15921 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
15922 assert_eq!(
15923 highlighted_edits.highlights[0].1.background_color,
15924 Some(cx.theme().status().created_background)
15925 );
15926 assert_eq!(
15927 highlighted_edits.highlights[1].1.background_color,
15928 Some(cx.theme().status().created_background)
15929 );
15930 },
15931 )
15932 .await;
15933
15934 // Multiple lines with edits
15935 assert_highlighted_edits(
15936 "First line\nSecond line\nThird line\nFourth line",
15937 vec![
15938 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
15939 (
15940 Point::new(2, 0)..Point::new(2, 10),
15941 "New third line".to_string(),
15942 ),
15943 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
15944 ],
15945 false,
15946 cx,
15947 |highlighted_edits, cx| {
15948 assert_eq!(
15949 highlighted_edits.text,
15950 "Second modified\nNew third line\nFourth updated line"
15951 );
15952 assert_eq!(highlighted_edits.highlights.len(), 3);
15953 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
15954 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
15955 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
15956 for highlight in &highlighted_edits.highlights {
15957 assert_eq!(
15958 highlight.1.background_color,
15959 Some(cx.theme().status().created_background)
15960 );
15961 }
15962 },
15963 )
15964 .await;
15965}
15966
15967#[gpui::test]
15968async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
15969 init_test(cx, |_| {});
15970
15971 // Deletion
15972 assert_highlighted_edits(
15973 "Hello, world!",
15974 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
15975 true,
15976 cx,
15977 |highlighted_edits, cx| {
15978 assert_eq!(highlighted_edits.text, "Hello, world!");
15979 assert_eq!(highlighted_edits.highlights.len(), 1);
15980 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
15981 assert_eq!(
15982 highlighted_edits.highlights[0].1.background_color,
15983 Some(cx.theme().status().deleted_background)
15984 );
15985 },
15986 )
15987 .await;
15988
15989 // Insertion
15990 assert_highlighted_edits(
15991 "Hello, world!",
15992 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
15993 true,
15994 cx,
15995 |highlighted_edits, cx| {
15996 assert_eq!(highlighted_edits.highlights.len(), 1);
15997 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
15998 assert_eq!(
15999 highlighted_edits.highlights[0].1.background_color,
16000 Some(cx.theme().status().created_background)
16001 );
16002 },
16003 )
16004 .await;
16005}
16006
16007async fn assert_highlighted_edits(
16008 text: &str,
16009 edits: Vec<(Range<Point>, String)>,
16010 include_deletions: bool,
16011 cx: &mut TestAppContext,
16012 assertion_fn: impl Fn(HighlightedText, &App),
16013) {
16014 let window = cx.add_window(|window, cx| {
16015 let buffer = MultiBuffer::build_simple(text, cx);
16016 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16017 });
16018 let cx = &mut VisualTestContext::from_window(*window, cx);
16019
16020 let (buffer, snapshot) = window
16021 .update(cx, |editor, _window, cx| {
16022 (
16023 editor.buffer().clone(),
16024 editor.buffer().read(cx).snapshot(cx),
16025 )
16026 })
16027 .unwrap();
16028
16029 let edits = edits
16030 .into_iter()
16031 .map(|(range, edit)| {
16032 (
16033 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16034 edit,
16035 )
16036 })
16037 .collect::<Vec<_>>();
16038
16039 let text_anchor_edits = edits
16040 .clone()
16041 .into_iter()
16042 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16043 .collect::<Vec<_>>();
16044
16045 let edit_preview = window
16046 .update(cx, |_, _window, cx| {
16047 buffer
16048 .read(cx)
16049 .as_singleton()
16050 .unwrap()
16051 .read(cx)
16052 .preview_edits(text_anchor_edits.into(), cx)
16053 })
16054 .unwrap()
16055 .await;
16056
16057 cx.update(|_window, cx| {
16058 let highlighted_edits = inline_completion_edit_text(
16059 &snapshot.as_singleton().unwrap().2,
16060 &edits,
16061 &edit_preview,
16062 include_deletions,
16063 cx,
16064 );
16065 assertion_fn(highlighted_edits, cx)
16066 });
16067}
16068
16069#[gpui::test]
16070async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16071 init_test(cx, |_| {});
16072 let capabilities = lsp::ServerCapabilities {
16073 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16074 prepare_provider: Some(true),
16075 work_done_progress_options: Default::default(),
16076 })),
16077 ..Default::default()
16078 };
16079 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16080
16081 cx.set_state(indoc! {"
16082 struct Fˇoo {}
16083 "});
16084
16085 cx.update_editor(|editor, _, cx| {
16086 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16087 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16088 editor.highlight_background::<DocumentHighlightRead>(
16089 &[highlight_range],
16090 |c| c.editor_document_highlight_read_background,
16091 cx,
16092 );
16093 });
16094
16095 let mut prepare_rename_handler =
16096 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16097 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16098 start: lsp::Position {
16099 line: 0,
16100 character: 7,
16101 },
16102 end: lsp::Position {
16103 line: 0,
16104 character: 10,
16105 },
16106 })))
16107 });
16108 let prepare_rename_task = cx
16109 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16110 .expect("Prepare rename was not started");
16111 prepare_rename_handler.next().await.unwrap();
16112 prepare_rename_task.await.expect("Prepare rename failed");
16113
16114 let mut rename_handler =
16115 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16116 let edit = lsp::TextEdit {
16117 range: lsp::Range {
16118 start: lsp::Position {
16119 line: 0,
16120 character: 7,
16121 },
16122 end: lsp::Position {
16123 line: 0,
16124 character: 10,
16125 },
16126 },
16127 new_text: "FooRenamed".to_string(),
16128 };
16129 Ok(Some(lsp::WorkspaceEdit::new(
16130 // Specify the same edit twice
16131 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16132 )))
16133 });
16134 let rename_task = cx
16135 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16136 .expect("Confirm rename was not started");
16137 rename_handler.next().await.unwrap();
16138 rename_task.await.expect("Confirm rename failed");
16139 cx.run_until_parked();
16140
16141 // Despite two edits, only one is actually applied as those are identical
16142 cx.assert_editor_state(indoc! {"
16143 struct FooRenamedˇ {}
16144 "});
16145}
16146
16147#[gpui::test]
16148async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16149 init_test(cx, |_| {});
16150 // These capabilities indicate that the server does not support prepare rename.
16151 let capabilities = lsp::ServerCapabilities {
16152 rename_provider: Some(lsp::OneOf::Left(true)),
16153 ..Default::default()
16154 };
16155 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16156
16157 cx.set_state(indoc! {"
16158 struct Fˇoo {}
16159 "});
16160
16161 cx.update_editor(|editor, _window, cx| {
16162 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16163 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16164 editor.highlight_background::<DocumentHighlightRead>(
16165 &[highlight_range],
16166 |c| c.editor_document_highlight_read_background,
16167 cx,
16168 );
16169 });
16170
16171 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16172 .expect("Prepare rename was not started")
16173 .await
16174 .expect("Prepare rename failed");
16175
16176 let mut rename_handler =
16177 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16178 let edit = lsp::TextEdit {
16179 range: lsp::Range {
16180 start: lsp::Position {
16181 line: 0,
16182 character: 7,
16183 },
16184 end: lsp::Position {
16185 line: 0,
16186 character: 10,
16187 },
16188 },
16189 new_text: "FooRenamed".to_string(),
16190 };
16191 Ok(Some(lsp::WorkspaceEdit::new(
16192 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16193 )))
16194 });
16195 let rename_task = cx
16196 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16197 .expect("Confirm rename was not started");
16198 rename_handler.next().await.unwrap();
16199 rename_task.await.expect("Confirm rename failed");
16200 cx.run_until_parked();
16201
16202 // Correct range is renamed, as `surrounding_word` is used to find it.
16203 cx.assert_editor_state(indoc! {"
16204 struct FooRenamedˇ {}
16205 "});
16206}
16207
16208#[gpui::test]
16209async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16210 init_test(cx, |_| {});
16211 let mut cx = EditorTestContext::new(cx).await;
16212
16213 let language = Arc::new(
16214 Language::new(
16215 LanguageConfig::default(),
16216 Some(tree_sitter_html::LANGUAGE.into()),
16217 )
16218 .with_brackets_query(
16219 r#"
16220 ("<" @open "/>" @close)
16221 ("</" @open ">" @close)
16222 ("<" @open ">" @close)
16223 ("\"" @open "\"" @close)
16224 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16225 "#,
16226 )
16227 .unwrap(),
16228 );
16229 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16230
16231 cx.set_state(indoc! {"
16232 <span>ˇ</span>
16233 "});
16234 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16235 cx.assert_editor_state(indoc! {"
16236 <span>
16237 ˇ
16238 </span>
16239 "});
16240
16241 cx.set_state(indoc! {"
16242 <span><span></span>ˇ</span>
16243 "});
16244 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16245 cx.assert_editor_state(indoc! {"
16246 <span><span></span>
16247 ˇ</span>
16248 "});
16249
16250 cx.set_state(indoc! {"
16251 <span>ˇ
16252 </span>
16253 "});
16254 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16255 cx.assert_editor_state(indoc! {"
16256 <span>
16257 ˇ
16258 </span>
16259 "});
16260}
16261
16262fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16263 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16264 point..point
16265}
16266
16267fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16268 let (text, ranges) = marked_text_ranges(marked_text, true);
16269 assert_eq!(editor.text(cx), text);
16270 assert_eq!(
16271 editor.selections.ranges(cx),
16272 ranges,
16273 "Assert selections are {}",
16274 marked_text
16275 );
16276}
16277
16278pub fn handle_signature_help_request(
16279 cx: &mut EditorLspTestContext,
16280 mocked_response: lsp::SignatureHelp,
16281) -> impl Future<Output = ()> {
16282 let mut request =
16283 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16284 let mocked_response = mocked_response.clone();
16285 async move { Ok(Some(mocked_response)) }
16286 });
16287
16288 async move {
16289 request.next().await;
16290 }
16291}
16292
16293/// Handle completion request passing a marked string specifying where the completion
16294/// should be triggered from using '|' character, what range should be replaced, and what completions
16295/// should be returned using '<' and '>' to delimit the range
16296pub fn handle_completion_request(
16297 cx: &mut EditorLspTestContext,
16298 marked_string: &str,
16299 completions: Vec<&'static str>,
16300 counter: Arc<AtomicUsize>,
16301) -> impl Future<Output = ()> {
16302 let complete_from_marker: TextRangeMarker = '|'.into();
16303 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16304 let (_, mut marked_ranges) = marked_text_ranges_by(
16305 marked_string,
16306 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16307 );
16308
16309 let complete_from_position =
16310 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16311 let replace_range =
16312 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16313
16314 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16315 let completions = completions.clone();
16316 counter.fetch_add(1, atomic::Ordering::Release);
16317 async move {
16318 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16319 assert_eq!(
16320 params.text_document_position.position,
16321 complete_from_position
16322 );
16323 Ok(Some(lsp::CompletionResponse::Array(
16324 completions
16325 .iter()
16326 .map(|completion_text| lsp::CompletionItem {
16327 label: completion_text.to_string(),
16328 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16329 range: replace_range,
16330 new_text: completion_text.to_string(),
16331 })),
16332 ..Default::default()
16333 })
16334 .collect(),
16335 )))
16336 }
16337 });
16338
16339 async move {
16340 request.next().await;
16341 }
16342}
16343
16344fn handle_resolve_completion_request(
16345 cx: &mut EditorLspTestContext,
16346 edits: Option<Vec<(&'static str, &'static str)>>,
16347) -> impl Future<Output = ()> {
16348 let edits = edits.map(|edits| {
16349 edits
16350 .iter()
16351 .map(|(marked_string, new_text)| {
16352 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16353 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16354 lsp::TextEdit::new(replace_range, new_text.to_string())
16355 })
16356 .collect::<Vec<_>>()
16357 });
16358
16359 let mut request =
16360 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16361 let edits = edits.clone();
16362 async move {
16363 Ok(lsp::CompletionItem {
16364 additional_text_edits: edits,
16365 ..Default::default()
16366 })
16367 }
16368 });
16369
16370 async move {
16371 request.next().await;
16372 }
16373}
16374
16375pub(crate) fn update_test_language_settings(
16376 cx: &mut TestAppContext,
16377 f: impl Fn(&mut AllLanguageSettingsContent),
16378) {
16379 cx.update(|cx| {
16380 SettingsStore::update_global(cx, |store, cx| {
16381 store.update_user_settings::<AllLanguageSettings>(cx, f);
16382 });
16383 });
16384}
16385
16386pub(crate) fn update_test_project_settings(
16387 cx: &mut TestAppContext,
16388 f: impl Fn(&mut ProjectSettings),
16389) {
16390 cx.update(|cx| {
16391 SettingsStore::update_global(cx, |store, cx| {
16392 store.update_user_settings::<ProjectSettings>(cx, f);
16393 });
16394 });
16395}
16396
16397pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16398 cx.update(|cx| {
16399 assets::Assets.load_test_fonts(cx);
16400 let store = SettingsStore::test(cx);
16401 cx.set_global(store);
16402 theme::init(theme::LoadThemes::JustBase, cx);
16403 release_channel::init(SemanticVersion::default(), cx);
16404 client::init_settings(cx);
16405 language::init(cx);
16406 Project::init_settings(cx);
16407 workspace::init_settings(cx);
16408 crate::init(cx);
16409 });
16410
16411 update_test_language_settings(cx, f);
16412}
16413
16414#[track_caller]
16415fn assert_hunk_revert(
16416 not_reverted_text_with_selections: &str,
16417 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
16418 expected_reverted_text_with_selections: &str,
16419 base_text: &str,
16420 cx: &mut EditorLspTestContext,
16421) {
16422 cx.set_state(not_reverted_text_with_selections);
16423 cx.set_head_text(base_text);
16424 cx.executor().run_until_parked();
16425
16426 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16427 let snapshot = editor.snapshot(window, cx);
16428 let reverted_hunk_statuses = snapshot
16429 .buffer_snapshot
16430 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16431 .map(|hunk| hunk.status())
16432 .collect::<Vec<_>>();
16433
16434 editor.git_restore(&Default::default(), window, cx);
16435 reverted_hunk_statuses
16436 });
16437 cx.executor().run_until_parked();
16438 cx.assert_editor_state(expected_reverted_text_with_selections);
16439 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16440}