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 futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic;
35use std::sync::atomic::AtomicUsize;
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use test::editor_lsp_test_context::rust_lang;
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let group_interval = Duration::from_millis(1);
174 let buffer = cx.new_model(|cx| {
175 let mut buf = language::Buffer::local("123456", cx);
176 buf.set_group_interval(group_interval);
177 buf
178 });
179 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
180 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
181
182 _ = editor.update(cx, |editor, cx| {
183 editor.start_transaction_at(now, cx);
184 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
185
186 editor.insert("cd", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cd56");
189 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
190
191 editor.start_transaction_at(now, cx);
192 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
193 editor.insert("e", cx);
194 editor.end_transaction_at(now, cx);
195 assert_eq!(editor.text(cx), "12cde6");
196 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
197
198 now += group_interval + Duration::from_millis(1);
199 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
200
201 // Simulate an edit in another editor
202 buffer.update(cx, |buffer, cx| {
203 buffer.start_transaction_at(now, cx);
204 buffer.edit([(0..1, "a")], None, cx);
205 buffer.edit([(1..1, "b")], None, cx);
206 buffer.end_transaction_at(now, cx);
207 });
208
209 assert_eq!(editor.text(cx), "ab2cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
211
212 // Last transaction happened past the group interval in a different editor.
213 // Undo it individually and don't restore selections.
214 editor.undo(&Undo, cx);
215 assert_eq!(editor.text(cx), "12cde6");
216 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
217
218 // First two transactions happened within the group interval in this editor.
219 // Undo them together and restore selections.
220 editor.undo(&Undo, cx);
221 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
222 assert_eq!(editor.text(cx), "123456");
223 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
224
225 // Redo the first two transactions together.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "12cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
229
230 // Redo the last transaction on its own.
231 editor.redo(&Redo, cx);
232 assert_eq!(editor.text(cx), "ab2cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
234
235 // Test empty transactions.
236 editor.start_transaction_at(now, cx);
237 editor.end_transaction_at(now, cx);
238 editor.undo(&Undo, cx);
239 assert_eq!(editor.text(cx), "12cde6");
240 });
241}
242
243#[gpui::test]
244fn test_ime_composition(cx: &mut TestAppContext) {
245 init_test(cx, |_| {});
246
247 let buffer = cx.new_model(|cx| {
248 let mut buffer = language::Buffer::local("abcde", cx);
249 // Ensure automatic grouping doesn't occur.
250 buffer.set_group_interval(Duration::ZERO);
251 buffer
252 });
253
254 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
255 cx.add_window(|cx| {
256 let mut editor = build_editor(buffer.clone(), cx);
257
258 // Start a new IME composition.
259 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
261 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
262 assert_eq!(editor.text(cx), "äbcde");
263 assert_eq!(
264 editor.marked_text_ranges(cx),
265 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
266 );
267
268 // Finalize IME composition.
269 editor.replace_text_in_range(None, "ā", cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // IME composition edits are grouped and are undone/redone at once.
274 editor.undo(&Default::default(), cx);
275 assert_eq!(editor.text(cx), "abcde");
276 assert_eq!(editor.marked_text_ranges(cx), None);
277 editor.redo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "ābcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280
281 // Start a new IME composition.
282 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
283 assert_eq!(
284 editor.marked_text_ranges(cx),
285 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
286 );
287
288 // Undoing during an IME composition cancels it.
289 editor.undo(&Default::default(), cx);
290 assert_eq!(editor.text(cx), "ābcde");
291 assert_eq!(editor.marked_text_ranges(cx), None);
292
293 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
294 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
295 assert_eq!(editor.text(cx), "ābcdè");
296 assert_eq!(
297 editor.marked_text_ranges(cx),
298 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
299 );
300
301 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
302 editor.replace_text_in_range(Some(4..999), "ę", cx);
303 assert_eq!(editor.text(cx), "ābcdę");
304 assert_eq!(editor.marked_text_ranges(cx), None);
305
306 // Start a new IME composition with multiple cursors.
307 editor.change_selections(None, cx, |s| {
308 s.select_ranges([
309 OffsetUtf16(1)..OffsetUtf16(1),
310 OffsetUtf16(3)..OffsetUtf16(3),
311 OffsetUtf16(5)..OffsetUtf16(5),
312 ])
313 });
314 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
315 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
316 assert_eq!(
317 editor.marked_text_ranges(cx),
318 Some(vec![
319 OffsetUtf16(0)..OffsetUtf16(3),
320 OffsetUtf16(4)..OffsetUtf16(7),
321 OffsetUtf16(8)..OffsetUtf16(11)
322 ])
323 );
324
325 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
326 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
327 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
328 assert_eq!(
329 editor.marked_text_ranges(cx),
330 Some(vec![
331 OffsetUtf16(1)..OffsetUtf16(2),
332 OffsetUtf16(5)..OffsetUtf16(6),
333 OffsetUtf16(9)..OffsetUtf16(10)
334 ])
335 );
336
337 // Finalize IME composition with multiple cursors.
338 editor.replace_text_in_range(Some(9..10), "2", cx);
339 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
340 assert_eq!(editor.marked_text_ranges(cx), None);
341
342 editor
343 });
344}
345
346#[gpui::test]
347fn test_selection_with_mouse(cx: &mut TestAppContext) {
348 init_test(cx, |_| {});
349
350 let editor = cx.add_window(|cx| {
351 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
352 build_editor(buffer, cx)
353 });
354
355 _ = editor.update(cx, |view, cx| {
356 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
357 });
358 assert_eq!(
359 editor
360 .update(cx, |view, cx| view.selections.display_ranges(cx))
361 .unwrap(),
362 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
363 );
364
365 _ = editor.update(cx, |view, cx| {
366 view.update_selection(
367 DisplayPoint::new(DisplayRow(3), 3),
368 0,
369 gpui::Point::<f32>::default(),
370 cx,
371 );
372 });
373
374 assert_eq!(
375 editor
376 .update(cx, |view, cx| view.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
379 );
380
381 _ = editor.update(cx, |view, cx| {
382 view.update_selection(
383 DisplayPoint::new(DisplayRow(1), 1),
384 0,
385 gpui::Point::<f32>::default(),
386 cx,
387 );
388 });
389
390 assert_eq!(
391 editor
392 .update(cx, |view, cx| view.selections.display_ranges(cx))
393 .unwrap(),
394 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
395 );
396
397 _ = editor.update(cx, |view, cx| {
398 view.end_selection(cx);
399 view.update_selection(
400 DisplayPoint::new(DisplayRow(3), 3),
401 0,
402 gpui::Point::<f32>::default(),
403 cx,
404 );
405 });
406
407 assert_eq!(
408 editor
409 .update(cx, |view, cx| view.selections.display_ranges(cx))
410 .unwrap(),
411 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
412 );
413
414 _ = editor.update(cx, |view, cx| {
415 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
416 view.update_selection(
417 DisplayPoint::new(DisplayRow(0), 0),
418 0,
419 gpui::Point::<f32>::default(),
420 cx,
421 );
422 });
423
424 assert_eq!(
425 editor
426 .update(cx, |view, cx| view.selections.display_ranges(cx))
427 .unwrap(),
428 [
429 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
430 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
431 ]
432 );
433
434 _ = editor.update(cx, |view, cx| {
435 view.end_selection(cx);
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |view, cx| view.selections.display_ranges(cx))
441 .unwrap(),
442 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
443 );
444}
445
446#[gpui::test]
447fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
448 init_test(cx, |_| {});
449
450 let editor = cx.add_window(|cx| {
451 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
452 build_editor(buffer, cx)
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.end_selection(cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
465 });
466
467 _ = editor.update(cx, |view, cx| {
468 view.end_selection(cx);
469 });
470
471 assert_eq!(
472 editor
473 .update(cx, |view, cx| view.selections.display_ranges(cx))
474 .unwrap(),
475 [
476 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
477 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
478 ]
479 );
480
481 _ = editor.update(cx, |view, cx| {
482 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
483 });
484
485 _ = editor.update(cx, |view, cx| {
486 view.end_selection(cx);
487 });
488
489 assert_eq!(
490 editor
491 .update(cx, |view, cx| view.selections.display_ranges(cx))
492 .unwrap(),
493 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
494 );
495}
496
497#[gpui::test]
498fn test_canceling_pending_selection(cx: &mut TestAppContext) {
499 init_test(cx, |_| {});
500
501 let view = cx.add_window(|cx| {
502 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
503 build_editor(buffer, cx)
504 });
505
506 _ = view.update(cx, |view, cx| {
507 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
508 assert_eq!(
509 view.selections.display_ranges(cx),
510 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
511 );
512 });
513
514 _ = view.update(cx, |view, cx| {
515 view.update_selection(
516 DisplayPoint::new(DisplayRow(3), 3),
517 0,
518 gpui::Point::<f32>::default(),
519 cx,
520 );
521 assert_eq!(
522 view.selections.display_ranges(cx),
523 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
524 );
525 });
526
527 _ = view.update(cx, |view, cx| {
528 view.cancel(&Cancel, cx);
529 view.update_selection(
530 DisplayPoint::new(DisplayRow(1), 1),
531 0,
532 gpui::Point::<f32>::default(),
533 cx,
534 );
535 assert_eq!(
536 view.selections.display_ranges(cx),
537 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
538 );
539 });
540}
541
542#[gpui::test]
543fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
544 init_test(cx, |_| {});
545
546 let view = cx.add_window(|cx| {
547 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
548 build_editor(buffer, cx)
549 });
550
551 _ = view.update(cx, |view, cx| {
552 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
553 assert_eq!(
554 view.selections.display_ranges(cx),
555 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
556 );
557
558 view.move_down(&Default::default(), cx);
559 assert_eq!(
560 view.selections.display_ranges(cx),
561 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
562 );
563
564 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
565 assert_eq!(
566 view.selections.display_ranges(cx),
567 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
568 );
569
570 view.move_up(&Default::default(), cx);
571 assert_eq!(
572 view.selections.display_ranges(cx),
573 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
574 );
575 });
576}
577
578#[gpui::test]
579fn test_clone(cx: &mut TestAppContext) {
580 init_test(cx, |_| {});
581
582 let (text, selection_ranges) = marked_text_ranges(
583 indoc! {"
584 one
585 two
586 threeˇ
587 four
588 fiveˇ
589 "},
590 true,
591 );
592
593 let editor = cx.add_window(|cx| {
594 let buffer = MultiBuffer::build_simple(&text, cx);
595 build_editor(buffer, cx)
596 });
597
598 _ = editor.update(cx, |editor, cx| {
599 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
600 editor.fold_creases(
601 vec![
602 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
603 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
604 ],
605 true,
606 cx,
607 );
608 });
609
610 let cloned_editor = editor
611 .update(cx, |editor, cx| {
612 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
613 })
614 .unwrap()
615 .unwrap();
616
617 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
619
620 assert_eq!(
621 cloned_editor
622 .update(cx, |e, cx| e.display_text(cx))
623 .unwrap(),
624 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
625 );
626 assert_eq!(
627 cloned_snapshot
628 .folds_in_range(0..text.len())
629 .collect::<Vec<_>>(),
630 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
631 );
632 assert_set_eq!(
633 cloned_editor
634 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
635 .unwrap(),
636 editor
637 .update(cx, |editor, cx| editor.selections.ranges(cx))
638 .unwrap()
639 );
640 assert_set_eq!(
641 cloned_editor
642 .update(cx, |e, cx| e.selections.display_ranges(cx))
643 .unwrap(),
644 editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap()
647 );
648}
649
650#[gpui::test]
651async fn test_navigation_history(cx: &mut TestAppContext) {
652 init_test(cx, |_| {});
653
654 use workspace::item::Item;
655
656 let fs = FakeFs::new(cx.executor());
657 let project = Project::test(fs, [], cx).await;
658 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
659 let pane = workspace
660 .update(cx, |workspace, _| workspace.active_pane().clone())
661 .unwrap();
662
663 _ = workspace.update(cx, |_v, cx| {
664 cx.new_view(|cx| {
665 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
666 let mut editor = build_editor(buffer.clone(), cx);
667 let handle = cx.view();
668 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
669
670 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
671 editor.nav_history.as_mut().unwrap().pop_backward(cx)
672 }
673
674 // Move the cursor a small distance.
675 // Nothing is added to the navigation history.
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
679 ])
680 });
681 editor.change_selections(None, cx, |s| {
682 s.select_display_ranges([
683 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
684 ])
685 });
686 assert!(pop_history(&mut editor, cx).is_none());
687
688 // Move the cursor a large distance.
689 // The history can jump back to the previous position.
690 editor.change_selections(None, cx, |s| {
691 s.select_display_ranges([
692 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
693 ])
694 });
695 let nav_entry = pop_history(&mut editor, cx).unwrap();
696 editor.navigate(nav_entry.data.unwrap(), cx);
697 assert_eq!(nav_entry.item.id(), cx.entity_id());
698 assert_eq!(
699 editor.selections.display_ranges(cx),
700 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
701 );
702 assert!(pop_history(&mut editor, cx).is_none());
703
704 // Move the cursor a small distance via the mouse.
705 // Nothing is added to the navigation history.
706 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
707 editor.end_selection(cx);
708 assert_eq!(
709 editor.selections.display_ranges(cx),
710 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
711 );
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance via the mouse.
715 // The history can jump back to the previous position.
716 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
717 editor.end_selection(cx);
718 assert_eq!(
719 editor.selections.display_ranges(cx),
720 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
721 );
722 let nav_entry = pop_history(&mut editor, cx).unwrap();
723 editor.navigate(nav_entry.data.unwrap(), cx);
724 assert_eq!(nav_entry.item.id(), cx.entity_id());
725 assert_eq!(
726 editor.selections.display_ranges(cx),
727 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
728 );
729 assert!(pop_history(&mut editor, cx).is_none());
730
731 // Set scroll position to check later
732 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
733 let original_scroll_position = editor.scroll_manager.anchor();
734
735 // Jump to the end of the document and adjust scroll
736 editor.move_to_end(&MoveToEnd, cx);
737 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
738 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 let nav_entry = pop_history(&mut editor, cx).unwrap();
741 editor.navigate(nav_entry.data.unwrap(), cx);
742 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
743
744 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
745 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
746 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
747 let invalid_point = Point::new(9999, 0);
748 editor.navigate(
749 Box::new(NavigationData {
750 cursor_anchor: invalid_anchor,
751 cursor_position: invalid_point,
752 scroll_anchor: ScrollAnchor {
753 anchor: invalid_anchor,
754 offset: Default::default(),
755 },
756 scroll_top_row: invalid_point.row,
757 }),
758 cx,
759 );
760 assert_eq!(
761 editor.selections.display_ranges(cx),
762 &[editor.max_point(cx)..editor.max_point(cx)]
763 );
764 assert_eq!(
765 editor.scroll_position(cx),
766 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
767 );
768
769 editor
770 })
771 });
772}
773
774#[gpui::test]
775fn test_cancel(cx: &mut TestAppContext) {
776 init_test(cx, |_| {});
777
778 let view = cx.add_window(|cx| {
779 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
780 build_editor(buffer, cx)
781 });
782
783 _ = view.update(cx, |view, cx| {
784 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
785 view.update_selection(
786 DisplayPoint::new(DisplayRow(1), 1),
787 0,
788 gpui::Point::<f32>::default(),
789 cx,
790 );
791 view.end_selection(cx);
792
793 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
794 view.update_selection(
795 DisplayPoint::new(DisplayRow(0), 3),
796 0,
797 gpui::Point::<f32>::default(),
798 cx,
799 );
800 view.end_selection(cx);
801 assert_eq!(
802 view.selections.display_ranges(cx),
803 [
804 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
805 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
806 ]
807 );
808 });
809
810 _ = view.update(cx, |view, cx| {
811 view.cancel(&Cancel, cx);
812 assert_eq!(
813 view.selections.display_ranges(cx),
814 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
815 );
816 });
817
818 _ = view.update(cx, |view, cx| {
819 view.cancel(&Cancel, cx);
820 assert_eq!(
821 view.selections.display_ranges(cx),
822 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
823 );
824 });
825}
826
827#[gpui::test]
828fn test_fold_action(cx: &mut TestAppContext) {
829 init_test(cx, |_| {});
830
831 let view = cx.add_window(|cx| {
832 let buffer = MultiBuffer::build_simple(
833 &"
834 impl Foo {
835 // Hello!
836
837 fn a() {
838 1
839 }
840
841 fn b() {
842 2
843 }
844
845 fn c() {
846 3
847 }
848 }
849 "
850 .unindent(),
851 cx,
852 );
853 build_editor(buffer.clone(), cx)
854 });
855
856 _ = view.update(cx, |view, cx| {
857 view.change_selections(None, cx, |s| {
858 s.select_display_ranges([
859 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
860 ]);
861 });
862 view.fold(&Fold, cx);
863 assert_eq!(
864 view.display_text(cx),
865 "
866 impl Foo {
867 // Hello!
868
869 fn a() {
870 1
871 }
872
873 fn b() {⋯
874 }
875
876 fn c() {⋯
877 }
878 }
879 "
880 .unindent(),
881 );
882
883 view.fold(&Fold, cx);
884 assert_eq!(
885 view.display_text(cx),
886 "
887 impl Foo {⋯
888 }
889 "
890 .unindent(),
891 );
892
893 view.unfold_lines(&UnfoldLines, cx);
894 assert_eq!(
895 view.display_text(cx),
896 "
897 impl Foo {
898 // Hello!
899
900 fn a() {
901 1
902 }
903
904 fn b() {⋯
905 }
906
907 fn c() {⋯
908 }
909 }
910 "
911 .unindent(),
912 );
913
914 view.unfold_lines(&UnfoldLines, cx);
915 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
916 });
917}
918
919#[gpui::test]
920fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
921 init_test(cx, |_| {});
922
923 let view = cx.add_window(|cx| {
924 let buffer = MultiBuffer::build_simple(
925 &"
926 class Foo:
927 # Hello!
928
929 def a():
930 print(1)
931
932 def b():
933 print(2)
934
935 def c():
936 print(3)
937 "
938 .unindent(),
939 cx,
940 );
941 build_editor(buffer.clone(), cx)
942 });
943
944 _ = view.update(cx, |view, cx| {
945 view.change_selections(None, cx, |s| {
946 s.select_display_ranges([
947 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
948 ]);
949 });
950 view.fold(&Fold, cx);
951 assert_eq!(
952 view.display_text(cx),
953 "
954 class Foo:
955 # Hello!
956
957 def a():
958 print(1)
959
960 def b():⋯
961
962 def c():⋯
963 "
964 .unindent(),
965 );
966
967 view.fold(&Fold, cx);
968 assert_eq!(
969 view.display_text(cx),
970 "
971 class Foo:⋯
972 "
973 .unindent(),
974 );
975
976 view.unfold_lines(&UnfoldLines, cx);
977 assert_eq!(
978 view.display_text(cx),
979 "
980 class Foo:
981 # Hello!
982
983 def a():
984 print(1)
985
986 def b():⋯
987
988 def c():⋯
989 "
990 .unindent(),
991 );
992
993 view.unfold_lines(&UnfoldLines, cx);
994 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
995 });
996}
997
998#[gpui::test]
999fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1000 init_test(cx, |_| {});
1001
1002 let view = cx.add_window(|cx| {
1003 let buffer = MultiBuffer::build_simple(
1004 &"
1005 class Foo:
1006 # Hello!
1007
1008 def a():
1009 print(1)
1010
1011 def b():
1012 print(2)
1013
1014
1015 def c():
1016 print(3)
1017
1018
1019 "
1020 .unindent(),
1021 cx,
1022 );
1023 build_editor(buffer.clone(), cx)
1024 });
1025
1026 _ = view.update(cx, |view, cx| {
1027 view.change_selections(None, cx, |s| {
1028 s.select_display_ranges([
1029 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1030 ]);
1031 });
1032 view.fold(&Fold, cx);
1033 assert_eq!(
1034 view.display_text(cx),
1035 "
1036 class Foo:
1037 # Hello!
1038
1039 def a():
1040 print(1)
1041
1042 def b():⋯
1043
1044
1045 def c():⋯
1046
1047
1048 "
1049 .unindent(),
1050 );
1051
1052 view.fold(&Fold, cx);
1053 assert_eq!(
1054 view.display_text(cx),
1055 "
1056 class Foo:⋯
1057
1058
1059 "
1060 .unindent(),
1061 );
1062
1063 view.unfold_lines(&UnfoldLines, cx);
1064 assert_eq!(
1065 view.display_text(cx),
1066 "
1067 class Foo:
1068 # Hello!
1069
1070 def a():
1071 print(1)
1072
1073 def b():⋯
1074
1075
1076 def c():⋯
1077
1078
1079 "
1080 .unindent(),
1081 );
1082
1083 view.unfold_lines(&UnfoldLines, cx);
1084 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1085 });
1086}
1087
1088#[gpui::test]
1089fn test_fold_at_level(cx: &mut TestAppContext) {
1090 init_test(cx, |_| {});
1091
1092 let view = cx.add_window(|cx| {
1093 let buffer = MultiBuffer::build_simple(
1094 &"
1095 class Foo:
1096 # Hello!
1097
1098 def a():
1099 print(1)
1100
1101 def b():
1102 print(2)
1103
1104
1105 class Bar:
1106 # World!
1107
1108 def a():
1109 print(1)
1110
1111 def b():
1112 print(2)
1113
1114
1115 "
1116 .unindent(),
1117 cx,
1118 );
1119 build_editor(buffer.clone(), cx)
1120 });
1121
1122 _ = view.update(cx, |view, cx| {
1123 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1124 assert_eq!(
1125 view.display_text(cx),
1126 "
1127 class Foo:
1128 # Hello!
1129
1130 def a():⋯
1131
1132 def b():⋯
1133
1134
1135 class Bar:
1136 # World!
1137
1138 def a():⋯
1139
1140 def b():⋯
1141
1142
1143 "
1144 .unindent(),
1145 );
1146
1147 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1148 assert_eq!(
1149 view.display_text(cx),
1150 "
1151 class Foo:⋯
1152
1153
1154 class Bar:⋯
1155
1156
1157 "
1158 .unindent(),
1159 );
1160
1161 view.unfold_all(&UnfoldAll, cx);
1162 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1163 assert_eq!(
1164 view.display_text(cx),
1165 "
1166 class Foo:
1167 # Hello!
1168
1169 def a():
1170 print(1)
1171
1172 def b():
1173 print(2)
1174
1175
1176 class Bar:
1177 # World!
1178
1179 def a():
1180 print(1)
1181
1182 def b():
1183 print(2)
1184
1185
1186 "
1187 .unindent(),
1188 );
1189
1190 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1191 });
1192}
1193
1194#[gpui::test]
1195fn test_move_cursor(cx: &mut TestAppContext) {
1196 init_test(cx, |_| {});
1197
1198 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1199 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1200
1201 buffer.update(cx, |buffer, cx| {
1202 buffer.edit(
1203 vec![
1204 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1205 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1206 ],
1207 None,
1208 cx,
1209 );
1210 });
1211 _ = view.update(cx, |view, cx| {
1212 assert_eq!(
1213 view.selections.display_ranges(cx),
1214 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1215 );
1216
1217 view.move_down(&MoveDown, cx);
1218 assert_eq!(
1219 view.selections.display_ranges(cx),
1220 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1221 );
1222
1223 view.move_right(&MoveRight, cx);
1224 assert_eq!(
1225 view.selections.display_ranges(cx),
1226 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1227 );
1228
1229 view.move_left(&MoveLeft, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1233 );
1234
1235 view.move_up(&MoveUp, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1239 );
1240
1241 view.move_to_end(&MoveToEnd, cx);
1242 assert_eq!(
1243 view.selections.display_ranges(cx),
1244 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1245 );
1246
1247 view.move_to_beginning(&MoveToBeginning, cx);
1248 assert_eq!(
1249 view.selections.display_ranges(cx),
1250 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1251 );
1252
1253 view.change_selections(None, cx, |s| {
1254 s.select_display_ranges([
1255 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1256 ]);
1257 });
1258 view.select_to_beginning(&SelectToBeginning, cx);
1259 assert_eq!(
1260 view.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 view.select_to_end(&SelectToEnd, cx);
1265 assert_eq!(
1266 view.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1268 );
1269 });
1270}
1271
1272// TODO: Re-enable this test
1273#[cfg(target_os = "macos")]
1274#[gpui::test]
1275fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1276 init_test(cx, |_| {});
1277
1278 let view = cx.add_window(|cx| {
1279 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1280 build_editor(buffer.clone(), cx)
1281 });
1282
1283 assert_eq!('ⓐ'.len_utf8(), 3);
1284 assert_eq!('α'.len_utf8(), 2);
1285
1286 _ = view.update(cx, |view, cx| {
1287 view.fold_creases(
1288 vec![
1289 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1291 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1292 ],
1293 true,
1294 cx,
1295 );
1296 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1297
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ".len())]
1307 );
1308 view.move_right(&MoveRight, cx);
1309 assert_eq!(
1310 view.selections.display_ranges(cx),
1311 &[empty_range(0, "ⓐⓑ⋯".len())]
1312 );
1313
1314 view.move_down(&MoveDown, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯e".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab⋯".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "ab".len())]
1328 );
1329 view.move_left(&MoveLeft, cx);
1330 assert_eq!(
1331 view.selections.display_ranges(cx),
1332 &[empty_range(1, "a".len())]
1333 );
1334
1335 view.move_down(&MoveDown, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "α".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯".len())]
1349 );
1350 view.move_right(&MoveRight, cx);
1351 assert_eq!(
1352 view.selections.display_ranges(cx),
1353 &[empty_range(2, "αβ⋯ε".len())]
1354 );
1355
1356 view.move_up(&MoveUp, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 view.move_down(&MoveDown, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(2, "αβ⋯ε".len())]
1365 );
1366 view.move_up(&MoveUp, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[empty_range(1, "ab⋯e".len())]
1370 );
1371
1372 view.move_up(&MoveUp, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐⓑ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "ⓐ".len())]
1381 );
1382 view.move_left(&MoveLeft, cx);
1383 assert_eq!(
1384 view.selections.display_ranges(cx),
1385 &[empty_range(0, "".len())]
1386 );
1387 });
1388}
1389
1390#[gpui::test]
1391fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1392 init_test(cx, |_| {});
1393
1394 let view = cx.add_window(|cx| {
1395 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1396 build_editor(buffer.clone(), cx)
1397 });
1398 _ = view.update(cx, |view, cx| {
1399 view.change_selections(None, cx, |s| {
1400 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1401 });
1402
1403 // moving above start of document should move selection to start of document,
1404 // but the next move down should still be at the original goal_x
1405 view.move_up(&MoveUp, cx);
1406 assert_eq!(
1407 view.selections.display_ranges(cx),
1408 &[empty_range(0, "".len())]
1409 );
1410
1411 view.move_down(&MoveDown, cx);
1412 assert_eq!(
1413 view.selections.display_ranges(cx),
1414 &[empty_range(1, "abcd".len())]
1415 );
1416
1417 view.move_down(&MoveDown, cx);
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[empty_range(2, "αβγ".len())]
1421 );
1422
1423 view.move_down(&MoveDown, cx);
1424 assert_eq!(
1425 view.selections.display_ranges(cx),
1426 &[empty_range(3, "abcd".len())]
1427 );
1428
1429 view.move_down(&MoveDown, cx);
1430 assert_eq!(
1431 view.selections.display_ranges(cx),
1432 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1433 );
1434
1435 // moving past end of document should not change goal_x
1436 view.move_down(&MoveDown, cx);
1437 assert_eq!(
1438 view.selections.display_ranges(cx),
1439 &[empty_range(5, "".len())]
1440 );
1441
1442 view.move_down(&MoveDown, cx);
1443 assert_eq!(
1444 view.selections.display_ranges(cx),
1445 &[empty_range(5, "".len())]
1446 );
1447
1448 view.move_up(&MoveUp, cx);
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1452 );
1453
1454 view.move_up(&MoveUp, cx);
1455 assert_eq!(
1456 view.selections.display_ranges(cx),
1457 &[empty_range(3, "abcd".len())]
1458 );
1459
1460 view.move_up(&MoveUp, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[empty_range(2, "αβγ".len())]
1464 );
1465 });
1466}
1467
1468#[gpui::test]
1469fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1470 init_test(cx, |_| {});
1471 let move_to_beg = MoveToBeginningOfLine {
1472 stop_at_soft_wraps: true,
1473 };
1474
1475 let move_to_end = MoveToEndOfLine {
1476 stop_at_soft_wraps: true,
1477 };
1478
1479 let view = cx.add_window(|cx| {
1480 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1481 build_editor(buffer, cx)
1482 });
1483 _ = view.update(cx, |view, cx| {
1484 view.change_selections(None, cx, |s| {
1485 s.select_display_ranges([
1486 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1487 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1488 ]);
1489 });
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_beginning_of_line(&move_to_beg, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1498 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1499 ]
1500 );
1501 });
1502
1503 _ = view.update(cx, |view, cx| {
1504 view.move_to_beginning_of_line(&move_to_beg, cx);
1505 assert_eq!(
1506 view.selections.display_ranges(cx),
1507 &[
1508 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1509 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1510 ]
1511 );
1512 });
1513
1514 _ = view.update(cx, |view, cx| {
1515 view.move_to_beginning_of_line(&move_to_beg, cx);
1516 assert_eq!(
1517 view.selections.display_ranges(cx),
1518 &[
1519 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1520 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1521 ]
1522 );
1523 });
1524
1525 _ = view.update(cx, |view, cx| {
1526 view.move_to_end_of_line(&move_to_end, cx);
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1531 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1532 ]
1533 );
1534 });
1535
1536 // Moving to the end of line again is a no-op.
1537 _ = view.update(cx, |view, cx| {
1538 view.move_to_end_of_line(&move_to_end, cx);
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1543 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.move_left(&MoveLeft, cx);
1550 view.select_to_beginning_of_line(
1551 &SelectToBeginningOfLine {
1552 stop_at_soft_wraps: true,
1553 },
1554 cx,
1555 );
1556 assert_eq!(
1557 view.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1561 ]
1562 );
1563 });
1564
1565 _ = view.update(cx, |view, cx| {
1566 view.select_to_beginning_of_line(
1567 &SelectToBeginningOfLine {
1568 stop_at_soft_wraps: true,
1569 },
1570 cx,
1571 );
1572 assert_eq!(
1573 view.selections.display_ranges(cx),
1574 &[
1575 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1576 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1577 ]
1578 );
1579 });
1580
1581 _ = view.update(cx, |view, cx| {
1582 view.select_to_beginning_of_line(
1583 &SelectToBeginningOfLine {
1584 stop_at_soft_wraps: true,
1585 },
1586 cx,
1587 );
1588 assert_eq!(
1589 view.selections.display_ranges(cx),
1590 &[
1591 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1592 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1593 ]
1594 );
1595 });
1596
1597 _ = view.update(cx, |view, cx| {
1598 view.select_to_end_of_line(
1599 &SelectToEndOfLine {
1600 stop_at_soft_wraps: true,
1601 },
1602 cx,
1603 );
1604 assert_eq!(
1605 view.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1609 ]
1610 );
1611 });
1612
1613 _ = view.update(cx, |view, cx| {
1614 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1615 assert_eq!(view.display_text(cx), "ab\n de");
1616 assert_eq!(
1617 view.selections.display_ranges(cx),
1618 &[
1619 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1620 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1621 ]
1622 );
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1627 assert_eq!(view.display_text(cx), "\n");
1628 assert_eq!(
1629 view.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636}
1637
1638#[gpui::test]
1639fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1640 init_test(cx, |_| {});
1641 let move_to_beg = MoveToBeginningOfLine {
1642 stop_at_soft_wraps: false,
1643 };
1644
1645 let move_to_end = MoveToEndOfLine {
1646 stop_at_soft_wraps: false,
1647 };
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656
1657 // We expect the following lines after wrapping
1658 // ```
1659 // thequickbrownfox
1660 // jumpedoverthelazydo
1661 // gs
1662 // ```
1663 // The final `gs` was soft-wrapped onto a new line.
1664 assert_eq!(
1665 "thequickbrownfox\njumpedoverthelaz\nydogs",
1666 view.display_text(cx),
1667 );
1668
1669 // First, let's assert behavior on the first line, that was not soft-wrapped.
1670 // Start the cursor at the `k` on the first line
1671 view.change_selections(None, cx, |s| {
1672 s.select_display_ranges([
1673 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1674 ]);
1675 });
1676
1677 // Moving to the beginning of the line should put us at the beginning of the line.
1678 view.move_to_beginning_of_line(&move_to_beg, cx);
1679 assert_eq!(
1680 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1681 view.selections.display_ranges(cx)
1682 );
1683
1684 // Moving to the end of the line should put us at the end of the line.
1685 view.move_to_end_of_line(&move_to_end, cx);
1686 assert_eq!(
1687 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1688 view.selections.display_ranges(cx)
1689 );
1690
1691 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1692 // Start the cursor at the last line (`y` that was wrapped to a new line)
1693 view.change_selections(None, cx, |s| {
1694 s.select_display_ranges([
1695 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1696 ]);
1697 });
1698
1699 // Moving to the beginning of the line should put us at the start of the second line of
1700 // display text, i.e., the `j`.
1701 view.move_to_beginning_of_line(&move_to_beg, cx);
1702 assert_eq!(
1703 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1704 view.selections.display_ranges(cx)
1705 );
1706
1707 // Moving to the beginning of the line again should be a no-op.
1708 view.move_to_beginning_of_line(&move_to_beg, cx);
1709 assert_eq!(
1710 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1711 view.selections.display_ranges(cx)
1712 );
1713
1714 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1715 // next display line.
1716 view.move_to_end_of_line(&move_to_end, cx);
1717 assert_eq!(
1718 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1719 view.selections.display_ranges(cx)
1720 );
1721
1722 // Moving to the end of the line again should be a no-op.
1723 view.move_to_end_of_line(&move_to_end, cx);
1724 assert_eq!(
1725 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1726 view.selections.display_ranges(cx)
1727 );
1728 });
1729}
1730
1731#[gpui::test]
1732fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1733 init_test(cx, |_| {});
1734
1735 let view = cx.add_window(|cx| {
1736 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1737 build_editor(buffer, cx)
1738 });
1739 _ = view.update(cx, |view, cx| {
1740 view.change_selections(None, cx, |s| {
1741 s.select_display_ranges([
1742 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1743 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1744 ])
1745 });
1746
1747 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1748 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1749
1750 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1751 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1752
1753 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1754 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1755
1756 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1757 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1758
1759 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1760 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1761
1762 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1763 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1764
1765 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1766 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1767
1768 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1769 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1770
1771 view.move_right(&MoveRight, cx);
1772 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1773 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1774
1775 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1776 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1777
1778 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1779 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1780 });
1781}
1782
1783#[gpui::test]
1784fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1785 init_test(cx, |_| {});
1786
1787 let view = cx.add_window(|cx| {
1788 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1789 build_editor(buffer, cx)
1790 });
1791
1792 _ = view.update(cx, |view, cx| {
1793 view.set_wrap_width(Some(140.0.into()), cx);
1794 assert_eq!(
1795 view.display_text(cx),
1796 "use one::{\n two::three::\n four::five\n};"
1797 );
1798
1799 view.change_selections(None, cx, |s| {
1800 s.select_display_ranges([
1801 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1802 ]);
1803 });
1804
1805 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1806 assert_eq!(
1807 view.selections.display_ranges(cx),
1808 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1809 );
1810
1811 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1812 assert_eq!(
1813 view.selections.display_ranges(cx),
1814 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1815 );
1816
1817 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1818 assert_eq!(
1819 view.selections.display_ranges(cx),
1820 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1821 );
1822
1823 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1824 assert_eq!(
1825 view.selections.display_ranges(cx),
1826 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1827 );
1828
1829 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1830 assert_eq!(
1831 view.selections.display_ranges(cx),
1832 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1833 );
1834
1835 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1836 assert_eq!(
1837 view.selections.display_ranges(cx),
1838 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1839 );
1840 });
1841}
1842
1843#[gpui::test]
1844async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1845 init_test(cx, |_| {});
1846 let mut cx = EditorTestContext::new(cx).await;
1847
1848 let line_height = cx.editor(|editor, cx| {
1849 editor
1850 .style()
1851 .unwrap()
1852 .text
1853 .line_height_in_pixels(cx.rem_size())
1854 });
1855 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1856
1857 cx.set_state(
1858 &r#"ˇone
1859 two
1860
1861 three
1862 fourˇ
1863 five
1864
1865 six"#
1866 .unindent(),
1867 );
1868
1869 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1870 cx.assert_editor_state(
1871 &r#"one
1872 two
1873 ˇ
1874 three
1875 four
1876 five
1877 ˇ
1878 six"#
1879 .unindent(),
1880 );
1881
1882 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1883 cx.assert_editor_state(
1884 &r#"one
1885 two
1886
1887 three
1888 four
1889 five
1890 ˇ
1891 sixˇ"#
1892 .unindent(),
1893 );
1894
1895 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1896 cx.assert_editor_state(
1897 &r#"one
1898 two
1899
1900 three
1901 four
1902 five
1903
1904 sixˇ"#
1905 .unindent(),
1906 );
1907
1908 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1909 cx.assert_editor_state(
1910 &r#"one
1911 two
1912
1913 three
1914 four
1915 five
1916 ˇ
1917 six"#
1918 .unindent(),
1919 );
1920
1921 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1922 cx.assert_editor_state(
1923 &r#"one
1924 two
1925 ˇ
1926 three
1927 four
1928 five
1929
1930 six"#
1931 .unindent(),
1932 );
1933
1934 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1935 cx.assert_editor_state(
1936 &r#"ˇone
1937 two
1938
1939 three
1940 four
1941 five
1942
1943 six"#
1944 .unindent(),
1945 );
1946}
1947
1948#[gpui::test]
1949async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1950 init_test(cx, |_| {});
1951 let mut cx = EditorTestContext::new(cx).await;
1952 let line_height = cx.editor(|editor, cx| {
1953 editor
1954 .style()
1955 .unwrap()
1956 .text
1957 .line_height_in_pixels(cx.rem_size())
1958 });
1959 let window = cx.window;
1960 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1961
1962 cx.set_state(
1963 r#"ˇone
1964 two
1965 three
1966 four
1967 five
1968 six
1969 seven
1970 eight
1971 nine
1972 ten
1973 "#,
1974 );
1975
1976 cx.update_editor(|editor, cx| {
1977 assert_eq!(
1978 editor.snapshot(cx).scroll_position(),
1979 gpui::Point::new(0., 0.)
1980 );
1981 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1982 assert_eq!(
1983 editor.snapshot(cx).scroll_position(),
1984 gpui::Point::new(0., 3.)
1985 );
1986 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1987 assert_eq!(
1988 editor.snapshot(cx).scroll_position(),
1989 gpui::Point::new(0., 6.)
1990 );
1991 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1992 assert_eq!(
1993 editor.snapshot(cx).scroll_position(),
1994 gpui::Point::new(0., 3.)
1995 );
1996
1997 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1998 assert_eq!(
1999 editor.snapshot(cx).scroll_position(),
2000 gpui::Point::new(0., 1.)
2001 );
2002 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2003 assert_eq!(
2004 editor.snapshot(cx).scroll_position(),
2005 gpui::Point::new(0., 3.)
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.update_editor(|editor, cx| {
2016 editor.set_vertical_scroll_margin(2, cx);
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(cx.rem_size())
2022 });
2023 let window = cx.window;
2024 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2025
2026 cx.set_state(
2027 r#"ˇone
2028 two
2029 three
2030 four
2031 five
2032 six
2033 seven
2034 eight
2035 nine
2036 ten
2037 "#,
2038 );
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 0.0)
2043 );
2044 });
2045
2046 // Add a cursor below the visible area. Since both cursors cannot fit
2047 // on screen, the editor autoscrolls to reveal the newest cursor, and
2048 // allows the vertical scroll margin below that cursor.
2049 cx.update_editor(|editor, cx| {
2050 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2051 selections.select_ranges([
2052 Point::new(0, 0)..Point::new(0, 0),
2053 Point::new(6, 0)..Point::new(6, 0),
2054 ]);
2055 })
2056 });
2057 cx.update_editor(|editor, cx| {
2058 assert_eq!(
2059 editor.snapshot(cx).scroll_position(),
2060 gpui::Point::new(0., 3.0)
2061 );
2062 });
2063
2064 // Move down. The editor cursor scrolls down to track the newest cursor.
2065 cx.update_editor(|editor, cx| {
2066 editor.move_down(&Default::default(), cx);
2067 });
2068 cx.update_editor(|editor, cx| {
2069 assert_eq!(
2070 editor.snapshot(cx).scroll_position(),
2071 gpui::Point::new(0., 4.0)
2072 );
2073 });
2074
2075 // Add a cursor above the visible area. Since both cursors fit on screen,
2076 // the editor scrolls to show both.
2077 cx.update_editor(|editor, cx| {
2078 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2079 selections.select_ranges([
2080 Point::new(1, 0)..Point::new(1, 0),
2081 Point::new(6, 0)..Point::new(6, 0),
2082 ]);
2083 })
2084 });
2085 cx.update_editor(|editor, cx| {
2086 assert_eq!(
2087 editor.snapshot(cx).scroll_position(),
2088 gpui::Point::new(0., 1.0)
2089 );
2090 });
2091}
2092
2093#[gpui::test]
2094async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2095 init_test(cx, |_| {});
2096 let mut cx = EditorTestContext::new(cx).await;
2097
2098 let line_height = cx.editor(|editor, cx| {
2099 editor
2100 .style()
2101 .unwrap()
2102 .text
2103 .line_height_in_pixels(cx.rem_size())
2104 });
2105 let window = cx.window;
2106 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2107 cx.set_state(
2108 &r#"
2109 ˇone
2110 two
2111 threeˇ
2112 four
2113 five
2114 six
2115 seven
2116 eight
2117 nine
2118 ten
2119 "#
2120 .unindent(),
2121 );
2122
2123 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2124 cx.assert_editor_state(
2125 &r#"
2126 one
2127 two
2128 three
2129 ˇfour
2130 five
2131 sixˇ
2132 seven
2133 eight
2134 nine
2135 ten
2136 "#
2137 .unindent(),
2138 );
2139
2140 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2141 cx.assert_editor_state(
2142 &r#"
2143 one
2144 two
2145 three
2146 four
2147 five
2148 six
2149 ˇseven
2150 eight
2151 nineˇ
2152 ten
2153 "#
2154 .unindent(),
2155 );
2156
2157 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2158 cx.assert_editor_state(
2159 &r#"
2160 one
2161 two
2162 three
2163 ˇfour
2164 five
2165 sixˇ
2166 seven
2167 eight
2168 nine
2169 ten
2170 "#
2171 .unindent(),
2172 );
2173
2174 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2175 cx.assert_editor_state(
2176 &r#"
2177 ˇone
2178 two
2179 threeˇ
2180 four
2181 five
2182 six
2183 seven
2184 eight
2185 nine
2186 ten
2187 "#
2188 .unindent(),
2189 );
2190
2191 // Test select collapsing
2192 cx.update_editor(|editor, cx| {
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 editor.move_page_down(&MovePageDown::default(), cx);
2196 });
2197 cx.assert_editor_state(
2198 &r#"
2199 one
2200 two
2201 three
2202 four
2203 five
2204 six
2205 seven
2206 eight
2207 nine
2208 ˇten
2209 ˇ"#
2210 .unindent(),
2211 );
2212}
2213
2214#[gpui::test]
2215async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2216 init_test(cx, |_| {});
2217 let mut cx = EditorTestContext::new(cx).await;
2218 cx.set_state("one «two threeˇ» four");
2219 cx.update_editor(|editor, cx| {
2220 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2221 assert_eq!(editor.text(cx), " four");
2222 });
2223}
2224
2225#[gpui::test]
2226fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2227 init_test(cx, |_| {});
2228
2229 let view = cx.add_window(|cx| {
2230 let buffer = MultiBuffer::build_simple("one two three four", cx);
2231 build_editor(buffer.clone(), cx)
2232 });
2233
2234 _ = view.update(cx, |view, cx| {
2235 view.change_selections(None, cx, |s| {
2236 s.select_display_ranges([
2237 // an empty selection - the preceding word fragment is deleted
2238 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2239 // characters selected - they are deleted
2240 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2241 ])
2242 });
2243 view.delete_to_previous_word_start(
2244 &DeleteToPreviousWordStart {
2245 ignore_newlines: false,
2246 },
2247 cx,
2248 );
2249 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2250 });
2251
2252 _ = view.update(cx, |view, cx| {
2253 view.change_selections(None, cx, |s| {
2254 s.select_display_ranges([
2255 // an empty selection - the following word fragment is deleted
2256 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2257 // characters selected - they are deleted
2258 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2259 ])
2260 });
2261 view.delete_to_next_word_end(
2262 &DeleteToNextWordEnd {
2263 ignore_newlines: false,
2264 },
2265 cx,
2266 );
2267 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2268 });
2269}
2270
2271#[gpui::test]
2272fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2273 init_test(cx, |_| {});
2274
2275 let view = cx.add_window(|cx| {
2276 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2277 build_editor(buffer.clone(), cx)
2278 });
2279 let del_to_prev_word_start = DeleteToPreviousWordStart {
2280 ignore_newlines: false,
2281 };
2282 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2283 ignore_newlines: true,
2284 };
2285
2286 _ = view.update(cx, |view, cx| {
2287 view.change_selections(None, cx, |s| {
2288 s.select_display_ranges([
2289 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2290 ])
2291 });
2292 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2293 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2294 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2295 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2296 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2297 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2298 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2299 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2300 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2301 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2302 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2303 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2304 });
2305}
2306
2307#[gpui::test]
2308fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2309 init_test(cx, |_| {});
2310
2311 let view = cx.add_window(|cx| {
2312 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2313 build_editor(buffer.clone(), cx)
2314 });
2315 let del_to_next_word_end = DeleteToNextWordEnd {
2316 ignore_newlines: false,
2317 };
2318 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2319 ignore_newlines: true,
2320 };
2321
2322 _ = view.update(cx, |view, cx| {
2323 view.change_selections(None, cx, |s| {
2324 s.select_display_ranges([
2325 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2326 ])
2327 });
2328 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2329 assert_eq!(
2330 view.buffer.read(cx).read(cx).text(),
2331 "one\n two\nthree\n four"
2332 );
2333 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2334 assert_eq!(
2335 view.buffer.read(cx).read(cx).text(),
2336 "\n two\nthree\n four"
2337 );
2338 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2339 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2340 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2341 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2342 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2343 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2344 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2345 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2346 });
2347}
2348
2349#[gpui::test]
2350fn test_newline(cx: &mut TestAppContext) {
2351 init_test(cx, |_| {});
2352
2353 let view = cx.add_window(|cx| {
2354 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2355 build_editor(buffer.clone(), cx)
2356 });
2357
2358 _ = view.update(cx, |view, cx| {
2359 view.change_selections(None, cx, |s| {
2360 s.select_display_ranges([
2361 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2362 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2363 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2364 ])
2365 });
2366
2367 view.newline(&Newline, cx);
2368 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2369 });
2370}
2371
2372#[gpui::test]
2373fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 let editor = cx.add_window(|cx| {
2377 let buffer = MultiBuffer::build_simple(
2378 "
2379 a
2380 b(
2381 X
2382 )
2383 c(
2384 X
2385 )
2386 "
2387 .unindent()
2388 .as_str(),
2389 cx,
2390 );
2391 let mut editor = build_editor(buffer.clone(), cx);
2392 editor.change_selections(None, cx, |s| {
2393 s.select_ranges([
2394 Point::new(2, 4)..Point::new(2, 5),
2395 Point::new(5, 4)..Point::new(5, 5),
2396 ])
2397 });
2398 editor
2399 });
2400
2401 _ = editor.update(cx, |editor, cx| {
2402 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2403 editor.buffer.update(cx, |buffer, cx| {
2404 buffer.edit(
2405 [
2406 (Point::new(1, 2)..Point::new(3, 0), ""),
2407 (Point::new(4, 2)..Point::new(6, 0), ""),
2408 ],
2409 None,
2410 cx,
2411 );
2412 assert_eq!(
2413 buffer.read(cx).text(),
2414 "
2415 a
2416 b()
2417 c()
2418 "
2419 .unindent()
2420 );
2421 });
2422 assert_eq!(
2423 editor.selections.ranges(cx),
2424 &[
2425 Point::new(1, 2)..Point::new(1, 2),
2426 Point::new(2, 2)..Point::new(2, 2),
2427 ],
2428 );
2429
2430 editor.newline(&Newline, cx);
2431 assert_eq!(
2432 editor.text(cx),
2433 "
2434 a
2435 b(
2436 )
2437 c(
2438 )
2439 "
2440 .unindent()
2441 );
2442
2443 // The selections are moved after the inserted newlines
2444 assert_eq!(
2445 editor.selections.ranges(cx),
2446 &[
2447 Point::new(2, 0)..Point::new(2, 0),
2448 Point::new(4, 0)..Point::new(4, 0),
2449 ],
2450 );
2451 });
2452}
2453
2454#[gpui::test]
2455async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2456 init_test(cx, |settings| {
2457 settings.defaults.tab_size = NonZeroU32::new(4)
2458 });
2459
2460 let language = Arc::new(
2461 Language::new(
2462 LanguageConfig::default(),
2463 Some(tree_sitter_rust::LANGUAGE.into()),
2464 )
2465 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2466 .unwrap(),
2467 );
2468
2469 let mut cx = EditorTestContext::new(cx).await;
2470 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2471 cx.set_state(indoc! {"
2472 const a: ˇA = (
2473 (ˇ
2474 «const_functionˇ»(ˇ),
2475 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2476 )ˇ
2477 ˇ);ˇ
2478 "});
2479
2480 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2481 cx.assert_editor_state(indoc! {"
2482 ˇ
2483 const a: A = (
2484 ˇ
2485 (
2486 ˇ
2487 ˇ
2488 const_function(),
2489 ˇ
2490 ˇ
2491 ˇ
2492 ˇ
2493 something_else,
2494 ˇ
2495 )
2496 ˇ
2497 ˇ
2498 );
2499 "});
2500}
2501
2502#[gpui::test]
2503async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2504 init_test(cx, |settings| {
2505 settings.defaults.tab_size = NonZeroU32::new(4)
2506 });
2507
2508 let language = Arc::new(
2509 Language::new(
2510 LanguageConfig::default(),
2511 Some(tree_sitter_rust::LANGUAGE.into()),
2512 )
2513 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2514 .unwrap(),
2515 );
2516
2517 let mut cx = EditorTestContext::new(cx).await;
2518 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2519 cx.set_state(indoc! {"
2520 const a: ˇA = (
2521 (ˇ
2522 «const_functionˇ»(ˇ),
2523 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2524 )ˇ
2525 ˇ);ˇ
2526 "});
2527
2528 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2529 cx.assert_editor_state(indoc! {"
2530 const a: A = (
2531 ˇ
2532 (
2533 ˇ
2534 const_function(),
2535 ˇ
2536 ˇ
2537 something_else,
2538 ˇ
2539 ˇ
2540 ˇ
2541 ˇ
2542 )
2543 ˇ
2544 );
2545 ˇ
2546 ˇ
2547 "});
2548}
2549
2550#[gpui::test]
2551async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2552 init_test(cx, |settings| {
2553 settings.defaults.tab_size = NonZeroU32::new(4)
2554 });
2555
2556 let language = Arc::new(Language::new(
2557 LanguageConfig {
2558 line_comments: vec!["//".into()],
2559 ..LanguageConfig::default()
2560 },
2561 None,
2562 ));
2563 {
2564 let mut cx = EditorTestContext::new(cx).await;
2565 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2566 cx.set_state(indoc! {"
2567 // Fooˇ
2568 "});
2569
2570 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2571 cx.assert_editor_state(indoc! {"
2572 // Foo
2573 //ˇ
2574 "});
2575 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2576 cx.set_state(indoc! {"
2577 ˇ// Foo
2578 "});
2579 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2580 cx.assert_editor_state(indoc! {"
2581
2582 ˇ// Foo
2583 "});
2584 }
2585 // Ensure that comment continuations can be disabled.
2586 update_test_language_settings(cx, |settings| {
2587 settings.defaults.extend_comment_on_newline = Some(false);
2588 });
2589 let mut cx = EditorTestContext::new(cx).await;
2590 cx.set_state(indoc! {"
2591 // Fooˇ
2592 "});
2593 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2594 cx.assert_editor_state(indoc! {"
2595 // Foo
2596 ˇ
2597 "});
2598}
2599
2600#[gpui::test]
2601fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2602 init_test(cx, |_| {});
2603
2604 let editor = cx.add_window(|cx| {
2605 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2606 let mut editor = build_editor(buffer.clone(), cx);
2607 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2608 editor
2609 });
2610
2611 _ = editor.update(cx, |editor, cx| {
2612 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2613 editor.buffer.update(cx, |buffer, cx| {
2614 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2615 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2616 });
2617 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2618
2619 editor.insert("Z", cx);
2620 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2621
2622 // The selections are moved after the inserted characters
2623 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2624 });
2625}
2626
2627#[gpui::test]
2628async fn test_tab(cx: &mut gpui::TestAppContext) {
2629 init_test(cx, |settings| {
2630 settings.defaults.tab_size = NonZeroU32::new(3)
2631 });
2632
2633 let mut cx = EditorTestContext::new(cx).await;
2634 cx.set_state(indoc! {"
2635 ˇabˇc
2636 ˇ🏀ˇ🏀ˇefg
2637 dˇ
2638 "});
2639 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2640 cx.assert_editor_state(indoc! {"
2641 ˇab ˇc
2642 ˇ🏀 ˇ🏀 ˇefg
2643 d ˇ
2644 "});
2645
2646 cx.set_state(indoc! {"
2647 a
2648 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 a
2653 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2654 "});
2655}
2656
2657#[gpui::test]
2658async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2659 init_test(cx, |_| {});
2660
2661 let mut cx = EditorTestContext::new(cx).await;
2662 let language = Arc::new(
2663 Language::new(
2664 LanguageConfig::default(),
2665 Some(tree_sitter_rust::LANGUAGE.into()),
2666 )
2667 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2668 .unwrap(),
2669 );
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671
2672 // cursors that are already at the suggested indent level insert
2673 // a soft tab. cursors that are to the left of the suggested indent
2674 // auto-indent their line.
2675 cx.set_state(indoc! {"
2676 ˇ
2677 const a: B = (
2678 c(
2679 d(
2680 ˇ
2681 )
2682 ˇ
2683 ˇ )
2684 );
2685 "});
2686 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2687 cx.assert_editor_state(indoc! {"
2688 ˇ
2689 const a: B = (
2690 c(
2691 d(
2692 ˇ
2693 )
2694 ˇ
2695 ˇ)
2696 );
2697 "});
2698
2699 // handle auto-indent when there are multiple cursors on the same line
2700 cx.set_state(indoc! {"
2701 const a: B = (
2702 c(
2703 ˇ ˇ
2704 ˇ )
2705 );
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 const a: B = (
2710 c(
2711 ˇ
2712 ˇ)
2713 );
2714 "});
2715}
2716
2717#[gpui::test]
2718async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2719 init_test(cx, |settings| {
2720 settings.defaults.tab_size = NonZeroU32::new(4)
2721 });
2722
2723 let language = Arc::new(
2724 Language::new(
2725 LanguageConfig::default(),
2726 Some(tree_sitter_rust::LANGUAGE.into()),
2727 )
2728 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2729 .unwrap(),
2730 );
2731
2732 let mut cx = EditorTestContext::new(cx).await;
2733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2734 cx.set_state(indoc! {"
2735 fn a() {
2736 if b {
2737 \t ˇc
2738 }
2739 }
2740 "});
2741
2742 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2743 cx.assert_editor_state(indoc! {"
2744 fn a() {
2745 if b {
2746 ˇc
2747 }
2748 }
2749 "});
2750}
2751
2752#[gpui::test]
2753async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2754 init_test(cx, |settings| {
2755 settings.defaults.tab_size = NonZeroU32::new(4);
2756 });
2757
2758 let mut cx = EditorTestContext::new(cx).await;
2759
2760 cx.set_state(indoc! {"
2761 «oneˇ» «twoˇ»
2762 three
2763 four
2764 "});
2765 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2766 cx.assert_editor_state(indoc! {"
2767 «oneˇ» «twoˇ»
2768 three
2769 four
2770 "});
2771
2772 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2773 cx.assert_editor_state(indoc! {"
2774 «oneˇ» «twoˇ»
2775 three
2776 four
2777 "});
2778
2779 // select across line ending
2780 cx.set_state(indoc! {"
2781 one two
2782 t«hree
2783 ˇ» four
2784 "});
2785 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2786 cx.assert_editor_state(indoc! {"
2787 one two
2788 t«hree
2789 ˇ» four
2790 "});
2791
2792 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2793 cx.assert_editor_state(indoc! {"
2794 one two
2795 t«hree
2796 ˇ» four
2797 "});
2798
2799 // Ensure that indenting/outdenting works when the cursor is at column 0.
2800 cx.set_state(indoc! {"
2801 one two
2802 ˇthree
2803 four
2804 "});
2805 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2806 cx.assert_editor_state(indoc! {"
2807 one two
2808 ˇthree
2809 four
2810 "});
2811
2812 cx.set_state(indoc! {"
2813 one two
2814 ˇ three
2815 four
2816 "});
2817 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2818 cx.assert_editor_state(indoc! {"
2819 one two
2820 ˇthree
2821 four
2822 "});
2823}
2824
2825#[gpui::test]
2826async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2827 init_test(cx, |settings| {
2828 settings.defaults.hard_tabs = Some(true);
2829 });
2830
2831 let mut cx = EditorTestContext::new(cx).await;
2832
2833 // select two ranges on one line
2834 cx.set_state(indoc! {"
2835 «oneˇ» «twoˇ»
2836 three
2837 four
2838 "});
2839 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2840 cx.assert_editor_state(indoc! {"
2841 \t«oneˇ» «twoˇ»
2842 three
2843 four
2844 "});
2845 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2846 cx.assert_editor_state(indoc! {"
2847 \t\t«oneˇ» «twoˇ»
2848 three
2849 four
2850 "});
2851 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2852 cx.assert_editor_state(indoc! {"
2853 \t«oneˇ» «twoˇ»
2854 three
2855 four
2856 "});
2857 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across a line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ»four
2869 "});
2870 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 \tt«hree
2874 ˇ»four
2875 "});
2876 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2877 cx.assert_editor_state(indoc! {"
2878 one two
2879 \t\tt«hree
2880 ˇ»four
2881 "});
2882 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2883 cx.assert_editor_state(indoc! {"
2884 one two
2885 \tt«hree
2886 ˇ»four
2887 "});
2888 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2889 cx.assert_editor_state(indoc! {"
2890 one two
2891 t«hree
2892 ˇ»four
2893 "});
2894
2895 // Ensure that indenting/outdenting works when the cursor is at column 0.
2896 cx.set_state(indoc! {"
2897 one two
2898 ˇthree
2899 four
2900 "});
2901 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2902 cx.assert_editor_state(indoc! {"
2903 one two
2904 ˇthree
2905 four
2906 "});
2907 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2908 cx.assert_editor_state(indoc! {"
2909 one two
2910 \tˇthree
2911 four
2912 "});
2913 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2914 cx.assert_editor_state(indoc! {"
2915 one two
2916 ˇthree
2917 four
2918 "});
2919}
2920
2921#[gpui::test]
2922fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2923 init_test(cx, |settings| {
2924 settings.languages.extend([
2925 (
2926 "TOML".into(),
2927 LanguageSettingsContent {
2928 tab_size: NonZeroU32::new(2),
2929 ..Default::default()
2930 },
2931 ),
2932 (
2933 "Rust".into(),
2934 LanguageSettingsContent {
2935 tab_size: NonZeroU32::new(4),
2936 ..Default::default()
2937 },
2938 ),
2939 ]);
2940 });
2941
2942 let toml_language = Arc::new(Language::new(
2943 LanguageConfig {
2944 name: "TOML".into(),
2945 ..Default::default()
2946 },
2947 None,
2948 ));
2949 let rust_language = Arc::new(Language::new(
2950 LanguageConfig {
2951 name: "Rust".into(),
2952 ..Default::default()
2953 },
2954 None,
2955 ));
2956
2957 let toml_buffer =
2958 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2959 let rust_buffer = cx.new_model(|cx| {
2960 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2961 });
2962 let multibuffer = cx.new_model(|cx| {
2963 let mut multibuffer = MultiBuffer::new(ReadWrite);
2964 multibuffer.push_excerpts(
2965 toml_buffer.clone(),
2966 [ExcerptRange {
2967 context: Point::new(0, 0)..Point::new(2, 0),
2968 primary: None,
2969 }],
2970 cx,
2971 );
2972 multibuffer.push_excerpts(
2973 rust_buffer.clone(),
2974 [ExcerptRange {
2975 context: Point::new(0, 0)..Point::new(1, 0),
2976 primary: None,
2977 }],
2978 cx,
2979 );
2980 multibuffer
2981 });
2982
2983 cx.add_window(|cx| {
2984 let mut editor = build_editor(multibuffer, cx);
2985
2986 assert_eq!(
2987 editor.text(cx),
2988 indoc! {"
2989 a = 1
2990 b = 2
2991
2992 const c: usize = 3;
2993 "}
2994 );
2995
2996 select_ranges(
2997 &mut editor,
2998 indoc! {"
2999 «aˇ» = 1
3000 b = 2
3001
3002 «const c:ˇ» usize = 3;
3003 "},
3004 cx,
3005 );
3006
3007 editor.tab(&Tab, cx);
3008 assert_text_with_selections(
3009 &mut editor,
3010 indoc! {"
3011 «aˇ» = 1
3012 b = 2
3013
3014 «const c:ˇ» usize = 3;
3015 "},
3016 cx,
3017 );
3018 editor.tab_prev(&TabPrev, cx);
3019 assert_text_with_selections(
3020 &mut editor,
3021 indoc! {"
3022 «aˇ» = 1
3023 b = 2
3024
3025 «const c:ˇ» usize = 3;
3026 "},
3027 cx,
3028 );
3029
3030 editor
3031 });
3032}
3033
3034#[gpui::test]
3035async fn test_backspace(cx: &mut gpui::TestAppContext) {
3036 init_test(cx, |_| {});
3037
3038 let mut cx = EditorTestContext::new(cx).await;
3039
3040 // Basic backspace
3041 cx.set_state(indoc! {"
3042 onˇe two three
3043 fou«rˇ» five six
3044 seven «ˇeight nine
3045 »ten
3046 "});
3047 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3048 cx.assert_editor_state(indoc! {"
3049 oˇe two three
3050 fouˇ five six
3051 seven ˇten
3052 "});
3053
3054 // Test backspace inside and around indents
3055 cx.set_state(indoc! {"
3056 zero
3057 ˇone
3058 ˇtwo
3059 ˇ ˇ ˇ three
3060 ˇ ˇ four
3061 "});
3062 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3063 cx.assert_editor_state(indoc! {"
3064 zero
3065 ˇone
3066 ˇtwo
3067 ˇ threeˇ four
3068 "});
3069
3070 // Test backspace with line_mode set to true
3071 cx.update_editor(|e, _| e.selections.line_mode = true);
3072 cx.set_state(indoc! {"
3073 The ˇquick ˇbrown
3074 fox jumps over
3075 the lazy dog
3076 ˇThe qu«ick bˇ»rown"});
3077 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3078 cx.assert_editor_state(indoc! {"
3079 ˇfox jumps over
3080 the lazy dogˇ"});
3081}
3082
3083#[gpui::test]
3084async fn test_delete(cx: &mut gpui::TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let mut cx = EditorTestContext::new(cx).await;
3088 cx.set_state(indoc! {"
3089 onˇe two three
3090 fou«rˇ» five six
3091 seven «ˇeight nine
3092 »ten
3093 "});
3094 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3095 cx.assert_editor_state(indoc! {"
3096 onˇ two three
3097 fouˇ five six
3098 seven ˇten
3099 "});
3100
3101 // Test backspace with line_mode set to true
3102 cx.update_editor(|e, _| e.selections.line_mode = true);
3103 cx.set_state(indoc! {"
3104 The ˇquick ˇbrown
3105 fox «ˇjum»ps over
3106 the lazy dog
3107 ˇThe qu«ick bˇ»rown"});
3108 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3109 cx.assert_editor_state("ˇthe lazy dogˇ");
3110}
3111
3112#[gpui::test]
3113fn test_delete_line(cx: &mut TestAppContext) {
3114 init_test(cx, |_| {});
3115
3116 let view = cx.add_window(|cx| {
3117 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3118 build_editor(buffer, cx)
3119 });
3120 _ = view.update(cx, |view, cx| {
3121 view.change_selections(None, cx, |s| {
3122 s.select_display_ranges([
3123 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3125 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3126 ])
3127 });
3128 view.delete_line(&DeleteLine, cx);
3129 assert_eq!(view.display_text(cx), "ghi");
3130 assert_eq!(
3131 view.selections.display_ranges(cx),
3132 vec![
3133 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3134 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3135 ]
3136 );
3137 });
3138
3139 let view = cx.add_window(|cx| {
3140 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3141 build_editor(buffer, cx)
3142 });
3143 _ = view.update(cx, |view, cx| {
3144 view.change_selections(None, cx, |s| {
3145 s.select_display_ranges([
3146 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3147 ])
3148 });
3149 view.delete_line(&DeleteLine, cx);
3150 assert_eq!(view.display_text(cx), "ghi\n");
3151 assert_eq!(
3152 view.selections.display_ranges(cx),
3153 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3154 );
3155 });
3156}
3157
3158#[gpui::test]
3159fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3160 init_test(cx, |_| {});
3161
3162 cx.add_window(|cx| {
3163 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3164 let mut editor = build_editor(buffer.clone(), cx);
3165 let buffer = buffer.read(cx).as_singleton().unwrap();
3166
3167 assert_eq!(
3168 editor.selections.ranges::<Point>(cx),
3169 &[Point::new(0, 0)..Point::new(0, 0)]
3170 );
3171
3172 // When on single line, replace newline at end by space
3173 editor.join_lines(&JoinLines, cx);
3174 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3175 assert_eq!(
3176 editor.selections.ranges::<Point>(cx),
3177 &[Point::new(0, 3)..Point::new(0, 3)]
3178 );
3179
3180 // When multiple lines are selected, remove newlines that are spanned by the selection
3181 editor.change_selections(None, cx, |s| {
3182 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3183 });
3184 editor.join_lines(&JoinLines, cx);
3185 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3186 assert_eq!(
3187 editor.selections.ranges::<Point>(cx),
3188 &[Point::new(0, 11)..Point::new(0, 11)]
3189 );
3190
3191 // Undo should be transactional
3192 editor.undo(&Undo, cx);
3193 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3194 assert_eq!(
3195 editor.selections.ranges::<Point>(cx),
3196 &[Point::new(0, 5)..Point::new(2, 2)]
3197 );
3198
3199 // When joining an empty line don't insert a space
3200 editor.change_selections(None, cx, |s| {
3201 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3202 });
3203 editor.join_lines(&JoinLines, cx);
3204 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3205 assert_eq!(
3206 editor.selections.ranges::<Point>(cx),
3207 [Point::new(2, 3)..Point::new(2, 3)]
3208 );
3209
3210 // We can remove trailing newlines
3211 editor.join_lines(&JoinLines, cx);
3212 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3213 assert_eq!(
3214 editor.selections.ranges::<Point>(cx),
3215 [Point::new(2, 3)..Point::new(2, 3)]
3216 );
3217
3218 // We don't blow up on the last line
3219 editor.join_lines(&JoinLines, cx);
3220 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3221 assert_eq!(
3222 editor.selections.ranges::<Point>(cx),
3223 [Point::new(2, 3)..Point::new(2, 3)]
3224 );
3225
3226 // reset to test indentation
3227 editor.buffer.update(cx, |buffer, cx| {
3228 buffer.edit(
3229 [
3230 (Point::new(1, 0)..Point::new(1, 2), " "),
3231 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3232 ],
3233 None,
3234 cx,
3235 )
3236 });
3237
3238 // We remove any leading spaces
3239 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3240 editor.change_selections(None, cx, |s| {
3241 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3242 });
3243 editor.join_lines(&JoinLines, cx);
3244 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3245
3246 // We don't insert a space for a line containing only spaces
3247 editor.join_lines(&JoinLines, cx);
3248 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3249
3250 // We ignore any leading tabs
3251 editor.join_lines(&JoinLines, cx);
3252 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3253
3254 editor
3255 });
3256}
3257
3258#[gpui::test]
3259fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3260 init_test(cx, |_| {});
3261
3262 cx.add_window(|cx| {
3263 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3264 let mut editor = build_editor(buffer.clone(), cx);
3265 let buffer = buffer.read(cx).as_singleton().unwrap();
3266
3267 editor.change_selections(None, cx, |s| {
3268 s.select_ranges([
3269 Point::new(0, 2)..Point::new(1, 1),
3270 Point::new(1, 2)..Point::new(1, 2),
3271 Point::new(3, 1)..Point::new(3, 2),
3272 ])
3273 });
3274
3275 editor.join_lines(&JoinLines, cx);
3276 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3277
3278 assert_eq!(
3279 editor.selections.ranges::<Point>(cx),
3280 [
3281 Point::new(0, 7)..Point::new(0, 7),
3282 Point::new(1, 3)..Point::new(1, 3)
3283 ]
3284 );
3285 editor
3286 });
3287}
3288
3289#[gpui::test]
3290async fn test_join_lines_with_git_diff_base(
3291 executor: BackgroundExecutor,
3292 cx: &mut gpui::TestAppContext,
3293) {
3294 init_test(cx, |_| {});
3295
3296 let mut cx = EditorTestContext::new(cx).await;
3297
3298 let diff_base = r#"
3299 Line 0
3300 Line 1
3301 Line 2
3302 Line 3
3303 "#
3304 .unindent();
3305
3306 cx.set_state(
3307 &r#"
3308 ˇLine 0
3309 Line 1
3310 Line 2
3311 Line 3
3312 "#
3313 .unindent(),
3314 );
3315
3316 cx.set_diff_base(Some(&diff_base));
3317 executor.run_until_parked();
3318
3319 // Join lines
3320 cx.update_editor(|editor, cx| {
3321 editor.join_lines(&JoinLines, cx);
3322 });
3323 executor.run_until_parked();
3324
3325 cx.assert_editor_state(
3326 &r#"
3327 Line 0ˇ Line 1
3328 Line 2
3329 Line 3
3330 "#
3331 .unindent(),
3332 );
3333 // Join again
3334 cx.update_editor(|editor, cx| {
3335 editor.join_lines(&JoinLines, cx);
3336 });
3337 executor.run_until_parked();
3338
3339 cx.assert_editor_state(
3340 &r#"
3341 Line 0 Line 1ˇ Line 2
3342 Line 3
3343 "#
3344 .unindent(),
3345 );
3346}
3347
3348#[gpui::test]
3349async fn test_custom_newlines_cause_no_false_positive_diffs(
3350 executor: BackgroundExecutor,
3351 cx: &mut gpui::TestAppContext,
3352) {
3353 init_test(cx, |_| {});
3354 let mut cx = EditorTestContext::new(cx).await;
3355 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3356 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3357 executor.run_until_parked();
3358
3359 cx.update_editor(|editor, cx| {
3360 assert_eq!(
3361 editor
3362 .buffer()
3363 .read(cx)
3364 .snapshot(cx)
3365 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3366 .collect::<Vec<_>>(),
3367 Vec::new(),
3368 "Should not have any diffs for files with custom newlines"
3369 );
3370 });
3371}
3372
3373#[gpui::test]
3374async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3375 init_test(cx, |_| {});
3376
3377 let mut cx = EditorTestContext::new(cx).await;
3378
3379 // Test sort_lines_case_insensitive()
3380 cx.set_state(indoc! {"
3381 «z
3382 y
3383 x
3384 Z
3385 Y
3386 Xˇ»
3387 "});
3388 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3389 cx.assert_editor_state(indoc! {"
3390 «x
3391 X
3392 y
3393 Y
3394 z
3395 Zˇ»
3396 "});
3397
3398 // Test reverse_lines()
3399 cx.set_state(indoc! {"
3400 «5
3401 4
3402 3
3403 2
3404 1ˇ»
3405 "});
3406 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3407 cx.assert_editor_state(indoc! {"
3408 «1
3409 2
3410 3
3411 4
3412 5ˇ»
3413 "});
3414
3415 // Skip testing shuffle_line()
3416
3417 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3418 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3419
3420 // Don't manipulate when cursor is on single line, but expand the selection
3421 cx.set_state(indoc! {"
3422 ddˇdd
3423 ccc
3424 bb
3425 a
3426 "});
3427 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3428 cx.assert_editor_state(indoc! {"
3429 «ddddˇ»
3430 ccc
3431 bb
3432 a
3433 "});
3434
3435 // Basic manipulate case
3436 // Start selection moves to column 0
3437 // End of selection shrinks to fit shorter line
3438 cx.set_state(indoc! {"
3439 dd«d
3440 ccc
3441 bb
3442 aaaaaˇ»
3443 "});
3444 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3445 cx.assert_editor_state(indoc! {"
3446 «aaaaa
3447 bb
3448 ccc
3449 dddˇ»
3450 "});
3451
3452 // Manipulate case with newlines
3453 cx.set_state(indoc! {"
3454 dd«d
3455 ccc
3456
3457 bb
3458 aaaaa
3459
3460 ˇ»
3461 "});
3462 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3463 cx.assert_editor_state(indoc! {"
3464 «
3465
3466 aaaaa
3467 bb
3468 ccc
3469 dddˇ»
3470
3471 "});
3472
3473 // Adding new line
3474 cx.set_state(indoc! {"
3475 aa«a
3476 bbˇ»b
3477 "});
3478 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3479 cx.assert_editor_state(indoc! {"
3480 «aaa
3481 bbb
3482 added_lineˇ»
3483 "});
3484
3485 // Removing line
3486 cx.set_state(indoc! {"
3487 aa«a
3488 bbbˇ»
3489 "});
3490 cx.update_editor(|e, cx| {
3491 e.manipulate_lines(cx, |lines| {
3492 lines.pop();
3493 })
3494 });
3495 cx.assert_editor_state(indoc! {"
3496 «aaaˇ»
3497 "});
3498
3499 // Removing all lines
3500 cx.set_state(indoc! {"
3501 aa«a
3502 bbbˇ»
3503 "});
3504 cx.update_editor(|e, cx| {
3505 e.manipulate_lines(cx, |lines| {
3506 lines.drain(..);
3507 })
3508 });
3509 cx.assert_editor_state(indoc! {"
3510 ˇ
3511 "});
3512}
3513
3514#[gpui::test]
3515async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3516 init_test(cx, |_| {});
3517
3518 let mut cx = EditorTestContext::new(cx).await;
3519
3520 // Consider continuous selection as single selection
3521 cx.set_state(indoc! {"
3522 Aaa«aa
3523 cˇ»c«c
3524 bb
3525 aaaˇ»aa
3526 "});
3527 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3528 cx.assert_editor_state(indoc! {"
3529 «Aaaaa
3530 ccc
3531 bb
3532 aaaaaˇ»
3533 "});
3534
3535 cx.set_state(indoc! {"
3536 Aaa«aa
3537 cˇ»c«c
3538 bb
3539 aaaˇ»aa
3540 "});
3541 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3542 cx.assert_editor_state(indoc! {"
3543 «Aaaaa
3544 ccc
3545 bbˇ»
3546 "});
3547
3548 // Consider non continuous selection as distinct dedup operations
3549 cx.set_state(indoc! {"
3550 «aaaaa
3551 bb
3552 aaaaa
3553 aaaaaˇ»
3554
3555 aaa«aaˇ»
3556 "});
3557 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3558 cx.assert_editor_state(indoc! {"
3559 «aaaaa
3560 bbˇ»
3561
3562 «aaaaaˇ»
3563 "});
3564}
3565
3566#[gpui::test]
3567async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let mut cx = EditorTestContext::new(cx).await;
3571
3572 cx.set_state(indoc! {"
3573 «Aaa
3574 aAa
3575 Aaaˇ»
3576 "});
3577 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3578 cx.assert_editor_state(indoc! {"
3579 «Aaa
3580 aAaˇ»
3581 "});
3582
3583 cx.set_state(indoc! {"
3584 «Aaa
3585 aAa
3586 aaAˇ»
3587 "});
3588 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3589 cx.assert_editor_state(indoc! {"
3590 «Aaaˇ»
3591 "});
3592}
3593
3594#[gpui::test]
3595async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3596 init_test(cx, |_| {});
3597
3598 let mut cx = EditorTestContext::new(cx).await;
3599
3600 // Manipulate with multiple selections on a single line
3601 cx.set_state(indoc! {"
3602 dd«dd
3603 cˇ»c«c
3604 bb
3605 aaaˇ»aa
3606 "});
3607 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3608 cx.assert_editor_state(indoc! {"
3609 «aaaaa
3610 bb
3611 ccc
3612 ddddˇ»
3613 "});
3614
3615 // Manipulate with multiple disjoin selections
3616 cx.set_state(indoc! {"
3617 5«
3618 4
3619 3
3620 2
3621 1ˇ»
3622
3623 dd«dd
3624 ccc
3625 bb
3626 aaaˇ»aa
3627 "});
3628 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3629 cx.assert_editor_state(indoc! {"
3630 «1
3631 2
3632 3
3633 4
3634 5ˇ»
3635
3636 «aaaaa
3637 bb
3638 ccc
3639 ddddˇ»
3640 "});
3641
3642 // Adding lines on each selection
3643 cx.set_state(indoc! {"
3644 2«
3645 1ˇ»
3646
3647 bb«bb
3648 aaaˇ»aa
3649 "});
3650 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3651 cx.assert_editor_state(indoc! {"
3652 «2
3653 1
3654 added lineˇ»
3655
3656 «bbbb
3657 aaaaa
3658 added lineˇ»
3659 "});
3660
3661 // Removing lines on each selection
3662 cx.set_state(indoc! {"
3663 2«
3664 1ˇ»
3665
3666 bb«bb
3667 aaaˇ»aa
3668 "});
3669 cx.update_editor(|e, cx| {
3670 e.manipulate_lines(cx, |lines| {
3671 lines.pop();
3672 })
3673 });
3674 cx.assert_editor_state(indoc! {"
3675 «2ˇ»
3676
3677 «bbbbˇ»
3678 "});
3679}
3680
3681#[gpui::test]
3682async fn test_manipulate_text(cx: &mut TestAppContext) {
3683 init_test(cx, |_| {});
3684
3685 let mut cx = EditorTestContext::new(cx).await;
3686
3687 // Test convert_to_upper_case()
3688 cx.set_state(indoc! {"
3689 «hello worldˇ»
3690 "});
3691 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3692 cx.assert_editor_state(indoc! {"
3693 «HELLO WORLDˇ»
3694 "});
3695
3696 // Test convert_to_lower_case()
3697 cx.set_state(indoc! {"
3698 «HELLO WORLDˇ»
3699 "});
3700 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3701 cx.assert_editor_state(indoc! {"
3702 «hello worldˇ»
3703 "});
3704
3705 // Test multiple line, single selection case
3706 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3707 cx.set_state(indoc! {"
3708 «The quick brown
3709 fox jumps over
3710 the lazy dogˇ»
3711 "});
3712 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3713 cx.assert_editor_state(indoc! {"
3714 «The Quick Brown
3715 Fox Jumps Over
3716 The Lazy Dogˇ»
3717 "});
3718
3719 // Test multiple line, single selection case
3720 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3721 cx.set_state(indoc! {"
3722 «The quick brown
3723 fox jumps over
3724 the lazy dogˇ»
3725 "});
3726 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3727 cx.assert_editor_state(indoc! {"
3728 «TheQuickBrown
3729 FoxJumpsOver
3730 TheLazyDogˇ»
3731 "});
3732
3733 // From here on out, test more complex cases of manipulate_text()
3734
3735 // Test no selection case - should affect words cursors are in
3736 // Cursor at beginning, middle, and end of word
3737 cx.set_state(indoc! {"
3738 ˇhello big beauˇtiful worldˇ
3739 "});
3740 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3741 cx.assert_editor_state(indoc! {"
3742 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3743 "});
3744
3745 // Test multiple selections on a single line and across multiple lines
3746 cx.set_state(indoc! {"
3747 «Theˇ» quick «brown
3748 foxˇ» jumps «overˇ»
3749 the «lazyˇ» dog
3750 "});
3751 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3752 cx.assert_editor_state(indoc! {"
3753 «THEˇ» quick «BROWN
3754 FOXˇ» jumps «OVERˇ»
3755 the «LAZYˇ» dog
3756 "});
3757
3758 // Test case where text length grows
3759 cx.set_state(indoc! {"
3760 «tschüߡ»
3761 "});
3762 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3763 cx.assert_editor_state(indoc! {"
3764 «TSCHÜSSˇ»
3765 "});
3766
3767 // Test to make sure we don't crash when text shrinks
3768 cx.set_state(indoc! {"
3769 aaa_bbbˇ
3770 "});
3771 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3772 cx.assert_editor_state(indoc! {"
3773 «aaaBbbˇ»
3774 "});
3775
3776 // Test to make sure we all aware of the fact that each word can grow and shrink
3777 // Final selections should be aware of this fact
3778 cx.set_state(indoc! {"
3779 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3780 "});
3781 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3782 cx.assert_editor_state(indoc! {"
3783 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3784 "});
3785
3786 cx.set_state(indoc! {"
3787 «hElLo, WoRld!ˇ»
3788 "});
3789 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3790 cx.assert_editor_state(indoc! {"
3791 «HeLlO, wOrLD!ˇ»
3792 "});
3793}
3794
3795#[gpui::test]
3796fn test_duplicate_line(cx: &mut TestAppContext) {
3797 init_test(cx, |_| {});
3798
3799 let view = cx.add_window(|cx| {
3800 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3801 build_editor(buffer, cx)
3802 });
3803 _ = view.update(cx, |view, cx| {
3804 view.change_selections(None, cx, |s| {
3805 s.select_display_ranges([
3806 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3807 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3808 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3809 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3810 ])
3811 });
3812 view.duplicate_line_down(&DuplicateLineDown, cx);
3813 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3814 assert_eq!(
3815 view.selections.display_ranges(cx),
3816 vec![
3817 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3818 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3819 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3820 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3821 ]
3822 );
3823 });
3824
3825 let view = cx.add_window(|cx| {
3826 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3827 build_editor(buffer, cx)
3828 });
3829 _ = view.update(cx, |view, cx| {
3830 view.change_selections(None, cx, |s| {
3831 s.select_display_ranges([
3832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3834 ])
3835 });
3836 view.duplicate_line_down(&DuplicateLineDown, cx);
3837 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3838 assert_eq!(
3839 view.selections.display_ranges(cx),
3840 vec![
3841 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3842 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3843 ]
3844 );
3845 });
3846
3847 // With `move_upwards` the selections stay in place, except for
3848 // the lines inserted above them
3849 let view = cx.add_window(|cx| {
3850 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3851 build_editor(buffer, cx)
3852 });
3853 _ = view.update(cx, |view, cx| {
3854 view.change_selections(None, cx, |s| {
3855 s.select_display_ranges([
3856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3857 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3859 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3860 ])
3861 });
3862 view.duplicate_line_up(&DuplicateLineUp, cx);
3863 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3864 assert_eq!(
3865 view.selections.display_ranges(cx),
3866 vec![
3867 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3868 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3869 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3870 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3871 ]
3872 );
3873 });
3874
3875 let view = cx.add_window(|cx| {
3876 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3877 build_editor(buffer, cx)
3878 });
3879 _ = view.update(cx, |view, cx| {
3880 view.change_selections(None, cx, |s| {
3881 s.select_display_ranges([
3882 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3883 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3884 ])
3885 });
3886 view.duplicate_line_up(&DuplicateLineUp, cx);
3887 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3888 assert_eq!(
3889 view.selections.display_ranges(cx),
3890 vec![
3891 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3892 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3893 ]
3894 );
3895 });
3896}
3897
3898#[gpui::test]
3899fn test_move_line_up_down(cx: &mut TestAppContext) {
3900 init_test(cx, |_| {});
3901
3902 let view = cx.add_window(|cx| {
3903 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3904 build_editor(buffer, cx)
3905 });
3906 _ = view.update(cx, |view, cx| {
3907 view.fold_creases(
3908 vec![
3909 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3910 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3911 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3912 ],
3913 true,
3914 cx,
3915 );
3916 view.change_selections(None, cx, |s| {
3917 s.select_display_ranges([
3918 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3919 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3920 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3921 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3922 ])
3923 });
3924 assert_eq!(
3925 view.display_text(cx),
3926 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3927 );
3928
3929 view.move_line_up(&MoveLineUp, cx);
3930 assert_eq!(
3931 view.display_text(cx),
3932 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3933 );
3934 assert_eq!(
3935 view.selections.display_ranges(cx),
3936 vec![
3937 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3938 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3939 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3940 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3941 ]
3942 );
3943 });
3944
3945 _ = view.update(cx, |view, cx| {
3946 view.move_line_down(&MoveLineDown, cx);
3947 assert_eq!(
3948 view.display_text(cx),
3949 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3950 );
3951 assert_eq!(
3952 view.selections.display_ranges(cx),
3953 vec![
3954 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3955 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3956 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3957 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3958 ]
3959 );
3960 });
3961
3962 _ = view.update(cx, |view, cx| {
3963 view.move_line_down(&MoveLineDown, cx);
3964 assert_eq!(
3965 view.display_text(cx),
3966 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3967 );
3968 assert_eq!(
3969 view.selections.display_ranges(cx),
3970 vec![
3971 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3972 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3973 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3974 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3975 ]
3976 );
3977 });
3978
3979 _ = view.update(cx, |view, cx| {
3980 view.move_line_up(&MoveLineUp, cx);
3981 assert_eq!(
3982 view.display_text(cx),
3983 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3984 );
3985 assert_eq!(
3986 view.selections.display_ranges(cx),
3987 vec![
3988 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3989 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3990 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3991 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3992 ]
3993 );
3994 });
3995}
3996
3997#[gpui::test]
3998fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3999 init_test(cx, |_| {});
4000
4001 let editor = cx.add_window(|cx| {
4002 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4003 build_editor(buffer, cx)
4004 });
4005 _ = editor.update(cx, |editor, cx| {
4006 let snapshot = editor.buffer.read(cx).snapshot(cx);
4007 editor.insert_blocks(
4008 [BlockProperties {
4009 style: BlockStyle::Fixed,
4010 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4011 height: 1,
4012 render: Arc::new(|_| div().into_any()),
4013 priority: 0,
4014 }],
4015 Some(Autoscroll::fit()),
4016 cx,
4017 );
4018 editor.change_selections(None, cx, |s| {
4019 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4020 });
4021 editor.move_line_down(&MoveLineDown, cx);
4022 });
4023}
4024
4025#[gpui::test]
4026async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4027 init_test(cx, |_| {});
4028
4029 let mut cx = EditorTestContext::new(cx).await;
4030 cx.set_state(
4031 &"
4032 ˇzero
4033 one
4034 two
4035 three
4036 four
4037 five
4038 "
4039 .unindent(),
4040 );
4041
4042 // Create a four-line block that replaces three lines of text.
4043 cx.update_editor(|editor, cx| {
4044 let snapshot = editor.snapshot(cx);
4045 let snapshot = &snapshot.buffer_snapshot;
4046 let placement = BlockPlacement::Replace(
4047 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4048 );
4049 editor.insert_blocks(
4050 [BlockProperties {
4051 placement,
4052 height: 4,
4053 style: BlockStyle::Sticky,
4054 render: Arc::new(|_| gpui::div().into_any_element()),
4055 priority: 0,
4056 }],
4057 None,
4058 cx,
4059 );
4060 });
4061
4062 // Move down so that the cursor touches the block.
4063 cx.update_editor(|editor, cx| {
4064 editor.move_down(&Default::default(), cx);
4065 });
4066 cx.assert_editor_state(
4067 &"
4068 zero
4069 «one
4070 two
4071 threeˇ»
4072 four
4073 five
4074 "
4075 .unindent(),
4076 );
4077
4078 // Move down past the block.
4079 cx.update_editor(|editor, cx| {
4080 editor.move_down(&Default::default(), cx);
4081 });
4082 cx.assert_editor_state(
4083 &"
4084 zero
4085 one
4086 two
4087 three
4088 ˇfour
4089 five
4090 "
4091 .unindent(),
4092 );
4093}
4094
4095#[gpui::test]
4096fn test_transpose(cx: &mut TestAppContext) {
4097 init_test(cx, |_| {});
4098
4099 _ = cx.add_window(|cx| {
4100 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4101 editor.set_style(EditorStyle::default(), cx);
4102 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4103 editor.transpose(&Default::default(), cx);
4104 assert_eq!(editor.text(cx), "bac");
4105 assert_eq!(editor.selections.ranges(cx), [2..2]);
4106
4107 editor.transpose(&Default::default(), cx);
4108 assert_eq!(editor.text(cx), "bca");
4109 assert_eq!(editor.selections.ranges(cx), [3..3]);
4110
4111 editor.transpose(&Default::default(), cx);
4112 assert_eq!(editor.text(cx), "bac");
4113 assert_eq!(editor.selections.ranges(cx), [3..3]);
4114
4115 editor
4116 });
4117
4118 _ = cx.add_window(|cx| {
4119 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4120 editor.set_style(EditorStyle::default(), cx);
4121 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4122 editor.transpose(&Default::default(), cx);
4123 assert_eq!(editor.text(cx), "acb\nde");
4124 assert_eq!(editor.selections.ranges(cx), [3..3]);
4125
4126 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4127 editor.transpose(&Default::default(), cx);
4128 assert_eq!(editor.text(cx), "acbd\ne");
4129 assert_eq!(editor.selections.ranges(cx), [5..5]);
4130
4131 editor.transpose(&Default::default(), cx);
4132 assert_eq!(editor.text(cx), "acbde\n");
4133 assert_eq!(editor.selections.ranges(cx), [6..6]);
4134
4135 editor.transpose(&Default::default(), cx);
4136 assert_eq!(editor.text(cx), "acbd\ne");
4137 assert_eq!(editor.selections.ranges(cx), [6..6]);
4138
4139 editor
4140 });
4141
4142 _ = cx.add_window(|cx| {
4143 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4144 editor.set_style(EditorStyle::default(), cx);
4145 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4146 editor.transpose(&Default::default(), cx);
4147 assert_eq!(editor.text(cx), "bacd\ne");
4148 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4149
4150 editor.transpose(&Default::default(), cx);
4151 assert_eq!(editor.text(cx), "bcade\n");
4152 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4153
4154 editor.transpose(&Default::default(), cx);
4155 assert_eq!(editor.text(cx), "bcda\ne");
4156 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4157
4158 editor.transpose(&Default::default(), cx);
4159 assert_eq!(editor.text(cx), "bcade\n");
4160 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4161
4162 editor.transpose(&Default::default(), cx);
4163 assert_eq!(editor.text(cx), "bcaed\n");
4164 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4165
4166 editor
4167 });
4168
4169 _ = cx.add_window(|cx| {
4170 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4171 editor.set_style(EditorStyle::default(), cx);
4172 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4173 editor.transpose(&Default::default(), cx);
4174 assert_eq!(editor.text(cx), "🏀🍐✋");
4175 assert_eq!(editor.selections.ranges(cx), [8..8]);
4176
4177 editor.transpose(&Default::default(), cx);
4178 assert_eq!(editor.text(cx), "🏀✋🍐");
4179 assert_eq!(editor.selections.ranges(cx), [11..11]);
4180
4181 editor.transpose(&Default::default(), cx);
4182 assert_eq!(editor.text(cx), "🏀🍐✋");
4183 assert_eq!(editor.selections.ranges(cx), [11..11]);
4184
4185 editor
4186 });
4187}
4188
4189#[gpui::test]
4190async fn test_rewrap(cx: &mut TestAppContext) {
4191 init_test(cx, |_| {});
4192
4193 let mut cx = EditorTestContext::new(cx).await;
4194
4195 let language_with_c_comments = Arc::new(Language::new(
4196 LanguageConfig {
4197 line_comments: vec!["// ".into()],
4198 ..LanguageConfig::default()
4199 },
4200 None,
4201 ));
4202 let language_with_pound_comments = Arc::new(Language::new(
4203 LanguageConfig {
4204 line_comments: vec!["# ".into()],
4205 ..LanguageConfig::default()
4206 },
4207 None,
4208 ));
4209 let markdown_language = Arc::new(Language::new(
4210 LanguageConfig {
4211 name: "Markdown".into(),
4212 ..LanguageConfig::default()
4213 },
4214 None,
4215 ));
4216 let language_with_doc_comments = Arc::new(Language::new(
4217 LanguageConfig {
4218 line_comments: vec!["// ".into(), "/// ".into()],
4219 ..LanguageConfig::default()
4220 },
4221 Some(tree_sitter_rust::LANGUAGE.into()),
4222 ));
4223
4224 let plaintext_language = Arc::new(Language::new(
4225 LanguageConfig {
4226 name: "Plain Text".into(),
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231
4232 assert_rewrap(
4233 indoc! {"
4234 // ˇ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.
4235 "},
4236 indoc! {"
4237 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4238 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4239 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4240 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4241 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4242 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4243 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4244 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4245 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4246 // porttitor id. Aliquam id accumsan eros.
4247 "},
4248 language_with_c_comments.clone(),
4249 &mut cx,
4250 );
4251
4252 // Test that rewrapping works inside of a selection
4253 assert_rewrap(
4254 indoc! {"
4255 «// 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.ˇ»
4256 "},
4257 indoc! {"
4258 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4259 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4260 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4261 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4262 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4263 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4264 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4265 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4266 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4267 // porttitor id. Aliquam id accumsan eros.ˇ»
4268 "},
4269 language_with_c_comments.clone(),
4270 &mut cx,
4271 );
4272
4273 // Test that cursors that expand to the same region are collapsed.
4274 assert_rewrap(
4275 indoc! {"
4276 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4277 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4278 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4279 // ˇ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.
4280 "},
4281 indoc! {"
4282 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4283 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4284 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4285 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4286 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4287 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4288 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4289 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4290 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4291 // porttitor id. Aliquam id accumsan eros.
4292 "},
4293 language_with_c_comments.clone(),
4294 &mut cx,
4295 );
4296
4297 // Test that non-contiguous selections are treated separately.
4298 assert_rewrap(
4299 indoc! {"
4300 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4301 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4302 //
4303 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4304 // ˇ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.
4305 "},
4306 indoc! {"
4307 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4308 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4309 // auctor, eu lacinia sapien scelerisque.
4310 //
4311 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4312 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4313 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4314 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4315 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4316 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4317 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4318 "},
4319 language_with_c_comments.clone(),
4320 &mut cx,
4321 );
4322
4323 // Test that different comment prefixes are supported.
4324 assert_rewrap(
4325 indoc! {"
4326 # ˇ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.
4327 "},
4328 indoc! {"
4329 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4330 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4331 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4332 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4333 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4334 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4335 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4336 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4337 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4338 # accumsan eros.
4339 "},
4340 language_with_pound_comments.clone(),
4341 &mut cx,
4342 );
4343
4344 // Test that rewrapping is ignored outside of comments in most languages.
4345 assert_rewrap(
4346 indoc! {"
4347 /// Adds two numbers.
4348 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4349 fn add(a: u32, b: u32) -> u32 {
4350 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ˇ
4351 }
4352 "},
4353 indoc! {"
4354 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4355 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4356 fn add(a: u32, b: u32) -> u32 {
4357 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ˇ
4358 }
4359 "},
4360 language_with_doc_comments.clone(),
4361 &mut cx,
4362 );
4363
4364 // Test that rewrapping works in Markdown and Plain Text languages.
4365 assert_rewrap(
4366 indoc! {"
4367 # Hello
4368
4369 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.
4370 "},
4371 indoc! {"
4372 # Hello
4373
4374 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4375 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4376 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4377 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4378 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4379 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4380 Integer sit amet scelerisque nisi.
4381 "},
4382 markdown_language,
4383 &mut cx,
4384 );
4385
4386 assert_rewrap(
4387 indoc! {"
4388 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.
4389 "},
4390 indoc! {"
4391 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4392 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4393 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4394 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4395 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4396 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4397 Integer sit amet scelerisque nisi.
4398 "},
4399 plaintext_language,
4400 &mut cx,
4401 );
4402
4403 // Test rewrapping unaligned comments in a selection.
4404 assert_rewrap(
4405 indoc! {"
4406 fn foo() {
4407 if true {
4408 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4409 // Praesent semper egestas tellus id dignissim.ˇ»
4410 do_something();
4411 } else {
4412 //
4413 }
4414 }
4415 "},
4416 indoc! {"
4417 fn foo() {
4418 if true {
4419 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4420 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4421 // egestas tellus id dignissim.ˇ»
4422 do_something();
4423 } else {
4424 //
4425 }
4426 }
4427 "},
4428 language_with_doc_comments.clone(),
4429 &mut cx,
4430 );
4431
4432 assert_rewrap(
4433 indoc! {"
4434 fn foo() {
4435 if true {
4436 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4437 // Praesent semper egestas tellus id dignissim.»
4438 do_something();
4439 } else {
4440 //
4441 }
4442
4443 }
4444 "},
4445 indoc! {"
4446 fn foo() {
4447 if true {
4448 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4449 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4450 // egestas tellus id dignissim.»
4451 do_something();
4452 } else {
4453 //
4454 }
4455
4456 }
4457 "},
4458 language_with_doc_comments.clone(),
4459 &mut cx,
4460 );
4461
4462 #[track_caller]
4463 fn assert_rewrap(
4464 unwrapped_text: &str,
4465 wrapped_text: &str,
4466 language: Arc<Language>,
4467 cx: &mut EditorTestContext,
4468 ) {
4469 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4470 cx.set_state(unwrapped_text);
4471 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4472 cx.assert_editor_state(wrapped_text);
4473 }
4474}
4475
4476#[gpui::test]
4477async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4478 init_test(cx, |_| {});
4479
4480 let mut cx = EditorTestContext::new(cx).await;
4481
4482 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4483 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4484 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4485
4486 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4487 cx.set_state("two ˇfour ˇsix ˇ");
4488 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4489 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4490
4491 // Paste again but with only two cursors. Since the number of cursors doesn't
4492 // match the number of slices in the clipboard, the entire clipboard text
4493 // is pasted at each cursor.
4494 cx.set_state("ˇtwo one✅ four three six five ˇ");
4495 cx.update_editor(|e, cx| {
4496 e.handle_input("( ", cx);
4497 e.paste(&Paste, cx);
4498 e.handle_input(") ", cx);
4499 });
4500 cx.assert_editor_state(
4501 &([
4502 "( one✅ ",
4503 "three ",
4504 "five ) ˇtwo one✅ four three six five ( one✅ ",
4505 "three ",
4506 "five ) ˇ",
4507 ]
4508 .join("\n")),
4509 );
4510
4511 // Cut with three selections, one of which is full-line.
4512 cx.set_state(indoc! {"
4513 1«2ˇ»3
4514 4ˇ567
4515 «8ˇ»9"});
4516 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4517 cx.assert_editor_state(indoc! {"
4518 1ˇ3
4519 ˇ9"});
4520
4521 // Paste with three selections, noticing how the copied selection that was full-line
4522 // gets inserted before the second cursor.
4523 cx.set_state(indoc! {"
4524 1ˇ3
4525 9ˇ
4526 «oˇ»ne"});
4527 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4528 cx.assert_editor_state(indoc! {"
4529 12ˇ3
4530 4567
4531 9ˇ
4532 8ˇne"});
4533
4534 // Copy with a single cursor only, which writes the whole line into the clipboard.
4535 cx.set_state(indoc! {"
4536 The quick brown
4537 fox juˇmps over
4538 the lazy dog"});
4539 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4540 assert_eq!(
4541 cx.read_from_clipboard()
4542 .and_then(|item| item.text().as_deref().map(str::to_string)),
4543 Some("fox jumps over\n".to_string())
4544 );
4545
4546 // Paste with three selections, noticing how the copied full-line selection is inserted
4547 // before the empty selections but replaces the selection that is non-empty.
4548 cx.set_state(indoc! {"
4549 Tˇhe quick brown
4550 «foˇ»x jumps over
4551 tˇhe lazy dog"});
4552 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4553 cx.assert_editor_state(indoc! {"
4554 fox jumps over
4555 Tˇhe quick brown
4556 fox jumps over
4557 ˇx jumps over
4558 fox jumps over
4559 tˇhe lazy dog"});
4560}
4561
4562#[gpui::test]
4563async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4564 init_test(cx, |_| {});
4565
4566 let mut cx = EditorTestContext::new(cx).await;
4567 let language = Arc::new(Language::new(
4568 LanguageConfig::default(),
4569 Some(tree_sitter_rust::LANGUAGE.into()),
4570 ));
4571 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4572
4573 // Cut an indented block, without the leading whitespace.
4574 cx.set_state(indoc! {"
4575 const a: B = (
4576 c(),
4577 «d(
4578 e,
4579 f
4580 )ˇ»
4581 );
4582 "});
4583 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4584 cx.assert_editor_state(indoc! {"
4585 const a: B = (
4586 c(),
4587 ˇ
4588 );
4589 "});
4590
4591 // Paste it at the same position.
4592 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4593 cx.assert_editor_state(indoc! {"
4594 const a: B = (
4595 c(),
4596 d(
4597 e,
4598 f
4599 )ˇ
4600 );
4601 "});
4602
4603 // Paste it at a line with a lower indent level.
4604 cx.set_state(indoc! {"
4605 ˇ
4606 const a: B = (
4607 c(),
4608 );
4609 "});
4610 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4611 cx.assert_editor_state(indoc! {"
4612 d(
4613 e,
4614 f
4615 )ˇ
4616 const a: B = (
4617 c(),
4618 );
4619 "});
4620
4621 // Cut an indented block, with the leading whitespace.
4622 cx.set_state(indoc! {"
4623 const a: B = (
4624 c(),
4625 « d(
4626 e,
4627 f
4628 )
4629 ˇ»);
4630 "});
4631 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4632 cx.assert_editor_state(indoc! {"
4633 const a: B = (
4634 c(),
4635 ˇ);
4636 "});
4637
4638 // Paste it at the same position.
4639 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4640 cx.assert_editor_state(indoc! {"
4641 const a: B = (
4642 c(),
4643 d(
4644 e,
4645 f
4646 )
4647 ˇ);
4648 "});
4649
4650 // Paste it at a line with a higher indent level.
4651 cx.set_state(indoc! {"
4652 const a: B = (
4653 c(),
4654 d(
4655 e,
4656 fˇ
4657 )
4658 );
4659 "});
4660 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4661 cx.assert_editor_state(indoc! {"
4662 const a: B = (
4663 c(),
4664 d(
4665 e,
4666 f d(
4667 e,
4668 f
4669 )
4670 ˇ
4671 )
4672 );
4673 "});
4674}
4675
4676#[gpui::test]
4677fn test_select_all(cx: &mut TestAppContext) {
4678 init_test(cx, |_| {});
4679
4680 let view = cx.add_window(|cx| {
4681 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4682 build_editor(buffer, cx)
4683 });
4684 _ = view.update(cx, |view, cx| {
4685 view.select_all(&SelectAll, cx);
4686 assert_eq!(
4687 view.selections.display_ranges(cx),
4688 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4689 );
4690 });
4691}
4692
4693#[gpui::test]
4694fn test_select_line(cx: &mut TestAppContext) {
4695 init_test(cx, |_| {});
4696
4697 let view = cx.add_window(|cx| {
4698 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4699 build_editor(buffer, cx)
4700 });
4701 _ = view.update(cx, |view, cx| {
4702 view.change_selections(None, cx, |s| {
4703 s.select_display_ranges([
4704 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4705 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4706 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4707 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4708 ])
4709 });
4710 view.select_line(&SelectLine, cx);
4711 assert_eq!(
4712 view.selections.display_ranges(cx),
4713 vec![
4714 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4715 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4716 ]
4717 );
4718 });
4719
4720 _ = view.update(cx, |view, cx| {
4721 view.select_line(&SelectLine, cx);
4722 assert_eq!(
4723 view.selections.display_ranges(cx),
4724 vec![
4725 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4726 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4727 ]
4728 );
4729 });
4730
4731 _ = view.update(cx, |view, cx| {
4732 view.select_line(&SelectLine, cx);
4733 assert_eq!(
4734 view.selections.display_ranges(cx),
4735 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4736 );
4737 });
4738}
4739
4740#[gpui::test]
4741fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4742 init_test(cx, |_| {});
4743
4744 let view = cx.add_window(|cx| {
4745 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4746 build_editor(buffer, cx)
4747 });
4748 _ = view.update(cx, |view, cx| {
4749 view.fold_creases(
4750 vec![
4751 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4752 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4753 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4754 ],
4755 true,
4756 cx,
4757 );
4758 view.change_selections(None, cx, |s| {
4759 s.select_display_ranges([
4760 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4761 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4762 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4763 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4764 ])
4765 });
4766 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4767 });
4768
4769 _ = view.update(cx, |view, cx| {
4770 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4771 assert_eq!(
4772 view.display_text(cx),
4773 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4774 );
4775 assert_eq!(
4776 view.selections.display_ranges(cx),
4777 [
4778 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4779 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4780 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4781 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4782 ]
4783 );
4784 });
4785
4786 _ = view.update(cx, |view, cx| {
4787 view.change_selections(None, cx, |s| {
4788 s.select_display_ranges([
4789 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4790 ])
4791 });
4792 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4793 assert_eq!(
4794 view.display_text(cx),
4795 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4796 );
4797 assert_eq!(
4798 view.selections.display_ranges(cx),
4799 [
4800 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4801 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4802 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4803 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4804 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4805 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4806 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4807 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4808 ]
4809 );
4810 });
4811}
4812
4813#[gpui::test]
4814async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4815 init_test(cx, |_| {});
4816
4817 let mut cx = EditorTestContext::new(cx).await;
4818
4819 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4820 cx.set_state(indoc!(
4821 r#"abc
4822 defˇghi
4823
4824 jk
4825 nlmo
4826 "#
4827 ));
4828
4829 cx.update_editor(|editor, cx| {
4830 editor.add_selection_above(&Default::default(), cx);
4831 });
4832
4833 cx.assert_editor_state(indoc!(
4834 r#"abcˇ
4835 defˇghi
4836
4837 jk
4838 nlmo
4839 "#
4840 ));
4841
4842 cx.update_editor(|editor, cx| {
4843 editor.add_selection_above(&Default::default(), cx);
4844 });
4845
4846 cx.assert_editor_state(indoc!(
4847 r#"abcˇ
4848 defˇghi
4849
4850 jk
4851 nlmo
4852 "#
4853 ));
4854
4855 cx.update_editor(|view, cx| {
4856 view.add_selection_below(&Default::default(), cx);
4857 });
4858
4859 cx.assert_editor_state(indoc!(
4860 r#"abc
4861 defˇghi
4862
4863 jk
4864 nlmo
4865 "#
4866 ));
4867
4868 cx.update_editor(|view, cx| {
4869 view.undo_selection(&Default::default(), cx);
4870 });
4871
4872 cx.assert_editor_state(indoc!(
4873 r#"abcˇ
4874 defˇghi
4875
4876 jk
4877 nlmo
4878 "#
4879 ));
4880
4881 cx.update_editor(|view, cx| {
4882 view.redo_selection(&Default::default(), cx);
4883 });
4884
4885 cx.assert_editor_state(indoc!(
4886 r#"abc
4887 defˇghi
4888
4889 jk
4890 nlmo
4891 "#
4892 ));
4893
4894 cx.update_editor(|view, cx| {
4895 view.add_selection_below(&Default::default(), cx);
4896 });
4897
4898 cx.assert_editor_state(indoc!(
4899 r#"abc
4900 defˇghi
4901
4902 jk
4903 nlmˇo
4904 "#
4905 ));
4906
4907 cx.update_editor(|view, cx| {
4908 view.add_selection_below(&Default::default(), cx);
4909 });
4910
4911 cx.assert_editor_state(indoc!(
4912 r#"abc
4913 defˇghi
4914
4915 jk
4916 nlmˇo
4917 "#
4918 ));
4919
4920 // change selections
4921 cx.set_state(indoc!(
4922 r#"abc
4923 def«ˇg»hi
4924
4925 jk
4926 nlmo
4927 "#
4928 ));
4929
4930 cx.update_editor(|view, cx| {
4931 view.add_selection_below(&Default::default(), cx);
4932 });
4933
4934 cx.assert_editor_state(indoc!(
4935 r#"abc
4936 def«ˇg»hi
4937
4938 jk
4939 nlm«ˇo»
4940 "#
4941 ));
4942
4943 cx.update_editor(|view, cx| {
4944 view.add_selection_below(&Default::default(), cx);
4945 });
4946
4947 cx.assert_editor_state(indoc!(
4948 r#"abc
4949 def«ˇg»hi
4950
4951 jk
4952 nlm«ˇo»
4953 "#
4954 ));
4955
4956 cx.update_editor(|view, cx| {
4957 view.add_selection_above(&Default::default(), cx);
4958 });
4959
4960 cx.assert_editor_state(indoc!(
4961 r#"abc
4962 def«ˇg»hi
4963
4964 jk
4965 nlmo
4966 "#
4967 ));
4968
4969 cx.update_editor(|view, cx| {
4970 view.add_selection_above(&Default::default(), cx);
4971 });
4972
4973 cx.assert_editor_state(indoc!(
4974 r#"abc
4975 def«ˇg»hi
4976
4977 jk
4978 nlmo
4979 "#
4980 ));
4981
4982 // Change selections again
4983 cx.set_state(indoc!(
4984 r#"a«bc
4985 defgˇ»hi
4986
4987 jk
4988 nlmo
4989 "#
4990 ));
4991
4992 cx.update_editor(|view, cx| {
4993 view.add_selection_below(&Default::default(), cx);
4994 });
4995
4996 cx.assert_editor_state(indoc!(
4997 r#"a«bcˇ»
4998 d«efgˇ»hi
4999
5000 j«kˇ»
5001 nlmo
5002 "#
5003 ));
5004
5005 cx.update_editor(|view, cx| {
5006 view.add_selection_below(&Default::default(), cx);
5007 });
5008 cx.assert_editor_state(indoc!(
5009 r#"a«bcˇ»
5010 d«efgˇ»hi
5011
5012 j«kˇ»
5013 n«lmoˇ»
5014 "#
5015 ));
5016 cx.update_editor(|view, cx| {
5017 view.add_selection_above(&Default::default(), cx);
5018 });
5019
5020 cx.assert_editor_state(indoc!(
5021 r#"a«bcˇ»
5022 d«efgˇ»hi
5023
5024 j«kˇ»
5025 nlmo
5026 "#
5027 ));
5028
5029 // Change selections again
5030 cx.set_state(indoc!(
5031 r#"abc
5032 d«ˇefghi
5033
5034 jk
5035 nlm»o
5036 "#
5037 ));
5038
5039 cx.update_editor(|view, cx| {
5040 view.add_selection_above(&Default::default(), cx);
5041 });
5042
5043 cx.assert_editor_state(indoc!(
5044 r#"a«ˇbc»
5045 d«ˇef»ghi
5046
5047 j«ˇk»
5048 n«ˇlm»o
5049 "#
5050 ));
5051
5052 cx.update_editor(|view, cx| {
5053 view.add_selection_below(&Default::default(), cx);
5054 });
5055
5056 cx.assert_editor_state(indoc!(
5057 r#"abc
5058 d«ˇef»ghi
5059
5060 j«ˇk»
5061 n«ˇlm»o
5062 "#
5063 ));
5064}
5065
5066#[gpui::test]
5067async fn test_select_next(cx: &mut gpui::TestAppContext) {
5068 init_test(cx, |_| {});
5069
5070 let mut cx = EditorTestContext::new(cx).await;
5071 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5072
5073 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5074 .unwrap();
5075 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5076
5077 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5078 .unwrap();
5079 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5080
5081 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5082 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5083
5084 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5085 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5086
5087 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5088 .unwrap();
5089 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5090
5091 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5092 .unwrap();
5093 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5094}
5095
5096#[gpui::test]
5097async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5098 init_test(cx, |_| {});
5099
5100 let mut cx = EditorTestContext::new(cx).await;
5101 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5102
5103 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5104 .unwrap();
5105 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5106}
5107
5108#[gpui::test]
5109async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5110 init_test(cx, |_| {});
5111
5112 let mut cx = EditorTestContext::new(cx).await;
5113 cx.set_state(
5114 r#"let foo = 2;
5115lˇet foo = 2;
5116let fooˇ = 2;
5117let foo = 2;
5118let foo = ˇ2;"#,
5119 );
5120
5121 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5122 .unwrap();
5123 cx.assert_editor_state(
5124 r#"let foo = 2;
5125«letˇ» foo = 2;
5126let «fooˇ» = 2;
5127let foo = 2;
5128let foo = «2ˇ»;"#,
5129 );
5130
5131 // noop for multiple selections with different contents
5132 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5133 .unwrap();
5134 cx.assert_editor_state(
5135 r#"let foo = 2;
5136«letˇ» foo = 2;
5137let «fooˇ» = 2;
5138let foo = 2;
5139let foo = «2ˇ»;"#,
5140 );
5141}
5142
5143#[gpui::test]
5144async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5145 init_test(cx, |_| {});
5146
5147 let mut cx =
5148 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5149
5150 cx.assert_editor_state(indoc! {"
5151 ˇbbb
5152 ccc
5153
5154 bbb
5155 ccc
5156 "});
5157 cx.dispatch_action(SelectPrevious::default());
5158 cx.assert_editor_state(indoc! {"
5159 «bbbˇ»
5160 ccc
5161
5162 bbb
5163 ccc
5164 "});
5165 cx.dispatch_action(SelectPrevious::default());
5166 cx.assert_editor_state(indoc! {"
5167 «bbbˇ»
5168 ccc
5169
5170 «bbbˇ»
5171 ccc
5172 "});
5173}
5174
5175#[gpui::test]
5176async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5177 init_test(cx, |_| {});
5178
5179 let mut cx = EditorTestContext::new(cx).await;
5180 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5181
5182 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5183 .unwrap();
5184 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5185
5186 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5187 .unwrap();
5188 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5189
5190 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5191 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5192
5193 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5194 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5195
5196 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5197 .unwrap();
5198 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5199
5200 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5201 .unwrap();
5202 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5203
5204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5205 .unwrap();
5206 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5207}
5208
5209#[gpui::test]
5210async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5211 init_test(cx, |_| {});
5212
5213 let mut cx = EditorTestContext::new(cx).await;
5214 cx.set_state(
5215 r#"let foo = 2;
5216lˇet foo = 2;
5217let fooˇ = 2;
5218let foo = 2;
5219let foo = ˇ2;"#,
5220 );
5221
5222 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5223 .unwrap();
5224 cx.assert_editor_state(
5225 r#"let foo = 2;
5226«letˇ» foo = 2;
5227let «fooˇ» = 2;
5228let foo = 2;
5229let foo = «2ˇ»;"#,
5230 );
5231
5232 // noop for multiple selections with different contents
5233 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5234 .unwrap();
5235 cx.assert_editor_state(
5236 r#"let foo = 2;
5237«letˇ» foo = 2;
5238let «fooˇ» = 2;
5239let foo = 2;
5240let foo = «2ˇ»;"#,
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5250
5251 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5252 .unwrap();
5253 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5254
5255 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5256 .unwrap();
5257 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5258
5259 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5260 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5261
5262 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5263 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5264
5265 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5266 .unwrap();
5267 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5268
5269 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5270 .unwrap();
5271 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5272}
5273
5274#[gpui::test]
5275async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5276 init_test(cx, |_| {});
5277
5278 let language = Arc::new(Language::new(
5279 LanguageConfig::default(),
5280 Some(tree_sitter_rust::LANGUAGE.into()),
5281 ));
5282
5283 let text = r#"
5284 use mod1::mod2::{mod3, mod4};
5285
5286 fn fn_1(param1: bool, param2: &str) {
5287 let var1 = "text";
5288 }
5289 "#
5290 .unindent();
5291
5292 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5293 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5294 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5295
5296 editor
5297 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5298 .await;
5299
5300 editor.update(cx, |view, cx| {
5301 view.change_selections(None, cx, |s| {
5302 s.select_display_ranges([
5303 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5304 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5305 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5306 ]);
5307 });
5308 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5309 });
5310 editor.update(cx, |editor, cx| {
5311 assert_text_with_selections(
5312 editor,
5313 indoc! {r#"
5314 use mod1::mod2::{mod3, «mod4ˇ»};
5315
5316 fn fn_1«ˇ(param1: bool, param2: &str)» {
5317 let var1 = "«textˇ»";
5318 }
5319 "#},
5320 cx,
5321 );
5322 });
5323
5324 editor.update(cx, |view, cx| {
5325 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5326 });
5327 editor.update(cx, |editor, cx| {
5328 assert_text_with_selections(
5329 editor,
5330 indoc! {r#"
5331 use mod1::mod2::«{mod3, mod4}ˇ»;
5332
5333 «ˇfn fn_1(param1: bool, param2: &str) {
5334 let var1 = "text";
5335 }»
5336 "#},
5337 cx,
5338 );
5339 });
5340
5341 editor.update(cx, |view, cx| {
5342 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5343 });
5344 assert_eq!(
5345 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5346 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5347 );
5348
5349 // Trying to expand the selected syntax node one more time has no effect.
5350 editor.update(cx, |view, cx| {
5351 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5352 });
5353 assert_eq!(
5354 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5355 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5356 );
5357
5358 editor.update(cx, |view, cx| {
5359 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5360 });
5361 editor.update(cx, |editor, cx| {
5362 assert_text_with_selections(
5363 editor,
5364 indoc! {r#"
5365 use mod1::mod2::«{mod3, mod4}ˇ»;
5366
5367 «ˇfn fn_1(param1: bool, param2: &str) {
5368 let var1 = "text";
5369 }»
5370 "#},
5371 cx,
5372 );
5373 });
5374
5375 editor.update(cx, |view, cx| {
5376 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5377 });
5378 editor.update(cx, |editor, cx| {
5379 assert_text_with_selections(
5380 editor,
5381 indoc! {r#"
5382 use mod1::mod2::{mod3, «mod4ˇ»};
5383
5384 fn fn_1«ˇ(param1: bool, param2: &str)» {
5385 let var1 = "«textˇ»";
5386 }
5387 "#},
5388 cx,
5389 );
5390 });
5391
5392 editor.update(cx, |view, cx| {
5393 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5394 });
5395 editor.update(cx, |editor, cx| {
5396 assert_text_with_selections(
5397 editor,
5398 indoc! {r#"
5399 use mod1::mod2::{mod3, mo«ˇ»d4};
5400
5401 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5402 let var1 = "te«ˇ»xt";
5403 }
5404 "#},
5405 cx,
5406 );
5407 });
5408
5409 // Trying to shrink the selected syntax node one more time has no effect.
5410 editor.update(cx, |view, cx| {
5411 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5412 });
5413 editor.update(cx, |editor, cx| {
5414 assert_text_with_selections(
5415 editor,
5416 indoc! {r#"
5417 use mod1::mod2::{mod3, mo«ˇ»d4};
5418
5419 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5420 let var1 = "te«ˇ»xt";
5421 }
5422 "#},
5423 cx,
5424 );
5425 });
5426
5427 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5428 // a fold.
5429 editor.update(cx, |view, cx| {
5430 view.fold_creases(
5431 vec![
5432 Crease::simple(
5433 Point::new(0, 21)..Point::new(0, 24),
5434 FoldPlaceholder::test(),
5435 ),
5436 Crease::simple(
5437 Point::new(3, 20)..Point::new(3, 22),
5438 FoldPlaceholder::test(),
5439 ),
5440 ],
5441 true,
5442 cx,
5443 );
5444 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5445 });
5446 editor.update(cx, |editor, cx| {
5447 assert_text_with_selections(
5448 editor,
5449 indoc! {r#"
5450 use mod1::mod2::«{mod3, mod4}ˇ»;
5451
5452 fn fn_1«ˇ(param1: bool, param2: &str)» {
5453 «let var1 = "text";ˇ»
5454 }
5455 "#},
5456 cx,
5457 );
5458 });
5459}
5460
5461#[gpui::test]
5462async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5463 init_test(cx, |_| {});
5464
5465 let language = Arc::new(
5466 Language::new(
5467 LanguageConfig {
5468 brackets: BracketPairConfig {
5469 pairs: vec![
5470 BracketPair {
5471 start: "{".to_string(),
5472 end: "}".to_string(),
5473 close: false,
5474 surround: false,
5475 newline: true,
5476 },
5477 BracketPair {
5478 start: "(".to_string(),
5479 end: ")".to_string(),
5480 close: false,
5481 surround: false,
5482 newline: true,
5483 },
5484 ],
5485 ..Default::default()
5486 },
5487 ..Default::default()
5488 },
5489 Some(tree_sitter_rust::LANGUAGE.into()),
5490 )
5491 .with_indents_query(
5492 r#"
5493 (_ "(" ")" @end) @indent
5494 (_ "{" "}" @end) @indent
5495 "#,
5496 )
5497 .unwrap(),
5498 );
5499
5500 let text = "fn a() {}";
5501
5502 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5503 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5504 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5505 editor
5506 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5507 .await;
5508
5509 editor.update(cx, |editor, cx| {
5510 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5511 editor.newline(&Newline, cx);
5512 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5513 assert_eq!(
5514 editor.selections.ranges(cx),
5515 &[
5516 Point::new(1, 4)..Point::new(1, 4),
5517 Point::new(3, 4)..Point::new(3, 4),
5518 Point::new(5, 0)..Point::new(5, 0)
5519 ]
5520 );
5521 });
5522}
5523
5524#[gpui::test]
5525async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5526 init_test(cx, |_| {});
5527
5528 {
5529 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5530 cx.set_state(indoc! {"
5531 impl A {
5532
5533 fn b() {}
5534
5535 «fn c() {
5536
5537 }ˇ»
5538 }
5539 "});
5540
5541 cx.update_editor(|editor, cx| {
5542 editor.autoindent(&Default::default(), cx);
5543 });
5544
5545 cx.assert_editor_state(indoc! {"
5546 impl A {
5547
5548 fn b() {}
5549
5550 «fn c() {
5551
5552 }ˇ»
5553 }
5554 "});
5555 }
5556
5557 {
5558 let mut cx = EditorTestContext::new_multibuffer(
5559 cx,
5560 [indoc! { "
5561 impl A {
5562 «
5563 // a
5564 fn b(){}
5565 »
5566 «
5567 }
5568 fn c(){}
5569 »
5570 "}],
5571 );
5572
5573 let buffer = cx.update_editor(|editor, cx| {
5574 let buffer = editor.buffer().update(cx, |buffer, _| {
5575 buffer.all_buffers().iter().next().unwrap().clone()
5576 });
5577 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5578 buffer
5579 });
5580
5581 cx.run_until_parked();
5582 cx.update_editor(|editor, cx| {
5583 editor.select_all(&Default::default(), cx);
5584 editor.autoindent(&Default::default(), cx)
5585 });
5586 cx.run_until_parked();
5587
5588 cx.update(|cx| {
5589 pretty_assertions::assert_eq!(
5590 buffer.read(cx).text(),
5591 indoc! { "
5592 impl A {
5593
5594 // a
5595 fn b(){}
5596
5597
5598 }
5599 fn c(){}
5600
5601 " }
5602 )
5603 });
5604 }
5605}
5606
5607#[gpui::test]
5608async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5609 init_test(cx, |_| {});
5610
5611 let mut cx = EditorTestContext::new(cx).await;
5612
5613 let language = Arc::new(Language::new(
5614 LanguageConfig {
5615 brackets: BracketPairConfig {
5616 pairs: vec![
5617 BracketPair {
5618 start: "{".to_string(),
5619 end: "}".to_string(),
5620 close: true,
5621 surround: true,
5622 newline: true,
5623 },
5624 BracketPair {
5625 start: "(".to_string(),
5626 end: ")".to_string(),
5627 close: true,
5628 surround: true,
5629 newline: true,
5630 },
5631 BracketPair {
5632 start: "/*".to_string(),
5633 end: " */".to_string(),
5634 close: true,
5635 surround: true,
5636 newline: true,
5637 },
5638 BracketPair {
5639 start: "[".to_string(),
5640 end: "]".to_string(),
5641 close: false,
5642 surround: false,
5643 newline: true,
5644 },
5645 BracketPair {
5646 start: "\"".to_string(),
5647 end: "\"".to_string(),
5648 close: true,
5649 surround: true,
5650 newline: false,
5651 },
5652 BracketPair {
5653 start: "<".to_string(),
5654 end: ">".to_string(),
5655 close: false,
5656 surround: true,
5657 newline: true,
5658 },
5659 ],
5660 ..Default::default()
5661 },
5662 autoclose_before: "})]".to_string(),
5663 ..Default::default()
5664 },
5665 Some(tree_sitter_rust::LANGUAGE.into()),
5666 ));
5667
5668 cx.language_registry().add(language.clone());
5669 cx.update_buffer(|buffer, cx| {
5670 buffer.set_language(Some(language), cx);
5671 });
5672
5673 cx.set_state(
5674 &r#"
5675 🏀ˇ
5676 εˇ
5677 ❤️ˇ
5678 "#
5679 .unindent(),
5680 );
5681
5682 // autoclose multiple nested brackets at multiple cursors
5683 cx.update_editor(|view, cx| {
5684 view.handle_input("{", cx);
5685 view.handle_input("{", cx);
5686 view.handle_input("{", cx);
5687 });
5688 cx.assert_editor_state(
5689 &"
5690 🏀{{{ˇ}}}
5691 ε{{{ˇ}}}
5692 ❤️{{{ˇ}}}
5693 "
5694 .unindent(),
5695 );
5696
5697 // insert a different closing bracket
5698 cx.update_editor(|view, cx| {
5699 view.handle_input(")", cx);
5700 });
5701 cx.assert_editor_state(
5702 &"
5703 🏀{{{)ˇ}}}
5704 ε{{{)ˇ}}}
5705 ❤️{{{)ˇ}}}
5706 "
5707 .unindent(),
5708 );
5709
5710 // skip over the auto-closed brackets when typing a closing bracket
5711 cx.update_editor(|view, cx| {
5712 view.move_right(&MoveRight, cx);
5713 view.handle_input("}", cx);
5714 view.handle_input("}", cx);
5715 view.handle_input("}", cx);
5716 });
5717 cx.assert_editor_state(
5718 &"
5719 🏀{{{)}}}}ˇ
5720 ε{{{)}}}}ˇ
5721 ❤️{{{)}}}}ˇ
5722 "
5723 .unindent(),
5724 );
5725
5726 // autoclose multi-character pairs
5727 cx.set_state(
5728 &"
5729 ˇ
5730 ˇ
5731 "
5732 .unindent(),
5733 );
5734 cx.update_editor(|view, cx| {
5735 view.handle_input("/", cx);
5736 view.handle_input("*", cx);
5737 });
5738 cx.assert_editor_state(
5739 &"
5740 /*ˇ */
5741 /*ˇ */
5742 "
5743 .unindent(),
5744 );
5745
5746 // one cursor autocloses a multi-character pair, one cursor
5747 // does not autoclose.
5748 cx.set_state(
5749 &"
5750 /ˇ
5751 ˇ
5752 "
5753 .unindent(),
5754 );
5755 cx.update_editor(|view, cx| view.handle_input("*", cx));
5756 cx.assert_editor_state(
5757 &"
5758 /*ˇ */
5759 *ˇ
5760 "
5761 .unindent(),
5762 );
5763
5764 // Don't autoclose if the next character isn't whitespace and isn't
5765 // listed in the language's "autoclose_before" section.
5766 cx.set_state("ˇa b");
5767 cx.update_editor(|view, cx| view.handle_input("{", cx));
5768 cx.assert_editor_state("{ˇa b");
5769
5770 // Don't autoclose if `close` is false for the bracket pair
5771 cx.set_state("ˇ");
5772 cx.update_editor(|view, cx| view.handle_input("[", cx));
5773 cx.assert_editor_state("[ˇ");
5774
5775 // Surround with brackets if text is selected
5776 cx.set_state("«aˇ» b");
5777 cx.update_editor(|view, cx| view.handle_input("{", cx));
5778 cx.assert_editor_state("{«aˇ»} b");
5779
5780 // Autclose pair where the start and end characters are the same
5781 cx.set_state("aˇ");
5782 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5783 cx.assert_editor_state("a\"ˇ\"");
5784 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5785 cx.assert_editor_state("a\"\"ˇ");
5786
5787 // Don't autoclose pair if autoclose is disabled
5788 cx.set_state("ˇ");
5789 cx.update_editor(|view, cx| view.handle_input("<", cx));
5790 cx.assert_editor_state("<ˇ");
5791
5792 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5793 cx.set_state("«aˇ» b");
5794 cx.update_editor(|view, cx| view.handle_input("<", cx));
5795 cx.assert_editor_state("<«aˇ»> b");
5796}
5797
5798#[gpui::test]
5799async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5800 init_test(cx, |settings| {
5801 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5802 });
5803
5804 let mut cx = EditorTestContext::new(cx).await;
5805
5806 let language = Arc::new(Language::new(
5807 LanguageConfig {
5808 brackets: BracketPairConfig {
5809 pairs: vec![
5810 BracketPair {
5811 start: "{".to_string(),
5812 end: "}".to_string(),
5813 close: true,
5814 surround: true,
5815 newline: true,
5816 },
5817 BracketPair {
5818 start: "(".to_string(),
5819 end: ")".to_string(),
5820 close: true,
5821 surround: true,
5822 newline: true,
5823 },
5824 BracketPair {
5825 start: "[".to_string(),
5826 end: "]".to_string(),
5827 close: false,
5828 surround: false,
5829 newline: true,
5830 },
5831 ],
5832 ..Default::default()
5833 },
5834 autoclose_before: "})]".to_string(),
5835 ..Default::default()
5836 },
5837 Some(tree_sitter_rust::LANGUAGE.into()),
5838 ));
5839
5840 cx.language_registry().add(language.clone());
5841 cx.update_buffer(|buffer, cx| {
5842 buffer.set_language(Some(language), cx);
5843 });
5844
5845 cx.set_state(
5846 &"
5847 ˇ
5848 ˇ
5849 ˇ
5850 "
5851 .unindent(),
5852 );
5853
5854 // ensure only matching closing brackets are skipped over
5855 cx.update_editor(|view, cx| {
5856 view.handle_input("}", cx);
5857 view.move_left(&MoveLeft, cx);
5858 view.handle_input(")", cx);
5859 view.move_left(&MoveLeft, cx);
5860 });
5861 cx.assert_editor_state(
5862 &"
5863 ˇ)}
5864 ˇ)}
5865 ˇ)}
5866 "
5867 .unindent(),
5868 );
5869
5870 // skip-over closing brackets at multiple cursors
5871 cx.update_editor(|view, cx| {
5872 view.handle_input(")", cx);
5873 view.handle_input("}", cx);
5874 });
5875 cx.assert_editor_state(
5876 &"
5877 )}ˇ
5878 )}ˇ
5879 )}ˇ
5880 "
5881 .unindent(),
5882 );
5883
5884 // ignore non-close brackets
5885 cx.update_editor(|view, cx| {
5886 view.handle_input("]", cx);
5887 view.move_left(&MoveLeft, cx);
5888 view.handle_input("]", cx);
5889 });
5890 cx.assert_editor_state(
5891 &"
5892 )}]ˇ]
5893 )}]ˇ]
5894 )}]ˇ]
5895 "
5896 .unindent(),
5897 );
5898}
5899
5900#[gpui::test]
5901async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5902 init_test(cx, |_| {});
5903
5904 let mut cx = EditorTestContext::new(cx).await;
5905
5906 let html_language = Arc::new(
5907 Language::new(
5908 LanguageConfig {
5909 name: "HTML".into(),
5910 brackets: BracketPairConfig {
5911 pairs: vec![
5912 BracketPair {
5913 start: "<".into(),
5914 end: ">".into(),
5915 close: true,
5916 ..Default::default()
5917 },
5918 BracketPair {
5919 start: "{".into(),
5920 end: "}".into(),
5921 close: true,
5922 ..Default::default()
5923 },
5924 BracketPair {
5925 start: "(".into(),
5926 end: ")".into(),
5927 close: true,
5928 ..Default::default()
5929 },
5930 ],
5931 ..Default::default()
5932 },
5933 autoclose_before: "})]>".into(),
5934 ..Default::default()
5935 },
5936 Some(tree_sitter_html::language()),
5937 )
5938 .with_injection_query(
5939 r#"
5940 (script_element
5941 (raw_text) @content
5942 (#set! "language" "javascript"))
5943 "#,
5944 )
5945 .unwrap(),
5946 );
5947
5948 let javascript_language = Arc::new(Language::new(
5949 LanguageConfig {
5950 name: "JavaScript".into(),
5951 brackets: BracketPairConfig {
5952 pairs: vec![
5953 BracketPair {
5954 start: "/*".into(),
5955 end: " */".into(),
5956 close: true,
5957 ..Default::default()
5958 },
5959 BracketPair {
5960 start: "{".into(),
5961 end: "}".into(),
5962 close: true,
5963 ..Default::default()
5964 },
5965 BracketPair {
5966 start: "(".into(),
5967 end: ")".into(),
5968 close: true,
5969 ..Default::default()
5970 },
5971 ],
5972 ..Default::default()
5973 },
5974 autoclose_before: "})]>".into(),
5975 ..Default::default()
5976 },
5977 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5978 ));
5979
5980 cx.language_registry().add(html_language.clone());
5981 cx.language_registry().add(javascript_language.clone());
5982
5983 cx.update_buffer(|buffer, cx| {
5984 buffer.set_language(Some(html_language), cx);
5985 });
5986
5987 cx.set_state(
5988 &r#"
5989 <body>ˇ
5990 <script>
5991 var x = 1;ˇ
5992 </script>
5993 </body>ˇ
5994 "#
5995 .unindent(),
5996 );
5997
5998 // Precondition: different languages are active at different locations.
5999 cx.update_editor(|editor, cx| {
6000 let snapshot = editor.snapshot(cx);
6001 let cursors = editor.selections.ranges::<usize>(cx);
6002 let languages = cursors
6003 .iter()
6004 .map(|c| snapshot.language_at(c.start).unwrap().name())
6005 .collect::<Vec<_>>();
6006 assert_eq!(
6007 languages,
6008 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6009 );
6010 });
6011
6012 // Angle brackets autoclose in HTML, but not JavaScript.
6013 cx.update_editor(|editor, cx| {
6014 editor.handle_input("<", cx);
6015 editor.handle_input("a", cx);
6016 });
6017 cx.assert_editor_state(
6018 &r#"
6019 <body><aˇ>
6020 <script>
6021 var x = 1;<aˇ
6022 </script>
6023 </body><aˇ>
6024 "#
6025 .unindent(),
6026 );
6027
6028 // Curly braces and parens autoclose in both HTML and JavaScript.
6029 cx.update_editor(|editor, cx| {
6030 editor.handle_input(" b=", cx);
6031 editor.handle_input("{", cx);
6032 editor.handle_input("c", cx);
6033 editor.handle_input("(", cx);
6034 });
6035 cx.assert_editor_state(
6036 &r#"
6037 <body><a b={c(ˇ)}>
6038 <script>
6039 var x = 1;<a b={c(ˇ)}
6040 </script>
6041 </body><a b={c(ˇ)}>
6042 "#
6043 .unindent(),
6044 );
6045
6046 // Brackets that were already autoclosed are skipped.
6047 cx.update_editor(|editor, cx| {
6048 editor.handle_input(")", cx);
6049 editor.handle_input("d", cx);
6050 editor.handle_input("}", cx);
6051 });
6052 cx.assert_editor_state(
6053 &r#"
6054 <body><a b={c()d}ˇ>
6055 <script>
6056 var x = 1;<a b={c()d}ˇ
6057 </script>
6058 </body><a b={c()d}ˇ>
6059 "#
6060 .unindent(),
6061 );
6062 cx.update_editor(|editor, cx| {
6063 editor.handle_input(">", cx);
6064 });
6065 cx.assert_editor_state(
6066 &r#"
6067 <body><a b={c()d}>ˇ
6068 <script>
6069 var x = 1;<a b={c()d}>ˇ
6070 </script>
6071 </body><a b={c()d}>ˇ
6072 "#
6073 .unindent(),
6074 );
6075
6076 // Reset
6077 cx.set_state(
6078 &r#"
6079 <body>ˇ
6080 <script>
6081 var x = 1;ˇ
6082 </script>
6083 </body>ˇ
6084 "#
6085 .unindent(),
6086 );
6087
6088 cx.update_editor(|editor, cx| {
6089 editor.handle_input("<", cx);
6090 });
6091 cx.assert_editor_state(
6092 &r#"
6093 <body><ˇ>
6094 <script>
6095 var x = 1;<ˇ
6096 </script>
6097 </body><ˇ>
6098 "#
6099 .unindent(),
6100 );
6101
6102 // When backspacing, the closing angle brackets are removed.
6103 cx.update_editor(|editor, cx| {
6104 editor.backspace(&Backspace, cx);
6105 });
6106 cx.assert_editor_state(
6107 &r#"
6108 <body>ˇ
6109 <script>
6110 var x = 1;ˇ
6111 </script>
6112 </body>ˇ
6113 "#
6114 .unindent(),
6115 );
6116
6117 // Block comments autoclose in JavaScript, but not HTML.
6118 cx.update_editor(|editor, cx| {
6119 editor.handle_input("/", cx);
6120 editor.handle_input("*", cx);
6121 });
6122 cx.assert_editor_state(
6123 &r#"
6124 <body>/*ˇ
6125 <script>
6126 var x = 1;/*ˇ */
6127 </script>
6128 </body>/*ˇ
6129 "#
6130 .unindent(),
6131 );
6132}
6133
6134#[gpui::test]
6135async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6136 init_test(cx, |_| {});
6137
6138 let mut cx = EditorTestContext::new(cx).await;
6139
6140 let rust_language = Arc::new(
6141 Language::new(
6142 LanguageConfig {
6143 name: "Rust".into(),
6144 brackets: serde_json::from_value(json!([
6145 { "start": "{", "end": "}", "close": true, "newline": true },
6146 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6147 ]))
6148 .unwrap(),
6149 autoclose_before: "})]>".into(),
6150 ..Default::default()
6151 },
6152 Some(tree_sitter_rust::LANGUAGE.into()),
6153 )
6154 .with_override_query("(string_literal) @string")
6155 .unwrap(),
6156 );
6157
6158 cx.language_registry().add(rust_language.clone());
6159 cx.update_buffer(|buffer, cx| {
6160 buffer.set_language(Some(rust_language), cx);
6161 });
6162
6163 cx.set_state(
6164 &r#"
6165 let x = ˇ
6166 "#
6167 .unindent(),
6168 );
6169
6170 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6171 cx.update_editor(|editor, cx| {
6172 editor.handle_input("\"", cx);
6173 });
6174 cx.assert_editor_state(
6175 &r#"
6176 let x = "ˇ"
6177 "#
6178 .unindent(),
6179 );
6180
6181 // Inserting another quotation mark. The cursor moves across the existing
6182 // automatically-inserted quotation mark.
6183 cx.update_editor(|editor, cx| {
6184 editor.handle_input("\"", cx);
6185 });
6186 cx.assert_editor_state(
6187 &r#"
6188 let x = ""ˇ
6189 "#
6190 .unindent(),
6191 );
6192
6193 // Reset
6194 cx.set_state(
6195 &r#"
6196 let x = ˇ
6197 "#
6198 .unindent(),
6199 );
6200
6201 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6202 cx.update_editor(|editor, cx| {
6203 editor.handle_input("\"", cx);
6204 editor.handle_input(" ", cx);
6205 editor.move_left(&Default::default(), cx);
6206 editor.handle_input("\\", cx);
6207 editor.handle_input("\"", cx);
6208 });
6209 cx.assert_editor_state(
6210 &r#"
6211 let x = "\"ˇ "
6212 "#
6213 .unindent(),
6214 );
6215
6216 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6217 // mark. Nothing is inserted.
6218 cx.update_editor(|editor, cx| {
6219 editor.move_right(&Default::default(), cx);
6220 editor.handle_input("\"", cx);
6221 });
6222 cx.assert_editor_state(
6223 &r#"
6224 let x = "\" "ˇ
6225 "#
6226 .unindent(),
6227 );
6228}
6229
6230#[gpui::test]
6231async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6232 init_test(cx, |_| {});
6233
6234 let language = Arc::new(Language::new(
6235 LanguageConfig {
6236 brackets: BracketPairConfig {
6237 pairs: vec![
6238 BracketPair {
6239 start: "{".to_string(),
6240 end: "}".to_string(),
6241 close: true,
6242 surround: true,
6243 newline: true,
6244 },
6245 BracketPair {
6246 start: "/* ".to_string(),
6247 end: "*/".to_string(),
6248 close: true,
6249 surround: true,
6250 ..Default::default()
6251 },
6252 ],
6253 ..Default::default()
6254 },
6255 ..Default::default()
6256 },
6257 Some(tree_sitter_rust::LANGUAGE.into()),
6258 ));
6259
6260 let text = r#"
6261 a
6262 b
6263 c
6264 "#
6265 .unindent();
6266
6267 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6268 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6269 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6270 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6271 .await;
6272
6273 view.update(cx, |view, cx| {
6274 view.change_selections(None, cx, |s| {
6275 s.select_display_ranges([
6276 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6277 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6278 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6279 ])
6280 });
6281
6282 view.handle_input("{", cx);
6283 view.handle_input("{", cx);
6284 view.handle_input("{", cx);
6285 assert_eq!(
6286 view.text(cx),
6287 "
6288 {{{a}}}
6289 {{{b}}}
6290 {{{c}}}
6291 "
6292 .unindent()
6293 );
6294 assert_eq!(
6295 view.selections.display_ranges(cx),
6296 [
6297 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6298 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6299 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6300 ]
6301 );
6302
6303 view.undo(&Undo, cx);
6304 view.undo(&Undo, cx);
6305 view.undo(&Undo, cx);
6306 assert_eq!(
6307 view.text(cx),
6308 "
6309 a
6310 b
6311 c
6312 "
6313 .unindent()
6314 );
6315 assert_eq!(
6316 view.selections.display_ranges(cx),
6317 [
6318 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6319 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6320 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6321 ]
6322 );
6323
6324 // Ensure inserting the first character of a multi-byte bracket pair
6325 // doesn't surround the selections with the bracket.
6326 view.handle_input("/", cx);
6327 assert_eq!(
6328 view.text(cx),
6329 "
6330 /
6331 /
6332 /
6333 "
6334 .unindent()
6335 );
6336 assert_eq!(
6337 view.selections.display_ranges(cx),
6338 [
6339 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6340 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6341 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6342 ]
6343 );
6344
6345 view.undo(&Undo, cx);
6346 assert_eq!(
6347 view.text(cx),
6348 "
6349 a
6350 b
6351 c
6352 "
6353 .unindent()
6354 );
6355 assert_eq!(
6356 view.selections.display_ranges(cx),
6357 [
6358 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6359 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6360 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6361 ]
6362 );
6363
6364 // Ensure inserting the last character of a multi-byte bracket pair
6365 // doesn't surround the selections with the bracket.
6366 view.handle_input("*", cx);
6367 assert_eq!(
6368 view.text(cx),
6369 "
6370 *
6371 *
6372 *
6373 "
6374 .unindent()
6375 );
6376 assert_eq!(
6377 view.selections.display_ranges(cx),
6378 [
6379 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6380 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6381 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6382 ]
6383 );
6384 });
6385}
6386
6387#[gpui::test]
6388async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6389 init_test(cx, |_| {});
6390
6391 let language = Arc::new(Language::new(
6392 LanguageConfig {
6393 brackets: BracketPairConfig {
6394 pairs: vec![BracketPair {
6395 start: "{".to_string(),
6396 end: "}".to_string(),
6397 close: true,
6398 surround: true,
6399 newline: true,
6400 }],
6401 ..Default::default()
6402 },
6403 autoclose_before: "}".to_string(),
6404 ..Default::default()
6405 },
6406 Some(tree_sitter_rust::LANGUAGE.into()),
6407 ));
6408
6409 let text = r#"
6410 a
6411 b
6412 c
6413 "#
6414 .unindent();
6415
6416 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6417 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6418 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6419 editor
6420 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6421 .await;
6422
6423 editor.update(cx, |editor, cx| {
6424 editor.change_selections(None, cx, |s| {
6425 s.select_ranges([
6426 Point::new(0, 1)..Point::new(0, 1),
6427 Point::new(1, 1)..Point::new(1, 1),
6428 Point::new(2, 1)..Point::new(2, 1),
6429 ])
6430 });
6431
6432 editor.handle_input("{", cx);
6433 editor.handle_input("{", cx);
6434 editor.handle_input("_", cx);
6435 assert_eq!(
6436 editor.text(cx),
6437 "
6438 a{{_}}
6439 b{{_}}
6440 c{{_}}
6441 "
6442 .unindent()
6443 );
6444 assert_eq!(
6445 editor.selections.ranges::<Point>(cx),
6446 [
6447 Point::new(0, 4)..Point::new(0, 4),
6448 Point::new(1, 4)..Point::new(1, 4),
6449 Point::new(2, 4)..Point::new(2, 4)
6450 ]
6451 );
6452
6453 editor.backspace(&Default::default(), cx);
6454 editor.backspace(&Default::default(), cx);
6455 assert_eq!(
6456 editor.text(cx),
6457 "
6458 a{}
6459 b{}
6460 c{}
6461 "
6462 .unindent()
6463 );
6464 assert_eq!(
6465 editor.selections.ranges::<Point>(cx),
6466 [
6467 Point::new(0, 2)..Point::new(0, 2),
6468 Point::new(1, 2)..Point::new(1, 2),
6469 Point::new(2, 2)..Point::new(2, 2)
6470 ]
6471 );
6472
6473 editor.delete_to_previous_word_start(&Default::default(), cx);
6474 assert_eq!(
6475 editor.text(cx),
6476 "
6477 a
6478 b
6479 c
6480 "
6481 .unindent()
6482 );
6483 assert_eq!(
6484 editor.selections.ranges::<Point>(cx),
6485 [
6486 Point::new(0, 1)..Point::new(0, 1),
6487 Point::new(1, 1)..Point::new(1, 1),
6488 Point::new(2, 1)..Point::new(2, 1)
6489 ]
6490 );
6491 });
6492}
6493
6494#[gpui::test]
6495async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6496 init_test(cx, |settings| {
6497 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6498 });
6499
6500 let mut cx = EditorTestContext::new(cx).await;
6501
6502 let language = Arc::new(Language::new(
6503 LanguageConfig {
6504 brackets: BracketPairConfig {
6505 pairs: vec![
6506 BracketPair {
6507 start: "{".to_string(),
6508 end: "}".to_string(),
6509 close: true,
6510 surround: true,
6511 newline: true,
6512 },
6513 BracketPair {
6514 start: "(".to_string(),
6515 end: ")".to_string(),
6516 close: true,
6517 surround: true,
6518 newline: true,
6519 },
6520 BracketPair {
6521 start: "[".to_string(),
6522 end: "]".to_string(),
6523 close: false,
6524 surround: true,
6525 newline: true,
6526 },
6527 ],
6528 ..Default::default()
6529 },
6530 autoclose_before: "})]".to_string(),
6531 ..Default::default()
6532 },
6533 Some(tree_sitter_rust::LANGUAGE.into()),
6534 ));
6535
6536 cx.language_registry().add(language.clone());
6537 cx.update_buffer(|buffer, cx| {
6538 buffer.set_language(Some(language), cx);
6539 });
6540
6541 cx.set_state(
6542 &"
6543 {(ˇ)}
6544 [[ˇ]]
6545 {(ˇ)}
6546 "
6547 .unindent(),
6548 );
6549
6550 cx.update_editor(|view, cx| {
6551 view.backspace(&Default::default(), cx);
6552 view.backspace(&Default::default(), cx);
6553 });
6554
6555 cx.assert_editor_state(
6556 &"
6557 ˇ
6558 ˇ]]
6559 ˇ
6560 "
6561 .unindent(),
6562 );
6563
6564 cx.update_editor(|view, cx| {
6565 view.handle_input("{", cx);
6566 view.handle_input("{", cx);
6567 view.move_right(&MoveRight, cx);
6568 view.move_right(&MoveRight, cx);
6569 view.move_left(&MoveLeft, cx);
6570 view.move_left(&MoveLeft, cx);
6571 view.backspace(&Default::default(), cx);
6572 });
6573
6574 cx.assert_editor_state(
6575 &"
6576 {ˇ}
6577 {ˇ}]]
6578 {ˇ}
6579 "
6580 .unindent(),
6581 );
6582
6583 cx.update_editor(|view, cx| {
6584 view.backspace(&Default::default(), cx);
6585 });
6586
6587 cx.assert_editor_state(
6588 &"
6589 ˇ
6590 ˇ]]
6591 ˇ
6592 "
6593 .unindent(),
6594 );
6595}
6596
6597#[gpui::test]
6598async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6599 init_test(cx, |_| {});
6600
6601 let language = Arc::new(Language::new(
6602 LanguageConfig::default(),
6603 Some(tree_sitter_rust::LANGUAGE.into()),
6604 ));
6605
6606 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6607 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6608 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6609 editor
6610 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6611 .await;
6612
6613 editor.update(cx, |editor, cx| {
6614 editor.set_auto_replace_emoji_shortcode(true);
6615
6616 editor.handle_input("Hello ", cx);
6617 editor.handle_input(":wave", cx);
6618 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6619
6620 editor.handle_input(":", cx);
6621 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6622
6623 editor.handle_input(" :smile", cx);
6624 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6625
6626 editor.handle_input(":", cx);
6627 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6628
6629 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6630 editor.handle_input(":wave", cx);
6631 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6632
6633 editor.handle_input(":", cx);
6634 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6635
6636 editor.handle_input(":1", cx);
6637 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6638
6639 editor.handle_input(":", cx);
6640 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6641
6642 // Ensure shortcode does not get replaced when it is part of a word
6643 editor.handle_input(" Test:wave", cx);
6644 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6645
6646 editor.handle_input(":", cx);
6647 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6648
6649 editor.set_auto_replace_emoji_shortcode(false);
6650
6651 // Ensure shortcode does not get replaced when auto replace is off
6652 editor.handle_input(" :wave", cx);
6653 assert_eq!(
6654 editor.text(cx),
6655 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6656 );
6657
6658 editor.handle_input(":", cx);
6659 assert_eq!(
6660 editor.text(cx),
6661 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6662 );
6663 });
6664}
6665
6666#[gpui::test]
6667async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6668 init_test(cx, |_| {});
6669
6670 let (text, insertion_ranges) = marked_text_ranges(
6671 indoc! {"
6672 ˇ
6673 "},
6674 false,
6675 );
6676
6677 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6678 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6679
6680 _ = editor.update(cx, |editor, cx| {
6681 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6682
6683 editor
6684 .insert_snippet(&insertion_ranges, snippet, cx)
6685 .unwrap();
6686
6687 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6688 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6689 assert_eq!(editor.text(cx), expected_text);
6690 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6691 }
6692
6693 assert(
6694 editor,
6695 cx,
6696 indoc! {"
6697 type «» =•
6698 "},
6699 );
6700
6701 assert!(editor.context_menu_visible(), "There should be a matches");
6702 });
6703}
6704
6705#[gpui::test]
6706async fn test_snippets(cx: &mut gpui::TestAppContext) {
6707 init_test(cx, |_| {});
6708
6709 let (text, insertion_ranges) = marked_text_ranges(
6710 indoc! {"
6711 a.ˇ b
6712 a.ˇ b
6713 a.ˇ b
6714 "},
6715 false,
6716 );
6717
6718 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6719 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6720
6721 editor.update(cx, |editor, cx| {
6722 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6723
6724 editor
6725 .insert_snippet(&insertion_ranges, snippet, cx)
6726 .unwrap();
6727
6728 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6729 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6730 assert_eq!(editor.text(cx), expected_text);
6731 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6732 }
6733
6734 assert(
6735 editor,
6736 cx,
6737 indoc! {"
6738 a.f(«one», two, «three») b
6739 a.f(«one», two, «three») b
6740 a.f(«one», two, «three») b
6741 "},
6742 );
6743
6744 // Can't move earlier than the first tab stop
6745 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6746 assert(
6747 editor,
6748 cx,
6749 indoc! {"
6750 a.f(«one», two, «three») b
6751 a.f(«one», two, «three») b
6752 a.f(«one», two, «three») b
6753 "},
6754 );
6755
6756 assert!(editor.move_to_next_snippet_tabstop(cx));
6757 assert(
6758 editor,
6759 cx,
6760 indoc! {"
6761 a.f(one, «two», three) b
6762 a.f(one, «two», three) b
6763 a.f(one, «two», three) b
6764 "},
6765 );
6766
6767 editor.move_to_prev_snippet_tabstop(cx);
6768 assert(
6769 editor,
6770 cx,
6771 indoc! {"
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 a.f(«one», two, «three») b
6775 "},
6776 );
6777
6778 assert!(editor.move_to_next_snippet_tabstop(cx));
6779 assert(
6780 editor,
6781 cx,
6782 indoc! {"
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 a.f(one, «two», three) b
6786 "},
6787 );
6788 assert!(editor.move_to_next_snippet_tabstop(cx));
6789 assert(
6790 editor,
6791 cx,
6792 indoc! {"
6793 a.f(one, two, three)ˇ b
6794 a.f(one, two, three)ˇ b
6795 a.f(one, two, three)ˇ b
6796 "},
6797 );
6798
6799 // As soon as the last tab stop is reached, snippet state is gone
6800 editor.move_to_prev_snippet_tabstop(cx);
6801 assert(
6802 editor,
6803 cx,
6804 indoc! {"
6805 a.f(one, two, three)ˇ b
6806 a.f(one, two, three)ˇ b
6807 a.f(one, two, three)ˇ b
6808 "},
6809 );
6810 });
6811}
6812
6813#[gpui::test]
6814async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6815 init_test(cx, |_| {});
6816
6817 let fs = FakeFs::new(cx.executor());
6818 fs.insert_file("/file.rs", Default::default()).await;
6819
6820 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6821
6822 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6823 language_registry.add(rust_lang());
6824 let mut fake_servers = language_registry.register_fake_lsp(
6825 "Rust",
6826 FakeLspAdapter {
6827 capabilities: lsp::ServerCapabilities {
6828 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6829 ..Default::default()
6830 },
6831 ..Default::default()
6832 },
6833 );
6834
6835 let buffer = project
6836 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6837 .await
6838 .unwrap();
6839
6840 cx.executor().start_waiting();
6841 let fake_server = fake_servers.next().await.unwrap();
6842
6843 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6844 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6845 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6846 assert!(cx.read(|cx| editor.is_dirty(cx)));
6847
6848 let save = editor
6849 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6850 .unwrap();
6851 fake_server
6852 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6853 assert_eq!(
6854 params.text_document.uri,
6855 lsp::Url::from_file_path("/file.rs").unwrap()
6856 );
6857 assert_eq!(params.options.tab_size, 4);
6858 Ok(Some(vec![lsp::TextEdit::new(
6859 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6860 ", ".to_string(),
6861 )]))
6862 })
6863 .next()
6864 .await;
6865 cx.executor().start_waiting();
6866 save.await;
6867
6868 assert_eq!(
6869 editor.update(cx, |editor, cx| editor.text(cx)),
6870 "one, two\nthree\n"
6871 );
6872 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6873
6874 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6875 assert!(cx.read(|cx| editor.is_dirty(cx)));
6876
6877 // Ensure we can still save even if formatting hangs.
6878 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6879 assert_eq!(
6880 params.text_document.uri,
6881 lsp::Url::from_file_path("/file.rs").unwrap()
6882 );
6883 futures::future::pending::<()>().await;
6884 unreachable!()
6885 });
6886 let save = editor
6887 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6888 .unwrap();
6889 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6890 cx.executor().start_waiting();
6891 save.await;
6892 assert_eq!(
6893 editor.update(cx, |editor, cx| editor.text(cx)),
6894 "one\ntwo\nthree\n"
6895 );
6896 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6897
6898 // For non-dirty buffer, no formatting request should be sent
6899 let save = editor
6900 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6901 .unwrap();
6902 let _pending_format_request = fake_server
6903 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6904 panic!("Should not be invoked on non-dirty buffer");
6905 })
6906 .next();
6907 cx.executor().start_waiting();
6908 save.await;
6909
6910 // Set rust language override and assert overridden tabsize is sent to language server
6911 update_test_language_settings(cx, |settings| {
6912 settings.languages.insert(
6913 "Rust".into(),
6914 LanguageSettingsContent {
6915 tab_size: NonZeroU32::new(8),
6916 ..Default::default()
6917 },
6918 );
6919 });
6920
6921 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6922 assert!(cx.read(|cx| editor.is_dirty(cx)));
6923 let save = editor
6924 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6925 .unwrap();
6926 fake_server
6927 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6928 assert_eq!(
6929 params.text_document.uri,
6930 lsp::Url::from_file_path("/file.rs").unwrap()
6931 );
6932 assert_eq!(params.options.tab_size, 8);
6933 Ok(Some(vec![]))
6934 })
6935 .next()
6936 .await;
6937 cx.executor().start_waiting();
6938 save.await;
6939}
6940
6941#[gpui::test]
6942async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6943 init_test(cx, |_| {});
6944
6945 let cols = 4;
6946 let rows = 10;
6947 let sample_text_1 = sample_text(rows, cols, 'a');
6948 assert_eq!(
6949 sample_text_1,
6950 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6951 );
6952 let sample_text_2 = sample_text(rows, cols, 'l');
6953 assert_eq!(
6954 sample_text_2,
6955 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6956 );
6957 let sample_text_3 = sample_text(rows, cols, 'v');
6958 assert_eq!(
6959 sample_text_3,
6960 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6961 );
6962
6963 let fs = FakeFs::new(cx.executor());
6964 fs.insert_tree(
6965 "/a",
6966 json!({
6967 "main.rs": sample_text_1,
6968 "other.rs": sample_text_2,
6969 "lib.rs": sample_text_3,
6970 }),
6971 )
6972 .await;
6973
6974 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6975 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6976 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6977
6978 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6979 language_registry.add(rust_lang());
6980 let mut fake_servers = language_registry.register_fake_lsp(
6981 "Rust",
6982 FakeLspAdapter {
6983 capabilities: lsp::ServerCapabilities {
6984 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6985 ..Default::default()
6986 },
6987 ..Default::default()
6988 },
6989 );
6990
6991 let worktree = project.update(cx, |project, cx| {
6992 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6993 assert_eq!(worktrees.len(), 1);
6994 worktrees.pop().unwrap()
6995 });
6996 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6997
6998 let buffer_1 = project
6999 .update(cx, |project, cx| {
7000 project.open_buffer((worktree_id, "main.rs"), cx)
7001 })
7002 .await
7003 .unwrap();
7004 let buffer_2 = project
7005 .update(cx, |project, cx| {
7006 project.open_buffer((worktree_id, "other.rs"), cx)
7007 })
7008 .await
7009 .unwrap();
7010 let buffer_3 = project
7011 .update(cx, |project, cx| {
7012 project.open_buffer((worktree_id, "lib.rs"), cx)
7013 })
7014 .await
7015 .unwrap();
7016
7017 let multi_buffer = cx.new_model(|cx| {
7018 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7019 multi_buffer.push_excerpts(
7020 buffer_1.clone(),
7021 [
7022 ExcerptRange {
7023 context: Point::new(0, 0)..Point::new(3, 0),
7024 primary: None,
7025 },
7026 ExcerptRange {
7027 context: Point::new(5, 0)..Point::new(7, 0),
7028 primary: None,
7029 },
7030 ExcerptRange {
7031 context: Point::new(9, 0)..Point::new(10, 4),
7032 primary: None,
7033 },
7034 ],
7035 cx,
7036 );
7037 multi_buffer.push_excerpts(
7038 buffer_2.clone(),
7039 [
7040 ExcerptRange {
7041 context: Point::new(0, 0)..Point::new(3, 0),
7042 primary: None,
7043 },
7044 ExcerptRange {
7045 context: Point::new(5, 0)..Point::new(7, 0),
7046 primary: None,
7047 },
7048 ExcerptRange {
7049 context: Point::new(9, 0)..Point::new(10, 4),
7050 primary: None,
7051 },
7052 ],
7053 cx,
7054 );
7055 multi_buffer.push_excerpts(
7056 buffer_3.clone(),
7057 [
7058 ExcerptRange {
7059 context: Point::new(0, 0)..Point::new(3, 0),
7060 primary: None,
7061 },
7062 ExcerptRange {
7063 context: Point::new(5, 0)..Point::new(7, 0),
7064 primary: None,
7065 },
7066 ExcerptRange {
7067 context: Point::new(9, 0)..Point::new(10, 4),
7068 primary: None,
7069 },
7070 ],
7071 cx,
7072 );
7073 multi_buffer
7074 });
7075 let multi_buffer_editor = cx.new_view(|cx| {
7076 Editor::new(
7077 EditorMode::Full,
7078 multi_buffer,
7079 Some(project.clone()),
7080 true,
7081 cx,
7082 )
7083 });
7084
7085 multi_buffer_editor.update(cx, |editor, cx| {
7086 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7087 editor.insert("|one|two|three|", cx);
7088 });
7089 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7090 multi_buffer_editor.update(cx, |editor, cx| {
7091 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7092 s.select_ranges(Some(60..70))
7093 });
7094 editor.insert("|four|five|six|", cx);
7095 });
7096 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7097
7098 // First two buffers should be edited, but not the third one.
7099 assert_eq!(
7100 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7101 "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}",
7102 );
7103 buffer_1.update(cx, |buffer, _| {
7104 assert!(buffer.is_dirty());
7105 assert_eq!(
7106 buffer.text(),
7107 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7108 )
7109 });
7110 buffer_2.update(cx, |buffer, _| {
7111 assert!(buffer.is_dirty());
7112 assert_eq!(
7113 buffer.text(),
7114 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7115 )
7116 });
7117 buffer_3.update(cx, |buffer, _| {
7118 assert!(!buffer.is_dirty());
7119 assert_eq!(buffer.text(), sample_text_3,)
7120 });
7121
7122 cx.executor().start_waiting();
7123 let save = multi_buffer_editor
7124 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7125 .unwrap();
7126
7127 let fake_server = fake_servers.next().await.unwrap();
7128 fake_server
7129 .server
7130 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7131 Ok(Some(vec![lsp::TextEdit::new(
7132 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7133 format!("[{} formatted]", params.text_document.uri),
7134 )]))
7135 })
7136 .detach();
7137 save.await;
7138
7139 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7140 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7141 assert_eq!(
7142 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7143 "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}",
7144 );
7145 buffer_1.update(cx, |buffer, _| {
7146 assert!(!buffer.is_dirty());
7147 assert_eq!(
7148 buffer.text(),
7149 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7150 )
7151 });
7152 buffer_2.update(cx, |buffer, _| {
7153 assert!(!buffer.is_dirty());
7154 assert_eq!(
7155 buffer.text(),
7156 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7157 )
7158 });
7159 buffer_3.update(cx, |buffer, _| {
7160 assert!(!buffer.is_dirty());
7161 assert_eq!(buffer.text(), sample_text_3,)
7162 });
7163}
7164
7165#[gpui::test]
7166async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7167 init_test(cx, |_| {});
7168
7169 let fs = FakeFs::new(cx.executor());
7170 fs.insert_file("/file.rs", Default::default()).await;
7171
7172 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7173
7174 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7175 language_registry.add(rust_lang());
7176 let mut fake_servers = language_registry.register_fake_lsp(
7177 "Rust",
7178 FakeLspAdapter {
7179 capabilities: lsp::ServerCapabilities {
7180 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7181 ..Default::default()
7182 },
7183 ..Default::default()
7184 },
7185 );
7186
7187 let buffer = project
7188 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7189 .await
7190 .unwrap();
7191
7192 cx.executor().start_waiting();
7193 let fake_server = fake_servers.next().await.unwrap();
7194
7195 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7196 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7197 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7198 assert!(cx.read(|cx| editor.is_dirty(cx)));
7199
7200 let save = editor
7201 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7202 .unwrap();
7203 fake_server
7204 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7205 assert_eq!(
7206 params.text_document.uri,
7207 lsp::Url::from_file_path("/file.rs").unwrap()
7208 );
7209 assert_eq!(params.options.tab_size, 4);
7210 Ok(Some(vec![lsp::TextEdit::new(
7211 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7212 ", ".to_string(),
7213 )]))
7214 })
7215 .next()
7216 .await;
7217 cx.executor().start_waiting();
7218 save.await;
7219 assert_eq!(
7220 editor.update(cx, |editor, cx| editor.text(cx)),
7221 "one, two\nthree\n"
7222 );
7223 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7224
7225 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7226 assert!(cx.read(|cx| editor.is_dirty(cx)));
7227
7228 // Ensure we can still save even if formatting hangs.
7229 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7230 move |params, _| async move {
7231 assert_eq!(
7232 params.text_document.uri,
7233 lsp::Url::from_file_path("/file.rs").unwrap()
7234 );
7235 futures::future::pending::<()>().await;
7236 unreachable!()
7237 },
7238 );
7239 let save = editor
7240 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7241 .unwrap();
7242 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7243 cx.executor().start_waiting();
7244 save.await;
7245 assert_eq!(
7246 editor.update(cx, |editor, cx| editor.text(cx)),
7247 "one\ntwo\nthree\n"
7248 );
7249 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7250
7251 // For non-dirty buffer, no formatting request should be sent
7252 let save = editor
7253 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7254 .unwrap();
7255 let _pending_format_request = fake_server
7256 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7257 panic!("Should not be invoked on non-dirty buffer");
7258 })
7259 .next();
7260 cx.executor().start_waiting();
7261 save.await;
7262
7263 // Set Rust language override and assert overridden tabsize is sent to language server
7264 update_test_language_settings(cx, |settings| {
7265 settings.languages.insert(
7266 "Rust".into(),
7267 LanguageSettingsContent {
7268 tab_size: NonZeroU32::new(8),
7269 ..Default::default()
7270 },
7271 );
7272 });
7273
7274 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7275 assert!(cx.read(|cx| editor.is_dirty(cx)));
7276 let save = editor
7277 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7278 .unwrap();
7279 fake_server
7280 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7281 assert_eq!(
7282 params.text_document.uri,
7283 lsp::Url::from_file_path("/file.rs").unwrap()
7284 );
7285 assert_eq!(params.options.tab_size, 8);
7286 Ok(Some(vec![]))
7287 })
7288 .next()
7289 .await;
7290 cx.executor().start_waiting();
7291 save.await;
7292}
7293
7294#[gpui::test]
7295async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7296 init_test(cx, |settings| {
7297 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7298 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7299 ))
7300 });
7301
7302 let fs = FakeFs::new(cx.executor());
7303 fs.insert_file("/file.rs", Default::default()).await;
7304
7305 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7306
7307 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7308 language_registry.add(Arc::new(Language::new(
7309 LanguageConfig {
7310 name: "Rust".into(),
7311 matcher: LanguageMatcher {
7312 path_suffixes: vec!["rs".to_string()],
7313 ..Default::default()
7314 },
7315 ..LanguageConfig::default()
7316 },
7317 Some(tree_sitter_rust::LANGUAGE.into()),
7318 )));
7319 update_test_language_settings(cx, |settings| {
7320 // Enable Prettier formatting for the same buffer, and ensure
7321 // LSP is called instead of Prettier.
7322 settings.defaults.prettier = Some(PrettierSettings {
7323 allowed: true,
7324 ..PrettierSettings::default()
7325 });
7326 });
7327 let mut fake_servers = language_registry.register_fake_lsp(
7328 "Rust",
7329 FakeLspAdapter {
7330 capabilities: lsp::ServerCapabilities {
7331 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7332 ..Default::default()
7333 },
7334 ..Default::default()
7335 },
7336 );
7337
7338 let buffer = project
7339 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7340 .await
7341 .unwrap();
7342
7343 cx.executor().start_waiting();
7344 let fake_server = fake_servers.next().await.unwrap();
7345
7346 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7347 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7348 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7349
7350 let format = editor
7351 .update(cx, |editor, cx| {
7352 editor.perform_format(
7353 project.clone(),
7354 FormatTrigger::Manual,
7355 FormatTarget::Buffer,
7356 cx,
7357 )
7358 })
7359 .unwrap();
7360 fake_server
7361 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7362 assert_eq!(
7363 params.text_document.uri,
7364 lsp::Url::from_file_path("/file.rs").unwrap()
7365 );
7366 assert_eq!(params.options.tab_size, 4);
7367 Ok(Some(vec![lsp::TextEdit::new(
7368 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7369 ", ".to_string(),
7370 )]))
7371 })
7372 .next()
7373 .await;
7374 cx.executor().start_waiting();
7375 format.await;
7376 assert_eq!(
7377 editor.update(cx, |editor, cx| editor.text(cx)),
7378 "one, two\nthree\n"
7379 );
7380
7381 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7382 // Ensure we don't lock if formatting hangs.
7383 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7384 assert_eq!(
7385 params.text_document.uri,
7386 lsp::Url::from_file_path("/file.rs").unwrap()
7387 );
7388 futures::future::pending::<()>().await;
7389 unreachable!()
7390 });
7391 let format = editor
7392 .update(cx, |editor, cx| {
7393 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7394 })
7395 .unwrap();
7396 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7397 cx.executor().start_waiting();
7398 format.await;
7399 assert_eq!(
7400 editor.update(cx, |editor, cx| editor.text(cx)),
7401 "one\ntwo\nthree\n"
7402 );
7403}
7404
7405#[gpui::test]
7406async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7407 init_test(cx, |_| {});
7408
7409 let mut cx = EditorLspTestContext::new_rust(
7410 lsp::ServerCapabilities {
7411 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7412 ..Default::default()
7413 },
7414 cx,
7415 )
7416 .await;
7417
7418 cx.set_state(indoc! {"
7419 one.twoˇ
7420 "});
7421
7422 // The format request takes a long time. When it completes, it inserts
7423 // a newline and an indent before the `.`
7424 cx.lsp
7425 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7426 let executor = cx.background_executor().clone();
7427 async move {
7428 executor.timer(Duration::from_millis(100)).await;
7429 Ok(Some(vec![lsp::TextEdit {
7430 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7431 new_text: "\n ".into(),
7432 }]))
7433 }
7434 });
7435
7436 // Submit a format request.
7437 let format_1 = cx
7438 .update_editor(|editor, cx| editor.format(&Format, cx))
7439 .unwrap();
7440 cx.executor().run_until_parked();
7441
7442 // Submit a second format request.
7443 let format_2 = cx
7444 .update_editor(|editor, cx| editor.format(&Format, cx))
7445 .unwrap();
7446 cx.executor().run_until_parked();
7447
7448 // Wait for both format requests to complete
7449 cx.executor().advance_clock(Duration::from_millis(200));
7450 cx.executor().start_waiting();
7451 format_1.await.unwrap();
7452 cx.executor().start_waiting();
7453 format_2.await.unwrap();
7454
7455 // The formatting edits only happens once.
7456 cx.assert_editor_state(indoc! {"
7457 one
7458 .twoˇ
7459 "});
7460}
7461
7462#[gpui::test]
7463async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7464 init_test(cx, |settings| {
7465 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7466 });
7467
7468 let mut cx = EditorLspTestContext::new_rust(
7469 lsp::ServerCapabilities {
7470 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7471 ..Default::default()
7472 },
7473 cx,
7474 )
7475 .await;
7476
7477 // Set up a buffer white some trailing whitespace and no trailing newline.
7478 cx.set_state(
7479 &[
7480 "one ", //
7481 "twoˇ", //
7482 "three ", //
7483 "four", //
7484 ]
7485 .join("\n"),
7486 );
7487
7488 // Submit a format request.
7489 let format = cx
7490 .update_editor(|editor, cx| editor.format(&Format, cx))
7491 .unwrap();
7492
7493 // Record which buffer changes have been sent to the language server
7494 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7495 cx.lsp
7496 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7497 let buffer_changes = buffer_changes.clone();
7498 move |params, _| {
7499 buffer_changes.lock().extend(
7500 params
7501 .content_changes
7502 .into_iter()
7503 .map(|e| (e.range.unwrap(), e.text)),
7504 );
7505 }
7506 });
7507
7508 // Handle formatting requests to the language server.
7509 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7510 let buffer_changes = buffer_changes.clone();
7511 move |_, _| {
7512 // When formatting is requested, trailing whitespace has already been stripped,
7513 // and the trailing newline has already been added.
7514 assert_eq!(
7515 &buffer_changes.lock()[1..],
7516 &[
7517 (
7518 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7519 "".into()
7520 ),
7521 (
7522 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7523 "".into()
7524 ),
7525 (
7526 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7527 "\n".into()
7528 ),
7529 ]
7530 );
7531
7532 // Insert blank lines between each line of the buffer.
7533 async move {
7534 Ok(Some(vec![
7535 lsp::TextEdit {
7536 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7537 new_text: "\n".into(),
7538 },
7539 lsp::TextEdit {
7540 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7541 new_text: "\n".into(),
7542 },
7543 ]))
7544 }
7545 }
7546 });
7547
7548 // After formatting the buffer, the trailing whitespace is stripped,
7549 // a newline is appended, and the edits provided by the language server
7550 // have been applied.
7551 format.await.unwrap();
7552 cx.assert_editor_state(
7553 &[
7554 "one", //
7555 "", //
7556 "twoˇ", //
7557 "", //
7558 "three", //
7559 "four", //
7560 "", //
7561 ]
7562 .join("\n"),
7563 );
7564
7565 // Undoing the formatting undoes the trailing whitespace removal, the
7566 // trailing newline, and the LSP edits.
7567 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7568 cx.assert_editor_state(
7569 &[
7570 "one ", //
7571 "twoˇ", //
7572 "three ", //
7573 "four", //
7574 ]
7575 .join("\n"),
7576 );
7577}
7578
7579#[gpui::test]
7580async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7581 cx: &mut gpui::TestAppContext,
7582) {
7583 init_test(cx, |_| {});
7584
7585 cx.update(|cx| {
7586 cx.update_global::<SettingsStore, _>(|settings, cx| {
7587 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7588 settings.auto_signature_help = Some(true);
7589 });
7590 });
7591 });
7592
7593 let mut cx = EditorLspTestContext::new_rust(
7594 lsp::ServerCapabilities {
7595 signature_help_provider: Some(lsp::SignatureHelpOptions {
7596 ..Default::default()
7597 }),
7598 ..Default::default()
7599 },
7600 cx,
7601 )
7602 .await;
7603
7604 let language = Language::new(
7605 LanguageConfig {
7606 name: "Rust".into(),
7607 brackets: BracketPairConfig {
7608 pairs: vec![
7609 BracketPair {
7610 start: "{".to_string(),
7611 end: "}".to_string(),
7612 close: true,
7613 surround: true,
7614 newline: true,
7615 },
7616 BracketPair {
7617 start: "(".to_string(),
7618 end: ")".to_string(),
7619 close: true,
7620 surround: true,
7621 newline: true,
7622 },
7623 BracketPair {
7624 start: "/*".to_string(),
7625 end: " */".to_string(),
7626 close: true,
7627 surround: true,
7628 newline: true,
7629 },
7630 BracketPair {
7631 start: "[".to_string(),
7632 end: "]".to_string(),
7633 close: false,
7634 surround: false,
7635 newline: true,
7636 },
7637 BracketPair {
7638 start: "\"".to_string(),
7639 end: "\"".to_string(),
7640 close: true,
7641 surround: true,
7642 newline: false,
7643 },
7644 BracketPair {
7645 start: "<".to_string(),
7646 end: ">".to_string(),
7647 close: false,
7648 surround: true,
7649 newline: true,
7650 },
7651 ],
7652 ..Default::default()
7653 },
7654 autoclose_before: "})]".to_string(),
7655 ..Default::default()
7656 },
7657 Some(tree_sitter_rust::LANGUAGE.into()),
7658 );
7659 let language = Arc::new(language);
7660
7661 cx.language_registry().add(language.clone());
7662 cx.update_buffer(|buffer, cx| {
7663 buffer.set_language(Some(language), cx);
7664 });
7665
7666 cx.set_state(
7667 &r#"
7668 fn main() {
7669 sampleˇ
7670 }
7671 "#
7672 .unindent(),
7673 );
7674
7675 cx.update_editor(|view, cx| {
7676 view.handle_input("(", cx);
7677 });
7678 cx.assert_editor_state(
7679 &"
7680 fn main() {
7681 sample(ˇ)
7682 }
7683 "
7684 .unindent(),
7685 );
7686
7687 let mocked_response = lsp::SignatureHelp {
7688 signatures: vec![lsp::SignatureInformation {
7689 label: "fn sample(param1: u8, param2: u8)".to_string(),
7690 documentation: None,
7691 parameters: Some(vec![
7692 lsp::ParameterInformation {
7693 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7694 documentation: None,
7695 },
7696 lsp::ParameterInformation {
7697 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7698 documentation: None,
7699 },
7700 ]),
7701 active_parameter: None,
7702 }],
7703 active_signature: Some(0),
7704 active_parameter: Some(0),
7705 };
7706 handle_signature_help_request(&mut cx, mocked_response).await;
7707
7708 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7709 .await;
7710
7711 cx.editor(|editor, _| {
7712 let signature_help_state = editor.signature_help_state.popover().cloned();
7713 assert!(signature_help_state.is_some());
7714 let ParsedMarkdown {
7715 text, highlights, ..
7716 } = signature_help_state.unwrap().parsed_content;
7717 assert_eq!(text, "param1: u8, param2: u8");
7718 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7719 });
7720}
7721
7722#[gpui::test]
7723async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7724 init_test(cx, |_| {});
7725
7726 cx.update(|cx| {
7727 cx.update_global::<SettingsStore, _>(|settings, cx| {
7728 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7729 settings.auto_signature_help = Some(false);
7730 settings.show_signature_help_after_edits = Some(false);
7731 });
7732 });
7733 });
7734
7735 let mut cx = EditorLspTestContext::new_rust(
7736 lsp::ServerCapabilities {
7737 signature_help_provider: Some(lsp::SignatureHelpOptions {
7738 ..Default::default()
7739 }),
7740 ..Default::default()
7741 },
7742 cx,
7743 )
7744 .await;
7745
7746 let language = Language::new(
7747 LanguageConfig {
7748 name: "Rust".into(),
7749 brackets: BracketPairConfig {
7750 pairs: vec![
7751 BracketPair {
7752 start: "{".to_string(),
7753 end: "}".to_string(),
7754 close: true,
7755 surround: true,
7756 newline: true,
7757 },
7758 BracketPair {
7759 start: "(".to_string(),
7760 end: ")".to_string(),
7761 close: true,
7762 surround: true,
7763 newline: true,
7764 },
7765 BracketPair {
7766 start: "/*".to_string(),
7767 end: " */".to_string(),
7768 close: true,
7769 surround: true,
7770 newline: true,
7771 },
7772 BracketPair {
7773 start: "[".to_string(),
7774 end: "]".to_string(),
7775 close: false,
7776 surround: false,
7777 newline: true,
7778 },
7779 BracketPair {
7780 start: "\"".to_string(),
7781 end: "\"".to_string(),
7782 close: true,
7783 surround: true,
7784 newline: false,
7785 },
7786 BracketPair {
7787 start: "<".to_string(),
7788 end: ">".to_string(),
7789 close: false,
7790 surround: true,
7791 newline: true,
7792 },
7793 ],
7794 ..Default::default()
7795 },
7796 autoclose_before: "})]".to_string(),
7797 ..Default::default()
7798 },
7799 Some(tree_sitter_rust::LANGUAGE.into()),
7800 );
7801 let language = Arc::new(language);
7802
7803 cx.language_registry().add(language.clone());
7804 cx.update_buffer(|buffer, cx| {
7805 buffer.set_language(Some(language), cx);
7806 });
7807
7808 // Ensure that signature_help is not called when no signature help is enabled.
7809 cx.set_state(
7810 &r#"
7811 fn main() {
7812 sampleˇ
7813 }
7814 "#
7815 .unindent(),
7816 );
7817 cx.update_editor(|view, cx| {
7818 view.handle_input("(", cx);
7819 });
7820 cx.assert_editor_state(
7821 &"
7822 fn main() {
7823 sample(ˇ)
7824 }
7825 "
7826 .unindent(),
7827 );
7828 cx.editor(|editor, _| {
7829 assert!(editor.signature_help_state.task().is_none());
7830 });
7831
7832 let mocked_response = lsp::SignatureHelp {
7833 signatures: vec![lsp::SignatureInformation {
7834 label: "fn sample(param1: u8, param2: u8)".to_string(),
7835 documentation: None,
7836 parameters: Some(vec![
7837 lsp::ParameterInformation {
7838 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7839 documentation: None,
7840 },
7841 lsp::ParameterInformation {
7842 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7843 documentation: None,
7844 },
7845 ]),
7846 active_parameter: None,
7847 }],
7848 active_signature: Some(0),
7849 active_parameter: Some(0),
7850 };
7851
7852 // Ensure that signature_help is called when enabled afte edits
7853 cx.update(|cx| {
7854 cx.update_global::<SettingsStore, _>(|settings, cx| {
7855 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7856 settings.auto_signature_help = Some(false);
7857 settings.show_signature_help_after_edits = Some(true);
7858 });
7859 });
7860 });
7861 cx.set_state(
7862 &r#"
7863 fn main() {
7864 sampleˇ
7865 }
7866 "#
7867 .unindent(),
7868 );
7869 cx.update_editor(|view, cx| {
7870 view.handle_input("(", cx);
7871 });
7872 cx.assert_editor_state(
7873 &"
7874 fn main() {
7875 sample(ˇ)
7876 }
7877 "
7878 .unindent(),
7879 );
7880 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7881 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7882 .await;
7883 cx.update_editor(|editor, _| {
7884 let signature_help_state = editor.signature_help_state.popover().cloned();
7885 assert!(signature_help_state.is_some());
7886 let ParsedMarkdown {
7887 text, highlights, ..
7888 } = signature_help_state.unwrap().parsed_content;
7889 assert_eq!(text, "param1: u8, param2: u8");
7890 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7891 editor.signature_help_state = SignatureHelpState::default();
7892 });
7893
7894 // Ensure that signature_help is called when auto signature help override is enabled
7895 cx.update(|cx| {
7896 cx.update_global::<SettingsStore, _>(|settings, cx| {
7897 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7898 settings.auto_signature_help = Some(true);
7899 settings.show_signature_help_after_edits = Some(false);
7900 });
7901 });
7902 });
7903 cx.set_state(
7904 &r#"
7905 fn main() {
7906 sampleˇ
7907 }
7908 "#
7909 .unindent(),
7910 );
7911 cx.update_editor(|view, cx| {
7912 view.handle_input("(", cx);
7913 });
7914 cx.assert_editor_state(
7915 &"
7916 fn main() {
7917 sample(ˇ)
7918 }
7919 "
7920 .unindent(),
7921 );
7922 handle_signature_help_request(&mut cx, mocked_response).await;
7923 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7924 .await;
7925 cx.editor(|editor, _| {
7926 let signature_help_state = editor.signature_help_state.popover().cloned();
7927 assert!(signature_help_state.is_some());
7928 let ParsedMarkdown {
7929 text, highlights, ..
7930 } = signature_help_state.unwrap().parsed_content;
7931 assert_eq!(text, "param1: u8, param2: u8");
7932 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7933 });
7934}
7935
7936#[gpui::test]
7937async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7938 init_test(cx, |_| {});
7939 cx.update(|cx| {
7940 cx.update_global::<SettingsStore, _>(|settings, cx| {
7941 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7942 settings.auto_signature_help = Some(true);
7943 });
7944 });
7945 });
7946
7947 let mut cx = EditorLspTestContext::new_rust(
7948 lsp::ServerCapabilities {
7949 signature_help_provider: Some(lsp::SignatureHelpOptions {
7950 ..Default::default()
7951 }),
7952 ..Default::default()
7953 },
7954 cx,
7955 )
7956 .await;
7957
7958 // A test that directly calls `show_signature_help`
7959 cx.update_editor(|editor, cx| {
7960 editor.show_signature_help(&ShowSignatureHelp, cx);
7961 });
7962
7963 let mocked_response = lsp::SignatureHelp {
7964 signatures: vec![lsp::SignatureInformation {
7965 label: "fn sample(param1: u8, param2: u8)".to_string(),
7966 documentation: None,
7967 parameters: Some(vec![
7968 lsp::ParameterInformation {
7969 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7970 documentation: None,
7971 },
7972 lsp::ParameterInformation {
7973 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7974 documentation: None,
7975 },
7976 ]),
7977 active_parameter: None,
7978 }],
7979 active_signature: Some(0),
7980 active_parameter: Some(0),
7981 };
7982 handle_signature_help_request(&mut cx, mocked_response).await;
7983
7984 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7985 .await;
7986
7987 cx.editor(|editor, _| {
7988 let signature_help_state = editor.signature_help_state.popover().cloned();
7989 assert!(signature_help_state.is_some());
7990 let ParsedMarkdown {
7991 text, highlights, ..
7992 } = signature_help_state.unwrap().parsed_content;
7993 assert_eq!(text, "param1: u8, param2: u8");
7994 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7995 });
7996
7997 // When exiting outside from inside the brackets, `signature_help` is closed.
7998 cx.set_state(indoc! {"
7999 fn main() {
8000 sample(ˇ);
8001 }
8002
8003 fn sample(param1: u8, param2: u8) {}
8004 "});
8005
8006 cx.update_editor(|editor, cx| {
8007 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8008 });
8009
8010 let mocked_response = lsp::SignatureHelp {
8011 signatures: Vec::new(),
8012 active_signature: None,
8013 active_parameter: None,
8014 };
8015 handle_signature_help_request(&mut cx, mocked_response).await;
8016
8017 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8018 .await;
8019
8020 cx.editor(|editor, _| {
8021 assert!(!editor.signature_help_state.is_shown());
8022 });
8023
8024 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8025 cx.set_state(indoc! {"
8026 fn main() {
8027 sample(ˇ);
8028 }
8029
8030 fn sample(param1: u8, param2: u8) {}
8031 "});
8032
8033 let mocked_response = lsp::SignatureHelp {
8034 signatures: vec![lsp::SignatureInformation {
8035 label: "fn sample(param1: u8, param2: u8)".to_string(),
8036 documentation: None,
8037 parameters: Some(vec![
8038 lsp::ParameterInformation {
8039 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8040 documentation: None,
8041 },
8042 lsp::ParameterInformation {
8043 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8044 documentation: None,
8045 },
8046 ]),
8047 active_parameter: None,
8048 }],
8049 active_signature: Some(0),
8050 active_parameter: Some(0),
8051 };
8052 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8053 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8054 .await;
8055 cx.editor(|editor, _| {
8056 assert!(editor.signature_help_state.is_shown());
8057 });
8058
8059 // Restore the popover with more parameter input
8060 cx.set_state(indoc! {"
8061 fn main() {
8062 sample(param1, param2ˇ);
8063 }
8064
8065 fn sample(param1: u8, param2: u8) {}
8066 "});
8067
8068 let mocked_response = lsp::SignatureHelp {
8069 signatures: vec![lsp::SignatureInformation {
8070 label: "fn sample(param1: u8, param2: u8)".to_string(),
8071 documentation: None,
8072 parameters: Some(vec![
8073 lsp::ParameterInformation {
8074 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8075 documentation: None,
8076 },
8077 lsp::ParameterInformation {
8078 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8079 documentation: None,
8080 },
8081 ]),
8082 active_parameter: None,
8083 }],
8084 active_signature: Some(0),
8085 active_parameter: Some(1),
8086 };
8087 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8088 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8089 .await;
8090
8091 // When selecting a range, the popover is gone.
8092 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8093 cx.update_editor(|editor, cx| {
8094 editor.change_selections(None, cx, |s| {
8095 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8096 })
8097 });
8098 cx.assert_editor_state(indoc! {"
8099 fn main() {
8100 sample(param1, «ˇparam2»);
8101 }
8102
8103 fn sample(param1: u8, param2: u8) {}
8104 "});
8105 cx.editor(|editor, _| {
8106 assert!(!editor.signature_help_state.is_shown());
8107 });
8108
8109 // When unselecting again, the popover is back if within the brackets.
8110 cx.update_editor(|editor, cx| {
8111 editor.change_selections(None, cx, |s| {
8112 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8113 })
8114 });
8115 cx.assert_editor_state(indoc! {"
8116 fn main() {
8117 sample(param1, ˇparam2);
8118 }
8119
8120 fn sample(param1: u8, param2: u8) {}
8121 "});
8122 handle_signature_help_request(&mut cx, mocked_response).await;
8123 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8124 .await;
8125 cx.editor(|editor, _| {
8126 assert!(editor.signature_help_state.is_shown());
8127 });
8128
8129 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8130 cx.update_editor(|editor, cx| {
8131 editor.change_selections(None, cx, |s| {
8132 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8133 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8134 })
8135 });
8136 cx.assert_editor_state(indoc! {"
8137 fn main() {
8138 sample(param1, ˇparam2);
8139 }
8140
8141 fn sample(param1: u8, param2: u8) {}
8142 "});
8143
8144 let mocked_response = lsp::SignatureHelp {
8145 signatures: vec![lsp::SignatureInformation {
8146 label: "fn sample(param1: u8, param2: u8)".to_string(),
8147 documentation: None,
8148 parameters: Some(vec![
8149 lsp::ParameterInformation {
8150 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8151 documentation: None,
8152 },
8153 lsp::ParameterInformation {
8154 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8155 documentation: None,
8156 },
8157 ]),
8158 active_parameter: None,
8159 }],
8160 active_signature: Some(0),
8161 active_parameter: Some(1),
8162 };
8163 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8164 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8165 .await;
8166 cx.update_editor(|editor, cx| {
8167 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8168 });
8169 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8170 .await;
8171 cx.update_editor(|editor, cx| {
8172 editor.change_selections(None, cx, |s| {
8173 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8174 })
8175 });
8176 cx.assert_editor_state(indoc! {"
8177 fn main() {
8178 sample(param1, «ˇparam2»);
8179 }
8180
8181 fn sample(param1: u8, param2: u8) {}
8182 "});
8183 cx.update_editor(|editor, cx| {
8184 editor.change_selections(None, cx, |s| {
8185 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8186 })
8187 });
8188 cx.assert_editor_state(indoc! {"
8189 fn main() {
8190 sample(param1, ˇparam2);
8191 }
8192
8193 fn sample(param1: u8, param2: u8) {}
8194 "});
8195 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8196 .await;
8197}
8198
8199#[gpui::test]
8200async fn test_completion(cx: &mut gpui::TestAppContext) {
8201 init_test(cx, |_| {});
8202
8203 let mut cx = EditorLspTestContext::new_rust(
8204 lsp::ServerCapabilities {
8205 completion_provider: Some(lsp::CompletionOptions {
8206 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8207 resolve_provider: Some(true),
8208 ..Default::default()
8209 }),
8210 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8211 ..Default::default()
8212 },
8213 cx,
8214 )
8215 .await;
8216 let counter = Arc::new(AtomicUsize::new(0));
8217
8218 cx.set_state(indoc! {"
8219 oneˇ
8220 two
8221 three
8222 "});
8223 cx.simulate_keystroke(".");
8224 handle_completion_request(
8225 &mut cx,
8226 indoc! {"
8227 one.|<>
8228 two
8229 three
8230 "},
8231 vec!["first_completion", "second_completion"],
8232 counter.clone(),
8233 )
8234 .await;
8235 cx.condition(|editor, _| editor.context_menu_visible())
8236 .await;
8237 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8238
8239 let _handler = handle_signature_help_request(
8240 &mut cx,
8241 lsp::SignatureHelp {
8242 signatures: vec![lsp::SignatureInformation {
8243 label: "test signature".to_string(),
8244 documentation: None,
8245 parameters: Some(vec![lsp::ParameterInformation {
8246 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8247 documentation: None,
8248 }]),
8249 active_parameter: None,
8250 }],
8251 active_signature: None,
8252 active_parameter: None,
8253 },
8254 );
8255 cx.update_editor(|editor, cx| {
8256 assert!(
8257 !editor.signature_help_state.is_shown(),
8258 "No signature help was called for"
8259 );
8260 editor.show_signature_help(&ShowSignatureHelp, cx);
8261 });
8262 cx.run_until_parked();
8263 cx.update_editor(|editor, _| {
8264 assert!(
8265 !editor.signature_help_state.is_shown(),
8266 "No signature help should be shown when completions menu is open"
8267 );
8268 });
8269
8270 let apply_additional_edits = cx.update_editor(|editor, cx| {
8271 editor.context_menu_next(&Default::default(), cx);
8272 editor
8273 .confirm_completion(&ConfirmCompletion::default(), cx)
8274 .unwrap()
8275 });
8276 cx.assert_editor_state(indoc! {"
8277 one.second_completionˇ
8278 two
8279 three
8280 "});
8281
8282 handle_resolve_completion_request(
8283 &mut cx,
8284 Some(vec![
8285 (
8286 //This overlaps with the primary completion edit which is
8287 //misbehavior from the LSP spec, test that we filter it out
8288 indoc! {"
8289 one.second_ˇcompletion
8290 two
8291 threeˇ
8292 "},
8293 "overlapping additional edit",
8294 ),
8295 (
8296 indoc! {"
8297 one.second_completion
8298 two
8299 threeˇ
8300 "},
8301 "\nadditional edit",
8302 ),
8303 ]),
8304 )
8305 .await;
8306 apply_additional_edits.await.unwrap();
8307 cx.assert_editor_state(indoc! {"
8308 one.second_completionˇ
8309 two
8310 three
8311 additional edit
8312 "});
8313
8314 cx.set_state(indoc! {"
8315 one.second_completion
8316 twoˇ
8317 threeˇ
8318 additional edit
8319 "});
8320 cx.simulate_keystroke(" ");
8321 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8322 cx.simulate_keystroke("s");
8323 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8324
8325 cx.assert_editor_state(indoc! {"
8326 one.second_completion
8327 two sˇ
8328 three sˇ
8329 additional edit
8330 "});
8331 handle_completion_request(
8332 &mut cx,
8333 indoc! {"
8334 one.second_completion
8335 two s
8336 three <s|>
8337 additional edit
8338 "},
8339 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8340 counter.clone(),
8341 )
8342 .await;
8343 cx.condition(|editor, _| editor.context_menu_visible())
8344 .await;
8345 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8346
8347 cx.simulate_keystroke("i");
8348
8349 handle_completion_request(
8350 &mut cx,
8351 indoc! {"
8352 one.second_completion
8353 two si
8354 three <si|>
8355 additional edit
8356 "},
8357 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8358 counter.clone(),
8359 )
8360 .await;
8361 cx.condition(|editor, _| editor.context_menu_visible())
8362 .await;
8363 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8364
8365 let apply_additional_edits = cx.update_editor(|editor, cx| {
8366 editor
8367 .confirm_completion(&ConfirmCompletion::default(), cx)
8368 .unwrap()
8369 });
8370 cx.assert_editor_state(indoc! {"
8371 one.second_completion
8372 two sixth_completionˇ
8373 three sixth_completionˇ
8374 additional edit
8375 "});
8376
8377 handle_resolve_completion_request(&mut cx, None).await;
8378 apply_additional_edits.await.unwrap();
8379
8380 cx.update(|cx| {
8381 cx.update_global::<SettingsStore, _>(|settings, cx| {
8382 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8383 settings.show_completions_on_input = Some(false);
8384 });
8385 })
8386 });
8387 cx.set_state("editorˇ");
8388 cx.simulate_keystroke(".");
8389 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8390 cx.simulate_keystroke("c");
8391 cx.simulate_keystroke("l");
8392 cx.simulate_keystroke("o");
8393 cx.assert_editor_state("editor.cloˇ");
8394 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8395 cx.update_editor(|editor, cx| {
8396 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8397 });
8398 handle_completion_request(
8399 &mut cx,
8400 "editor.<clo|>",
8401 vec!["close", "clobber"],
8402 counter.clone(),
8403 )
8404 .await;
8405 cx.condition(|editor, _| editor.context_menu_visible())
8406 .await;
8407 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8408
8409 let apply_additional_edits = cx.update_editor(|editor, cx| {
8410 editor
8411 .confirm_completion(&ConfirmCompletion::default(), cx)
8412 .unwrap()
8413 });
8414 cx.assert_editor_state("editor.closeˇ");
8415 handle_resolve_completion_request(&mut cx, None).await;
8416 apply_additional_edits.await.unwrap();
8417}
8418
8419#[gpui::test]
8420async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8421 init_test(cx, |_| {});
8422 let mut cx = EditorLspTestContext::new_rust(
8423 lsp::ServerCapabilities {
8424 completion_provider: Some(lsp::CompletionOptions {
8425 trigger_characters: Some(vec![".".to_string()]),
8426 ..Default::default()
8427 }),
8428 ..Default::default()
8429 },
8430 cx,
8431 )
8432 .await;
8433 cx.lsp
8434 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8435 Ok(Some(lsp::CompletionResponse::Array(vec![
8436 lsp::CompletionItem {
8437 label: "first".into(),
8438 ..Default::default()
8439 },
8440 lsp::CompletionItem {
8441 label: "last".into(),
8442 ..Default::default()
8443 },
8444 ])))
8445 });
8446 cx.set_state("variableˇ");
8447 cx.simulate_keystroke(".");
8448 cx.executor().run_until_parked();
8449
8450 cx.update_editor(|editor, _| {
8451 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8452 assert_eq!(
8453 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8454 &["first", "last"]
8455 );
8456 } else {
8457 panic!("expected completion menu to be open");
8458 }
8459 });
8460
8461 cx.update_editor(|editor, cx| {
8462 editor.move_page_down(&MovePageDown::default(), cx);
8463 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8464 assert!(
8465 menu.selected_item == 1,
8466 "expected PageDown to select the last item from the context menu"
8467 );
8468 } else {
8469 panic!("expected completion menu to stay open after PageDown");
8470 }
8471 });
8472
8473 cx.update_editor(|editor, cx| {
8474 editor.move_page_up(&MovePageUp::default(), cx);
8475 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8476 assert!(
8477 menu.selected_item == 0,
8478 "expected PageUp to select the first item from the context menu"
8479 );
8480 } else {
8481 panic!("expected completion menu to stay open after PageUp");
8482 }
8483 });
8484}
8485
8486#[gpui::test]
8487async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8488 init_test(cx, |_| {});
8489 let mut cx = EditorLspTestContext::new_rust(
8490 lsp::ServerCapabilities {
8491 completion_provider: Some(lsp::CompletionOptions {
8492 trigger_characters: Some(vec![".".to_string()]),
8493 ..Default::default()
8494 }),
8495 ..Default::default()
8496 },
8497 cx,
8498 )
8499 .await;
8500 cx.lsp
8501 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8502 Ok(Some(lsp::CompletionResponse::Array(vec![
8503 lsp::CompletionItem {
8504 label: "Range".into(),
8505 sort_text: Some("a".into()),
8506 ..Default::default()
8507 },
8508 lsp::CompletionItem {
8509 label: "r".into(),
8510 sort_text: Some("b".into()),
8511 ..Default::default()
8512 },
8513 lsp::CompletionItem {
8514 label: "ret".into(),
8515 sort_text: Some("c".into()),
8516 ..Default::default()
8517 },
8518 lsp::CompletionItem {
8519 label: "return".into(),
8520 sort_text: Some("d".into()),
8521 ..Default::default()
8522 },
8523 lsp::CompletionItem {
8524 label: "slice".into(),
8525 sort_text: Some("d".into()),
8526 ..Default::default()
8527 },
8528 ])))
8529 });
8530 cx.set_state("rˇ");
8531 cx.executor().run_until_parked();
8532 cx.update_editor(|editor, cx| {
8533 editor.show_completions(
8534 &ShowCompletions {
8535 trigger: Some("r".into()),
8536 },
8537 cx,
8538 );
8539 });
8540 cx.executor().run_until_parked();
8541
8542 cx.update_editor(|editor, _| {
8543 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8544 assert_eq!(
8545 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8546 &["r", "ret", "Range", "return"]
8547 );
8548 } else {
8549 panic!("expected completion menu to be open");
8550 }
8551 });
8552}
8553
8554#[gpui::test]
8555async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8556 init_test(cx, |_| {});
8557
8558 let mut cx = EditorLspTestContext::new_rust(
8559 lsp::ServerCapabilities {
8560 completion_provider: Some(lsp::CompletionOptions {
8561 trigger_characters: Some(vec![".".to_string()]),
8562 resolve_provider: Some(true),
8563 ..Default::default()
8564 }),
8565 ..Default::default()
8566 },
8567 cx,
8568 )
8569 .await;
8570
8571 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8572 cx.simulate_keystroke(".");
8573 let completion_item = lsp::CompletionItem {
8574 label: "Some".into(),
8575 kind: Some(lsp::CompletionItemKind::SNIPPET),
8576 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8577 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8578 kind: lsp::MarkupKind::Markdown,
8579 value: "```rust\nSome(2)\n```".to_string(),
8580 })),
8581 deprecated: Some(false),
8582 sort_text: Some("Some".to_string()),
8583 filter_text: Some("Some".to_string()),
8584 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8585 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8586 range: lsp::Range {
8587 start: lsp::Position {
8588 line: 0,
8589 character: 22,
8590 },
8591 end: lsp::Position {
8592 line: 0,
8593 character: 22,
8594 },
8595 },
8596 new_text: "Some(2)".to_string(),
8597 })),
8598 additional_text_edits: Some(vec![lsp::TextEdit {
8599 range: lsp::Range {
8600 start: lsp::Position {
8601 line: 0,
8602 character: 20,
8603 },
8604 end: lsp::Position {
8605 line: 0,
8606 character: 22,
8607 },
8608 },
8609 new_text: "".to_string(),
8610 }]),
8611 ..Default::default()
8612 };
8613
8614 let closure_completion_item = completion_item.clone();
8615 let counter = Arc::new(AtomicUsize::new(0));
8616 let counter_clone = counter.clone();
8617 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8618 let task_completion_item = closure_completion_item.clone();
8619 counter_clone.fetch_add(1, atomic::Ordering::Release);
8620 async move {
8621 Ok(Some(lsp::CompletionResponse::Array(vec![
8622 task_completion_item,
8623 ])))
8624 }
8625 });
8626
8627 cx.condition(|editor, _| editor.context_menu_visible())
8628 .await;
8629 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8630 assert!(request.next().await.is_some());
8631 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8632
8633 cx.simulate_keystroke("S");
8634 cx.simulate_keystroke("o");
8635 cx.simulate_keystroke("m");
8636 cx.condition(|editor, _| editor.context_menu_visible())
8637 .await;
8638 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8639 assert!(request.next().await.is_some());
8640 assert!(request.next().await.is_some());
8641 assert!(request.next().await.is_some());
8642 request.close();
8643 assert!(request.next().await.is_none());
8644 assert_eq!(
8645 counter.load(atomic::Ordering::Acquire),
8646 4,
8647 "With the completions menu open, only one LSP request should happen per input"
8648 );
8649}
8650
8651#[gpui::test]
8652async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8653 init_test(cx, |_| {});
8654 let mut cx = EditorTestContext::new(cx).await;
8655 let language = Arc::new(Language::new(
8656 LanguageConfig {
8657 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8658 ..Default::default()
8659 },
8660 Some(tree_sitter_rust::LANGUAGE.into()),
8661 ));
8662 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8663
8664 // If multiple selections intersect a line, the line is only toggled once.
8665 cx.set_state(indoc! {"
8666 fn a() {
8667 «//b();
8668 ˇ»// «c();
8669 //ˇ» d();
8670 }
8671 "});
8672
8673 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8674
8675 cx.assert_editor_state(indoc! {"
8676 fn a() {
8677 «b();
8678 c();
8679 ˇ» d();
8680 }
8681 "});
8682
8683 // The comment prefix is inserted at the same column for every line in a
8684 // selection.
8685 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8686
8687 cx.assert_editor_state(indoc! {"
8688 fn a() {
8689 // «b();
8690 // c();
8691 ˇ»// d();
8692 }
8693 "});
8694
8695 // If a selection ends at the beginning of a line, that line is not toggled.
8696 cx.set_selections_state(indoc! {"
8697 fn a() {
8698 // b();
8699 «// c();
8700 ˇ» // d();
8701 }
8702 "});
8703
8704 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8705
8706 cx.assert_editor_state(indoc! {"
8707 fn a() {
8708 // b();
8709 «c();
8710 ˇ» // d();
8711 }
8712 "});
8713
8714 // If a selection span a single line and is empty, the line is toggled.
8715 cx.set_state(indoc! {"
8716 fn a() {
8717 a();
8718 b();
8719 ˇ
8720 }
8721 "});
8722
8723 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8724
8725 cx.assert_editor_state(indoc! {"
8726 fn a() {
8727 a();
8728 b();
8729 //•ˇ
8730 }
8731 "});
8732
8733 // If a selection span multiple lines, empty lines are not toggled.
8734 cx.set_state(indoc! {"
8735 fn a() {
8736 «a();
8737
8738 c();ˇ»
8739 }
8740 "});
8741
8742 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8743
8744 cx.assert_editor_state(indoc! {"
8745 fn a() {
8746 // «a();
8747
8748 // c();ˇ»
8749 }
8750 "});
8751
8752 // If a selection includes multiple comment prefixes, all lines are uncommented.
8753 cx.set_state(indoc! {"
8754 fn a() {
8755 «// a();
8756 /// b();
8757 //! c();ˇ»
8758 }
8759 "});
8760
8761 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8762
8763 cx.assert_editor_state(indoc! {"
8764 fn a() {
8765 «a();
8766 b();
8767 c();ˇ»
8768 }
8769 "});
8770}
8771
8772#[gpui::test]
8773async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8774 init_test(cx, |_| {});
8775 let mut cx = EditorTestContext::new(cx).await;
8776 let language = Arc::new(Language::new(
8777 LanguageConfig {
8778 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8779 ..Default::default()
8780 },
8781 Some(tree_sitter_rust::LANGUAGE.into()),
8782 ));
8783 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8784
8785 let toggle_comments = &ToggleComments {
8786 advance_downwards: false,
8787 ignore_indent: true,
8788 };
8789
8790 // If multiple selections intersect a line, the line is only toggled once.
8791 cx.set_state(indoc! {"
8792 fn a() {
8793 // «b();
8794 // c();
8795 // ˇ» d();
8796 }
8797 "});
8798
8799 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8800
8801 cx.assert_editor_state(indoc! {"
8802 fn a() {
8803 «b();
8804 c();
8805 ˇ» d();
8806 }
8807 "});
8808
8809 // The comment prefix is inserted at the beginning of each line
8810 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8811
8812 cx.assert_editor_state(indoc! {"
8813 fn a() {
8814 // «b();
8815 // c();
8816 // ˇ» d();
8817 }
8818 "});
8819
8820 // If a selection ends at the beginning of a line, that line is not toggled.
8821 cx.set_selections_state(indoc! {"
8822 fn a() {
8823 // b();
8824 // «c();
8825 ˇ»// d();
8826 }
8827 "});
8828
8829 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8830
8831 cx.assert_editor_state(indoc! {"
8832 fn a() {
8833 // b();
8834 «c();
8835 ˇ»// d();
8836 }
8837 "});
8838
8839 // If a selection span a single line and is empty, the line is toggled.
8840 cx.set_state(indoc! {"
8841 fn a() {
8842 a();
8843 b();
8844 ˇ
8845 }
8846 "});
8847
8848 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8849
8850 cx.assert_editor_state(indoc! {"
8851 fn a() {
8852 a();
8853 b();
8854 //ˇ
8855 }
8856 "});
8857
8858 // If a selection span multiple lines, empty lines are not toggled.
8859 cx.set_state(indoc! {"
8860 fn a() {
8861 «a();
8862
8863 c();ˇ»
8864 }
8865 "});
8866
8867 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8868
8869 cx.assert_editor_state(indoc! {"
8870 fn a() {
8871 // «a();
8872
8873 // c();ˇ»
8874 }
8875 "});
8876
8877 // If a selection includes multiple comment prefixes, all lines are uncommented.
8878 cx.set_state(indoc! {"
8879 fn a() {
8880 // «a();
8881 /// b();
8882 //! c();ˇ»
8883 }
8884 "});
8885
8886 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8887
8888 cx.assert_editor_state(indoc! {"
8889 fn a() {
8890 «a();
8891 b();
8892 c();ˇ»
8893 }
8894 "});
8895}
8896
8897#[gpui::test]
8898async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8899 init_test(cx, |_| {});
8900
8901 let language = Arc::new(Language::new(
8902 LanguageConfig {
8903 line_comments: vec!["// ".into()],
8904 ..Default::default()
8905 },
8906 Some(tree_sitter_rust::LANGUAGE.into()),
8907 ));
8908
8909 let mut cx = EditorTestContext::new(cx).await;
8910
8911 cx.language_registry().add(language.clone());
8912 cx.update_buffer(|buffer, cx| {
8913 buffer.set_language(Some(language), cx);
8914 });
8915
8916 let toggle_comments = &ToggleComments {
8917 advance_downwards: true,
8918 ignore_indent: false,
8919 };
8920
8921 // Single cursor on one line -> advance
8922 // Cursor moves horizontally 3 characters as well on non-blank line
8923 cx.set_state(indoc!(
8924 "fn a() {
8925 ˇdog();
8926 cat();
8927 }"
8928 ));
8929 cx.update_editor(|editor, cx| {
8930 editor.toggle_comments(toggle_comments, cx);
8931 });
8932 cx.assert_editor_state(indoc!(
8933 "fn a() {
8934 // dog();
8935 catˇ();
8936 }"
8937 ));
8938
8939 // Single selection on one line -> don't advance
8940 cx.set_state(indoc!(
8941 "fn a() {
8942 «dog()ˇ»;
8943 cat();
8944 }"
8945 ));
8946 cx.update_editor(|editor, cx| {
8947 editor.toggle_comments(toggle_comments, cx);
8948 });
8949 cx.assert_editor_state(indoc!(
8950 "fn a() {
8951 // «dog()ˇ»;
8952 cat();
8953 }"
8954 ));
8955
8956 // Multiple cursors on one line -> advance
8957 cx.set_state(indoc!(
8958 "fn a() {
8959 ˇdˇog();
8960 cat();
8961 }"
8962 ));
8963 cx.update_editor(|editor, cx| {
8964 editor.toggle_comments(toggle_comments, cx);
8965 });
8966 cx.assert_editor_state(indoc!(
8967 "fn a() {
8968 // dog();
8969 catˇ(ˇ);
8970 }"
8971 ));
8972
8973 // Multiple cursors on one line, with selection -> don't advance
8974 cx.set_state(indoc!(
8975 "fn a() {
8976 ˇdˇog«()ˇ»;
8977 cat();
8978 }"
8979 ));
8980 cx.update_editor(|editor, cx| {
8981 editor.toggle_comments(toggle_comments, cx);
8982 });
8983 cx.assert_editor_state(indoc!(
8984 "fn a() {
8985 // ˇdˇog«()ˇ»;
8986 cat();
8987 }"
8988 ));
8989
8990 // Single cursor on one line -> advance
8991 // Cursor moves to column 0 on blank line
8992 cx.set_state(indoc!(
8993 "fn a() {
8994 ˇdog();
8995
8996 cat();
8997 }"
8998 ));
8999 cx.update_editor(|editor, cx| {
9000 editor.toggle_comments(toggle_comments, cx);
9001 });
9002 cx.assert_editor_state(indoc!(
9003 "fn a() {
9004 // dog();
9005 ˇ
9006 cat();
9007 }"
9008 ));
9009
9010 // Single cursor on one line -> advance
9011 // Cursor starts and ends at column 0
9012 cx.set_state(indoc!(
9013 "fn a() {
9014 ˇ dog();
9015 cat();
9016 }"
9017 ));
9018 cx.update_editor(|editor, cx| {
9019 editor.toggle_comments(toggle_comments, cx);
9020 });
9021 cx.assert_editor_state(indoc!(
9022 "fn a() {
9023 // dog();
9024 ˇ cat();
9025 }"
9026 ));
9027}
9028
9029#[gpui::test]
9030async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9031 init_test(cx, |_| {});
9032
9033 let mut cx = EditorTestContext::new(cx).await;
9034
9035 let html_language = Arc::new(
9036 Language::new(
9037 LanguageConfig {
9038 name: "HTML".into(),
9039 block_comment: Some(("<!-- ".into(), " -->".into())),
9040 ..Default::default()
9041 },
9042 Some(tree_sitter_html::language()),
9043 )
9044 .with_injection_query(
9045 r#"
9046 (script_element
9047 (raw_text) @content
9048 (#set! "language" "javascript"))
9049 "#,
9050 )
9051 .unwrap(),
9052 );
9053
9054 let javascript_language = Arc::new(Language::new(
9055 LanguageConfig {
9056 name: "JavaScript".into(),
9057 line_comments: vec!["// ".into()],
9058 ..Default::default()
9059 },
9060 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9061 ));
9062
9063 cx.language_registry().add(html_language.clone());
9064 cx.language_registry().add(javascript_language.clone());
9065 cx.update_buffer(|buffer, cx| {
9066 buffer.set_language(Some(html_language), cx);
9067 });
9068
9069 // Toggle comments for empty selections
9070 cx.set_state(
9071 &r#"
9072 <p>A</p>ˇ
9073 <p>B</p>ˇ
9074 <p>C</p>ˇ
9075 "#
9076 .unindent(),
9077 );
9078 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9079 cx.assert_editor_state(
9080 &r#"
9081 <!-- <p>A</p>ˇ -->
9082 <!-- <p>B</p>ˇ -->
9083 <!-- <p>C</p>ˇ -->
9084 "#
9085 .unindent(),
9086 );
9087 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9088 cx.assert_editor_state(
9089 &r#"
9090 <p>A</p>ˇ
9091 <p>B</p>ˇ
9092 <p>C</p>ˇ
9093 "#
9094 .unindent(),
9095 );
9096
9097 // Toggle comments for mixture of empty and non-empty selections, where
9098 // multiple selections occupy a given line.
9099 cx.set_state(
9100 &r#"
9101 <p>A«</p>
9102 <p>ˇ»B</p>ˇ
9103 <p>C«</p>
9104 <p>ˇ»D</p>ˇ
9105 "#
9106 .unindent(),
9107 );
9108
9109 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9110 cx.assert_editor_state(
9111 &r#"
9112 <!-- <p>A«</p>
9113 <p>ˇ»B</p>ˇ -->
9114 <!-- <p>C«</p>
9115 <p>ˇ»D</p>ˇ -->
9116 "#
9117 .unindent(),
9118 );
9119 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9120 cx.assert_editor_state(
9121 &r#"
9122 <p>A«</p>
9123 <p>ˇ»B</p>ˇ
9124 <p>C«</p>
9125 <p>ˇ»D</p>ˇ
9126 "#
9127 .unindent(),
9128 );
9129
9130 // Toggle comments when different languages are active for different
9131 // selections.
9132 cx.set_state(
9133 &r#"
9134 ˇ<script>
9135 ˇvar x = new Y();
9136 ˇ</script>
9137 "#
9138 .unindent(),
9139 );
9140 cx.executor().run_until_parked();
9141 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9142 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9143 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9144 cx.assert_editor_state(
9145 &r#"
9146 <!-- ˇ<script> -->
9147 // ˇvar x = new Y();
9148 // ˇ</script>
9149 "#
9150 .unindent(),
9151 );
9152}
9153
9154#[gpui::test]
9155fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9156 init_test(cx, |_| {});
9157
9158 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9159 let multibuffer = cx.new_model(|cx| {
9160 let mut multibuffer = MultiBuffer::new(ReadWrite);
9161 multibuffer.push_excerpts(
9162 buffer.clone(),
9163 [
9164 ExcerptRange {
9165 context: Point::new(0, 0)..Point::new(0, 4),
9166 primary: None,
9167 },
9168 ExcerptRange {
9169 context: Point::new(1, 0)..Point::new(1, 4),
9170 primary: None,
9171 },
9172 ],
9173 cx,
9174 );
9175 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9176 multibuffer
9177 });
9178
9179 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9180 view.update(cx, |view, cx| {
9181 assert_eq!(view.text(cx), "aaaa\nbbbb");
9182 view.change_selections(None, cx, |s| {
9183 s.select_ranges([
9184 Point::new(0, 0)..Point::new(0, 0),
9185 Point::new(1, 0)..Point::new(1, 0),
9186 ])
9187 });
9188
9189 view.handle_input("X", cx);
9190 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9191 assert_eq!(
9192 view.selections.ranges(cx),
9193 [
9194 Point::new(0, 1)..Point::new(0, 1),
9195 Point::new(1, 1)..Point::new(1, 1),
9196 ]
9197 );
9198
9199 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9200 view.change_selections(None, cx, |s| {
9201 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9202 });
9203 view.backspace(&Default::default(), cx);
9204 assert_eq!(view.text(cx), "Xa\nbbb");
9205 assert_eq!(
9206 view.selections.ranges(cx),
9207 [Point::new(1, 0)..Point::new(1, 0)]
9208 );
9209
9210 view.change_selections(None, cx, |s| {
9211 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9212 });
9213 view.backspace(&Default::default(), cx);
9214 assert_eq!(view.text(cx), "X\nbb");
9215 assert_eq!(
9216 view.selections.ranges(cx),
9217 [Point::new(0, 1)..Point::new(0, 1)]
9218 );
9219 });
9220}
9221
9222#[gpui::test]
9223fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9224 init_test(cx, |_| {});
9225
9226 let markers = vec![('[', ']').into(), ('(', ')').into()];
9227 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9228 indoc! {"
9229 [aaaa
9230 (bbbb]
9231 cccc)",
9232 },
9233 markers.clone(),
9234 );
9235 let excerpt_ranges = markers.into_iter().map(|marker| {
9236 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9237 ExcerptRange {
9238 context,
9239 primary: None,
9240 }
9241 });
9242 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9243 let multibuffer = cx.new_model(|cx| {
9244 let mut multibuffer = MultiBuffer::new(ReadWrite);
9245 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9246 multibuffer
9247 });
9248
9249 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9250 view.update(cx, |view, cx| {
9251 let (expected_text, selection_ranges) = marked_text_ranges(
9252 indoc! {"
9253 aaaa
9254 bˇbbb
9255 bˇbbˇb
9256 cccc"
9257 },
9258 true,
9259 );
9260 assert_eq!(view.text(cx), expected_text);
9261 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9262
9263 view.handle_input("X", cx);
9264
9265 let (expected_text, expected_selections) = marked_text_ranges(
9266 indoc! {"
9267 aaaa
9268 bXˇbbXb
9269 bXˇbbXˇb
9270 cccc"
9271 },
9272 false,
9273 );
9274 assert_eq!(view.text(cx), expected_text);
9275 assert_eq!(view.selections.ranges(cx), expected_selections);
9276
9277 view.newline(&Newline, cx);
9278 let (expected_text, expected_selections) = marked_text_ranges(
9279 indoc! {"
9280 aaaa
9281 bX
9282 ˇbbX
9283 b
9284 bX
9285 ˇbbX
9286 ˇb
9287 cccc"
9288 },
9289 false,
9290 );
9291 assert_eq!(view.text(cx), expected_text);
9292 assert_eq!(view.selections.ranges(cx), expected_selections);
9293 });
9294}
9295
9296#[gpui::test]
9297fn test_refresh_selections(cx: &mut TestAppContext) {
9298 init_test(cx, |_| {});
9299
9300 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9301 let mut excerpt1_id = None;
9302 let multibuffer = cx.new_model(|cx| {
9303 let mut multibuffer = MultiBuffer::new(ReadWrite);
9304 excerpt1_id = multibuffer
9305 .push_excerpts(
9306 buffer.clone(),
9307 [
9308 ExcerptRange {
9309 context: Point::new(0, 0)..Point::new(1, 4),
9310 primary: None,
9311 },
9312 ExcerptRange {
9313 context: Point::new(1, 0)..Point::new(2, 4),
9314 primary: None,
9315 },
9316 ],
9317 cx,
9318 )
9319 .into_iter()
9320 .next();
9321 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9322 multibuffer
9323 });
9324
9325 let editor = cx.add_window(|cx| {
9326 let mut editor = build_editor(multibuffer.clone(), cx);
9327 let snapshot = editor.snapshot(cx);
9328 editor.change_selections(None, cx, |s| {
9329 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9330 });
9331 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9332 assert_eq!(
9333 editor.selections.ranges(cx),
9334 [
9335 Point::new(1, 3)..Point::new(1, 3),
9336 Point::new(2, 1)..Point::new(2, 1),
9337 ]
9338 );
9339 editor
9340 });
9341
9342 // Refreshing selections is a no-op when excerpts haven't changed.
9343 _ = editor.update(cx, |editor, cx| {
9344 editor.change_selections(None, cx, |s| s.refresh());
9345 assert_eq!(
9346 editor.selections.ranges(cx),
9347 [
9348 Point::new(1, 3)..Point::new(1, 3),
9349 Point::new(2, 1)..Point::new(2, 1),
9350 ]
9351 );
9352 });
9353
9354 multibuffer.update(cx, |multibuffer, cx| {
9355 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9356 });
9357 _ = editor.update(cx, |editor, cx| {
9358 // Removing an excerpt causes the first selection to become degenerate.
9359 assert_eq!(
9360 editor.selections.ranges(cx),
9361 [
9362 Point::new(0, 0)..Point::new(0, 0),
9363 Point::new(0, 1)..Point::new(0, 1)
9364 ]
9365 );
9366
9367 // Refreshing selections will relocate the first selection to the original buffer
9368 // location.
9369 editor.change_selections(None, cx, |s| s.refresh());
9370 assert_eq!(
9371 editor.selections.ranges(cx),
9372 [
9373 Point::new(0, 1)..Point::new(0, 1),
9374 Point::new(0, 3)..Point::new(0, 3)
9375 ]
9376 );
9377 assert!(editor.selections.pending_anchor().is_some());
9378 });
9379}
9380
9381#[gpui::test]
9382fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9383 init_test(cx, |_| {});
9384
9385 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9386 let mut excerpt1_id = None;
9387 let multibuffer = cx.new_model(|cx| {
9388 let mut multibuffer = MultiBuffer::new(ReadWrite);
9389 excerpt1_id = multibuffer
9390 .push_excerpts(
9391 buffer.clone(),
9392 [
9393 ExcerptRange {
9394 context: Point::new(0, 0)..Point::new(1, 4),
9395 primary: None,
9396 },
9397 ExcerptRange {
9398 context: Point::new(1, 0)..Point::new(2, 4),
9399 primary: None,
9400 },
9401 ],
9402 cx,
9403 )
9404 .into_iter()
9405 .next();
9406 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9407 multibuffer
9408 });
9409
9410 let editor = cx.add_window(|cx| {
9411 let mut editor = build_editor(multibuffer.clone(), cx);
9412 let snapshot = editor.snapshot(cx);
9413 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9414 assert_eq!(
9415 editor.selections.ranges(cx),
9416 [Point::new(1, 3)..Point::new(1, 3)]
9417 );
9418 editor
9419 });
9420
9421 multibuffer.update(cx, |multibuffer, cx| {
9422 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9423 });
9424 _ = editor.update(cx, |editor, cx| {
9425 assert_eq!(
9426 editor.selections.ranges(cx),
9427 [Point::new(0, 0)..Point::new(0, 0)]
9428 );
9429
9430 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9431 editor.change_selections(None, cx, |s| s.refresh());
9432 assert_eq!(
9433 editor.selections.ranges(cx),
9434 [Point::new(0, 3)..Point::new(0, 3)]
9435 );
9436 assert!(editor.selections.pending_anchor().is_some());
9437 });
9438}
9439
9440#[gpui::test]
9441async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9442 init_test(cx, |_| {});
9443
9444 let language = Arc::new(
9445 Language::new(
9446 LanguageConfig {
9447 brackets: BracketPairConfig {
9448 pairs: vec![
9449 BracketPair {
9450 start: "{".to_string(),
9451 end: "}".to_string(),
9452 close: true,
9453 surround: true,
9454 newline: true,
9455 },
9456 BracketPair {
9457 start: "/* ".to_string(),
9458 end: " */".to_string(),
9459 close: true,
9460 surround: true,
9461 newline: true,
9462 },
9463 ],
9464 ..Default::default()
9465 },
9466 ..Default::default()
9467 },
9468 Some(tree_sitter_rust::LANGUAGE.into()),
9469 )
9470 .with_indents_query("")
9471 .unwrap(),
9472 );
9473
9474 let text = concat!(
9475 "{ }\n", //
9476 " x\n", //
9477 " /* */\n", //
9478 "x\n", //
9479 "{{} }\n", //
9480 );
9481
9482 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9483 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9484 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9485 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9486 .await;
9487
9488 view.update(cx, |view, cx| {
9489 view.change_selections(None, cx, |s| {
9490 s.select_display_ranges([
9491 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9492 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9493 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9494 ])
9495 });
9496 view.newline(&Newline, cx);
9497
9498 assert_eq!(
9499 view.buffer().read(cx).read(cx).text(),
9500 concat!(
9501 "{ \n", // Suppress rustfmt
9502 "\n", //
9503 "}\n", //
9504 " x\n", //
9505 " /* \n", //
9506 " \n", //
9507 " */\n", //
9508 "x\n", //
9509 "{{} \n", //
9510 "}\n", //
9511 )
9512 );
9513 });
9514}
9515
9516#[gpui::test]
9517fn test_highlighted_ranges(cx: &mut TestAppContext) {
9518 init_test(cx, |_| {});
9519
9520 let editor = cx.add_window(|cx| {
9521 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9522 build_editor(buffer.clone(), cx)
9523 });
9524
9525 _ = editor.update(cx, |editor, cx| {
9526 struct Type1;
9527 struct Type2;
9528
9529 let buffer = editor.buffer.read(cx).snapshot(cx);
9530
9531 let anchor_range =
9532 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9533
9534 editor.highlight_background::<Type1>(
9535 &[
9536 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9537 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9538 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9539 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9540 ],
9541 |_| Hsla::red(),
9542 cx,
9543 );
9544 editor.highlight_background::<Type2>(
9545 &[
9546 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9547 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9548 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9549 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9550 ],
9551 |_| Hsla::green(),
9552 cx,
9553 );
9554
9555 let snapshot = editor.snapshot(cx);
9556 let mut highlighted_ranges = editor.background_highlights_in_range(
9557 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9558 &snapshot,
9559 cx.theme().colors(),
9560 );
9561 // Enforce a consistent ordering based on color without relying on the ordering of the
9562 // highlight's `TypeId` which is non-executor.
9563 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9564 assert_eq!(
9565 highlighted_ranges,
9566 &[
9567 (
9568 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9569 Hsla::red(),
9570 ),
9571 (
9572 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9573 Hsla::red(),
9574 ),
9575 (
9576 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9577 Hsla::green(),
9578 ),
9579 (
9580 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9581 Hsla::green(),
9582 ),
9583 ]
9584 );
9585 assert_eq!(
9586 editor.background_highlights_in_range(
9587 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9588 &snapshot,
9589 cx.theme().colors(),
9590 ),
9591 &[(
9592 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9593 Hsla::red(),
9594 )]
9595 );
9596 });
9597}
9598
9599#[gpui::test]
9600async fn test_following(cx: &mut gpui::TestAppContext) {
9601 init_test(cx, |_| {});
9602
9603 let fs = FakeFs::new(cx.executor());
9604 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9605
9606 let buffer = project.update(cx, |project, cx| {
9607 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9608 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9609 });
9610 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9611 let follower = cx.update(|cx| {
9612 cx.open_window(
9613 WindowOptions {
9614 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9615 gpui::Point::new(px(0.), px(0.)),
9616 gpui::Point::new(px(10.), px(80.)),
9617 ))),
9618 ..Default::default()
9619 },
9620 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9621 )
9622 .unwrap()
9623 });
9624
9625 let is_still_following = Rc::new(RefCell::new(true));
9626 let follower_edit_event_count = Rc::new(RefCell::new(0));
9627 let pending_update = Rc::new(RefCell::new(None));
9628 _ = follower.update(cx, {
9629 let update = pending_update.clone();
9630 let is_still_following = is_still_following.clone();
9631 let follower_edit_event_count = follower_edit_event_count.clone();
9632 |_, cx| {
9633 cx.subscribe(
9634 &leader.root_view(cx).unwrap(),
9635 move |_, leader, event, cx| {
9636 leader
9637 .read(cx)
9638 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9639 },
9640 )
9641 .detach();
9642
9643 cx.subscribe(
9644 &follower.root_view(cx).unwrap(),
9645 move |_, _, event: &EditorEvent, _cx| {
9646 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9647 *is_still_following.borrow_mut() = false;
9648 }
9649
9650 if let EditorEvent::BufferEdited = event {
9651 *follower_edit_event_count.borrow_mut() += 1;
9652 }
9653 },
9654 )
9655 .detach();
9656 }
9657 });
9658
9659 // Update the selections only
9660 _ = leader.update(cx, |leader, cx| {
9661 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9662 });
9663 follower
9664 .update(cx, |follower, cx| {
9665 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9666 })
9667 .unwrap()
9668 .await
9669 .unwrap();
9670 _ = follower.update(cx, |follower, cx| {
9671 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9672 });
9673 assert!(*is_still_following.borrow());
9674 assert_eq!(*follower_edit_event_count.borrow(), 0);
9675
9676 // Update the scroll position only
9677 _ = leader.update(cx, |leader, cx| {
9678 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9679 });
9680 follower
9681 .update(cx, |follower, cx| {
9682 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9683 })
9684 .unwrap()
9685 .await
9686 .unwrap();
9687 assert_eq!(
9688 follower
9689 .update(cx, |follower, cx| follower.scroll_position(cx))
9690 .unwrap(),
9691 gpui::Point::new(1.5, 3.5)
9692 );
9693 assert!(*is_still_following.borrow());
9694 assert_eq!(*follower_edit_event_count.borrow(), 0);
9695
9696 // Update the selections and scroll position. The follower's scroll position is updated
9697 // via autoscroll, not via the leader's exact scroll position.
9698 _ = leader.update(cx, |leader, cx| {
9699 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9700 leader.request_autoscroll(Autoscroll::newest(), cx);
9701 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9702 });
9703 follower
9704 .update(cx, |follower, cx| {
9705 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9706 })
9707 .unwrap()
9708 .await
9709 .unwrap();
9710 _ = follower.update(cx, |follower, cx| {
9711 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9712 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9713 });
9714 assert!(*is_still_following.borrow());
9715
9716 // Creating a pending selection that precedes another selection
9717 _ = leader.update(cx, |leader, cx| {
9718 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9719 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9720 });
9721 follower
9722 .update(cx, |follower, cx| {
9723 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9724 })
9725 .unwrap()
9726 .await
9727 .unwrap();
9728 _ = follower.update(cx, |follower, cx| {
9729 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9730 });
9731 assert!(*is_still_following.borrow());
9732
9733 // Extend the pending selection so that it surrounds another selection
9734 _ = leader.update(cx, |leader, cx| {
9735 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9736 });
9737 follower
9738 .update(cx, |follower, cx| {
9739 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9740 })
9741 .unwrap()
9742 .await
9743 .unwrap();
9744 _ = follower.update(cx, |follower, cx| {
9745 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9746 });
9747
9748 // Scrolling locally breaks the follow
9749 _ = follower.update(cx, |follower, cx| {
9750 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9751 follower.set_scroll_anchor(
9752 ScrollAnchor {
9753 anchor: top_anchor,
9754 offset: gpui::Point::new(0.0, 0.5),
9755 },
9756 cx,
9757 );
9758 });
9759 assert!(!(*is_still_following.borrow()));
9760}
9761
9762#[gpui::test]
9763async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9764 init_test(cx, |_| {});
9765
9766 let fs = FakeFs::new(cx.executor());
9767 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9768 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9769 let pane = workspace
9770 .update(cx, |workspace, _| workspace.active_pane().clone())
9771 .unwrap();
9772
9773 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9774
9775 let leader = pane.update(cx, |_, cx| {
9776 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9777 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9778 });
9779
9780 // Start following the editor when it has no excerpts.
9781 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9782 let follower_1 = cx
9783 .update_window(*workspace.deref(), |_, cx| {
9784 Editor::from_state_proto(
9785 workspace.root_view(cx).unwrap(),
9786 ViewId {
9787 creator: Default::default(),
9788 id: 0,
9789 },
9790 &mut state_message,
9791 cx,
9792 )
9793 })
9794 .unwrap()
9795 .unwrap()
9796 .await
9797 .unwrap();
9798
9799 let update_message = Rc::new(RefCell::new(None));
9800 follower_1.update(cx, {
9801 let update = update_message.clone();
9802 |_, cx| {
9803 cx.subscribe(&leader, move |_, leader, event, cx| {
9804 leader
9805 .read(cx)
9806 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9807 })
9808 .detach();
9809 }
9810 });
9811
9812 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9813 (
9814 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9815 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9816 )
9817 });
9818
9819 // Insert some excerpts.
9820 leader.update(cx, |leader, cx| {
9821 leader.buffer.update(cx, |multibuffer, cx| {
9822 let excerpt_ids = multibuffer.push_excerpts(
9823 buffer_1.clone(),
9824 [
9825 ExcerptRange {
9826 context: 1..6,
9827 primary: None,
9828 },
9829 ExcerptRange {
9830 context: 12..15,
9831 primary: None,
9832 },
9833 ExcerptRange {
9834 context: 0..3,
9835 primary: None,
9836 },
9837 ],
9838 cx,
9839 );
9840 multibuffer.insert_excerpts_after(
9841 excerpt_ids[0],
9842 buffer_2.clone(),
9843 [
9844 ExcerptRange {
9845 context: 8..12,
9846 primary: None,
9847 },
9848 ExcerptRange {
9849 context: 0..6,
9850 primary: None,
9851 },
9852 ],
9853 cx,
9854 );
9855 });
9856 });
9857
9858 // Apply the update of adding the excerpts.
9859 follower_1
9860 .update(cx, |follower, cx| {
9861 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9862 })
9863 .await
9864 .unwrap();
9865 assert_eq!(
9866 follower_1.update(cx, |editor, cx| editor.text(cx)),
9867 leader.update(cx, |editor, cx| editor.text(cx))
9868 );
9869 update_message.borrow_mut().take();
9870
9871 // Start following separately after it already has excerpts.
9872 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9873 let follower_2 = cx
9874 .update_window(*workspace.deref(), |_, cx| {
9875 Editor::from_state_proto(
9876 workspace.root_view(cx).unwrap().clone(),
9877 ViewId {
9878 creator: Default::default(),
9879 id: 0,
9880 },
9881 &mut state_message,
9882 cx,
9883 )
9884 })
9885 .unwrap()
9886 .unwrap()
9887 .await
9888 .unwrap();
9889 assert_eq!(
9890 follower_2.update(cx, |editor, cx| editor.text(cx)),
9891 leader.update(cx, |editor, cx| editor.text(cx))
9892 );
9893
9894 // Remove some excerpts.
9895 leader.update(cx, |leader, cx| {
9896 leader.buffer.update(cx, |multibuffer, cx| {
9897 let excerpt_ids = multibuffer.excerpt_ids();
9898 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9899 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9900 });
9901 });
9902
9903 // Apply the update of removing the excerpts.
9904 follower_1
9905 .update(cx, |follower, cx| {
9906 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9907 })
9908 .await
9909 .unwrap();
9910 follower_2
9911 .update(cx, |follower, cx| {
9912 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9913 })
9914 .await
9915 .unwrap();
9916 update_message.borrow_mut().take();
9917 assert_eq!(
9918 follower_1.update(cx, |editor, cx| editor.text(cx)),
9919 leader.update(cx, |editor, cx| editor.text(cx))
9920 );
9921}
9922
9923#[gpui::test]
9924async fn go_to_prev_overlapping_diagnostic(
9925 executor: BackgroundExecutor,
9926 cx: &mut gpui::TestAppContext,
9927) {
9928 init_test(cx, |_| {});
9929
9930 let mut cx = EditorTestContext::new(cx).await;
9931 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9932
9933 cx.set_state(indoc! {"
9934 ˇfn func(abc def: i32) -> u32 {
9935 }
9936 "});
9937
9938 cx.update(|cx| {
9939 project.update(cx, |project, cx| {
9940 project
9941 .update_diagnostics(
9942 LanguageServerId(0),
9943 lsp::PublishDiagnosticsParams {
9944 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9945 version: None,
9946 diagnostics: vec![
9947 lsp::Diagnostic {
9948 range: lsp::Range::new(
9949 lsp::Position::new(0, 11),
9950 lsp::Position::new(0, 12),
9951 ),
9952 severity: Some(lsp::DiagnosticSeverity::ERROR),
9953 ..Default::default()
9954 },
9955 lsp::Diagnostic {
9956 range: lsp::Range::new(
9957 lsp::Position::new(0, 12),
9958 lsp::Position::new(0, 15),
9959 ),
9960 severity: Some(lsp::DiagnosticSeverity::ERROR),
9961 ..Default::default()
9962 },
9963 lsp::Diagnostic {
9964 range: lsp::Range::new(
9965 lsp::Position::new(0, 25),
9966 lsp::Position::new(0, 28),
9967 ),
9968 severity: Some(lsp::DiagnosticSeverity::ERROR),
9969 ..Default::default()
9970 },
9971 ],
9972 },
9973 &[],
9974 cx,
9975 )
9976 .unwrap()
9977 });
9978 });
9979
9980 executor.run_until_parked();
9981
9982 cx.update_editor(|editor, cx| {
9983 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9984 });
9985
9986 cx.assert_editor_state(indoc! {"
9987 fn func(abc def: i32) -> ˇu32 {
9988 }
9989 "});
9990
9991 cx.update_editor(|editor, cx| {
9992 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9993 });
9994
9995 cx.assert_editor_state(indoc! {"
9996 fn func(abc ˇdef: i32) -> u32 {
9997 }
9998 "});
9999
10000 cx.update_editor(|editor, cx| {
10001 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10002 });
10003
10004 cx.assert_editor_state(indoc! {"
10005 fn func(abcˇ def: i32) -> u32 {
10006 }
10007 "});
10008
10009 cx.update_editor(|editor, cx| {
10010 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10011 });
10012
10013 cx.assert_editor_state(indoc! {"
10014 fn func(abc def: i32) -> ˇu32 {
10015 }
10016 "});
10017}
10018
10019#[gpui::test]
10020async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10021 init_test(cx, |_| {});
10022
10023 let mut cx = EditorTestContext::new(cx).await;
10024
10025 cx.set_state(indoc! {"
10026 fn func(abˇc def: i32) -> u32 {
10027 }
10028 "});
10029 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
10030
10031 cx.update(|cx| {
10032 project.update(cx, |project, cx| {
10033 project.update_diagnostics(
10034 LanguageServerId(0),
10035 lsp::PublishDiagnosticsParams {
10036 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10037 version: None,
10038 diagnostics: vec![lsp::Diagnostic {
10039 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10040 severity: Some(lsp::DiagnosticSeverity::ERROR),
10041 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10042 ..Default::default()
10043 }],
10044 },
10045 &[],
10046 cx,
10047 )
10048 })
10049 }).unwrap();
10050 cx.run_until_parked();
10051 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10052 cx.run_until_parked();
10053 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10054}
10055
10056#[gpui::test]
10057async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10058 init_test(cx, |_| {});
10059
10060 let mut cx = EditorTestContext::new(cx).await;
10061
10062 let diff_base = r#"
10063 use some::mod;
10064
10065 const A: u32 = 42;
10066
10067 fn main() {
10068 println!("hello");
10069
10070 println!("world");
10071 }
10072 "#
10073 .unindent();
10074
10075 // Edits are modified, removed, modified, added
10076 cx.set_state(
10077 &r#"
10078 use some::modified;
10079
10080 ˇ
10081 fn main() {
10082 println!("hello there");
10083
10084 println!("around the");
10085 println!("world");
10086 }
10087 "#
10088 .unindent(),
10089 );
10090
10091 cx.set_diff_base(Some(&diff_base));
10092 executor.run_until_parked();
10093
10094 cx.update_editor(|editor, cx| {
10095 //Wrap around the bottom of the buffer
10096 for _ in 0..3 {
10097 editor.go_to_next_hunk(&GoToHunk, cx);
10098 }
10099 });
10100
10101 cx.assert_editor_state(
10102 &r#"
10103 ˇuse some::modified;
10104
10105
10106 fn main() {
10107 println!("hello there");
10108
10109 println!("around the");
10110 println!("world");
10111 }
10112 "#
10113 .unindent(),
10114 );
10115
10116 cx.update_editor(|editor, cx| {
10117 //Wrap around the top of the buffer
10118 for _ in 0..2 {
10119 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10120 }
10121 });
10122
10123 cx.assert_editor_state(
10124 &r#"
10125 use some::modified;
10126
10127
10128 fn main() {
10129 ˇ println!("hello there");
10130
10131 println!("around the");
10132 println!("world");
10133 }
10134 "#
10135 .unindent(),
10136 );
10137
10138 cx.update_editor(|editor, cx| {
10139 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10140 });
10141
10142 cx.assert_editor_state(
10143 &r#"
10144 use some::modified;
10145
10146 ˇ
10147 fn main() {
10148 println!("hello there");
10149
10150 println!("around the");
10151 println!("world");
10152 }
10153 "#
10154 .unindent(),
10155 );
10156
10157 cx.update_editor(|editor, cx| {
10158 for _ in 0..3 {
10159 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10160 }
10161 });
10162
10163 cx.assert_editor_state(
10164 &r#"
10165 use some::modified;
10166
10167
10168 fn main() {
10169 ˇ println!("hello there");
10170
10171 println!("around the");
10172 println!("world");
10173 }
10174 "#
10175 .unindent(),
10176 );
10177
10178 cx.update_editor(|editor, cx| {
10179 editor.fold(&Fold, cx);
10180
10181 //Make sure that the fold only gets one hunk
10182 for _ in 0..4 {
10183 editor.go_to_next_hunk(&GoToHunk, cx);
10184 }
10185 });
10186
10187 cx.assert_editor_state(
10188 &r#"
10189 ˇuse some::modified;
10190
10191
10192 fn main() {
10193 println!("hello there");
10194
10195 println!("around the");
10196 println!("world");
10197 }
10198 "#
10199 .unindent(),
10200 );
10201}
10202
10203#[test]
10204fn test_split_words() {
10205 fn split(text: &str) -> Vec<&str> {
10206 split_words(text).collect()
10207 }
10208
10209 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10210 assert_eq!(split("hello_world"), &["hello_", "world"]);
10211 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10212 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10213 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10214 assert_eq!(split("helloworld"), &["helloworld"]);
10215
10216 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10217}
10218
10219#[gpui::test]
10220async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10221 init_test(cx, |_| {});
10222
10223 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10224 let mut assert = |before, after| {
10225 let _state_context = cx.set_state(before);
10226 cx.update_editor(|editor, cx| {
10227 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10228 });
10229 cx.assert_editor_state(after);
10230 };
10231
10232 // Outside bracket jumps to outside of matching bracket
10233 assert("console.logˇ(var);", "console.log(var)ˇ;");
10234 assert("console.log(var)ˇ;", "console.logˇ(var);");
10235
10236 // Inside bracket jumps to inside of matching bracket
10237 assert("console.log(ˇvar);", "console.log(varˇ);");
10238 assert("console.log(varˇ);", "console.log(ˇvar);");
10239
10240 // When outside a bracket and inside, favor jumping to the inside bracket
10241 assert(
10242 "console.log('foo', [1, 2, 3]ˇ);",
10243 "console.log(ˇ'foo', [1, 2, 3]);",
10244 );
10245 assert(
10246 "console.log(ˇ'foo', [1, 2, 3]);",
10247 "console.log('foo', [1, 2, 3]ˇ);",
10248 );
10249
10250 // Bias forward if two options are equally likely
10251 assert(
10252 "let result = curried_fun()ˇ();",
10253 "let result = curried_fun()()ˇ;",
10254 );
10255
10256 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10257 assert(
10258 indoc! {"
10259 function test() {
10260 console.log('test')ˇ
10261 }"},
10262 indoc! {"
10263 function test() {
10264 console.logˇ('test')
10265 }"},
10266 );
10267}
10268
10269#[gpui::test]
10270async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10271 init_test(cx, |_| {});
10272
10273 let fs = FakeFs::new(cx.executor());
10274 fs.insert_tree(
10275 "/a",
10276 json!({
10277 "main.rs": "fn main() { let a = 5; }",
10278 "other.rs": "// Test file",
10279 }),
10280 )
10281 .await;
10282 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10283
10284 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10285 language_registry.add(Arc::new(Language::new(
10286 LanguageConfig {
10287 name: "Rust".into(),
10288 matcher: LanguageMatcher {
10289 path_suffixes: vec!["rs".to_string()],
10290 ..Default::default()
10291 },
10292 brackets: BracketPairConfig {
10293 pairs: vec![BracketPair {
10294 start: "{".to_string(),
10295 end: "}".to_string(),
10296 close: true,
10297 surround: true,
10298 newline: true,
10299 }],
10300 disabled_scopes_by_bracket_ix: Vec::new(),
10301 },
10302 ..Default::default()
10303 },
10304 Some(tree_sitter_rust::LANGUAGE.into()),
10305 )));
10306 let mut fake_servers = language_registry.register_fake_lsp(
10307 "Rust",
10308 FakeLspAdapter {
10309 capabilities: lsp::ServerCapabilities {
10310 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10311 first_trigger_character: "{".to_string(),
10312 more_trigger_character: None,
10313 }),
10314 ..Default::default()
10315 },
10316 ..Default::default()
10317 },
10318 );
10319
10320 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10321
10322 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10323
10324 let worktree_id = workspace
10325 .update(cx, |workspace, cx| {
10326 workspace.project().update(cx, |project, cx| {
10327 project.worktrees(cx).next().unwrap().read(cx).id()
10328 })
10329 })
10330 .unwrap();
10331
10332 let buffer = project
10333 .update(cx, |project, cx| {
10334 project.open_local_buffer("/a/main.rs", cx)
10335 })
10336 .await
10337 .unwrap();
10338 cx.executor().run_until_parked();
10339 cx.executor().start_waiting();
10340 let fake_server = fake_servers.next().await.unwrap();
10341 let editor_handle = workspace
10342 .update(cx, |workspace, cx| {
10343 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10344 })
10345 .unwrap()
10346 .await
10347 .unwrap()
10348 .downcast::<Editor>()
10349 .unwrap();
10350
10351 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10352 assert_eq!(
10353 params.text_document_position.text_document.uri,
10354 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10355 );
10356 assert_eq!(
10357 params.text_document_position.position,
10358 lsp::Position::new(0, 21),
10359 );
10360
10361 Ok(Some(vec![lsp::TextEdit {
10362 new_text: "]".to_string(),
10363 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10364 }]))
10365 });
10366
10367 editor_handle.update(cx, |editor, cx| {
10368 editor.focus(cx);
10369 editor.change_selections(None, cx, |s| {
10370 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10371 });
10372 editor.handle_input("{", cx);
10373 });
10374
10375 cx.executor().run_until_parked();
10376
10377 buffer.update(cx, |buffer, _| {
10378 assert_eq!(
10379 buffer.text(),
10380 "fn main() { let a = {5}; }",
10381 "No extra braces from on type formatting should appear in the buffer"
10382 )
10383 });
10384}
10385
10386#[gpui::test]
10387async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10388 init_test(cx, |_| {});
10389
10390 let fs = FakeFs::new(cx.executor());
10391 fs.insert_tree(
10392 "/a",
10393 json!({
10394 "main.rs": "fn main() { let a = 5; }",
10395 "other.rs": "// Test file",
10396 }),
10397 )
10398 .await;
10399
10400 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10401
10402 let server_restarts = Arc::new(AtomicUsize::new(0));
10403 let closure_restarts = Arc::clone(&server_restarts);
10404 let language_server_name = "test language server";
10405 let language_name: LanguageName = "Rust".into();
10406
10407 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10408 language_registry.add(Arc::new(Language::new(
10409 LanguageConfig {
10410 name: language_name.clone(),
10411 matcher: LanguageMatcher {
10412 path_suffixes: vec!["rs".to_string()],
10413 ..Default::default()
10414 },
10415 ..Default::default()
10416 },
10417 Some(tree_sitter_rust::LANGUAGE.into()),
10418 )));
10419 let mut fake_servers = language_registry.register_fake_lsp(
10420 "Rust",
10421 FakeLspAdapter {
10422 name: language_server_name,
10423 initialization_options: Some(json!({
10424 "testOptionValue": true
10425 })),
10426 initializer: Some(Box::new(move |fake_server| {
10427 let task_restarts = Arc::clone(&closure_restarts);
10428 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10429 task_restarts.fetch_add(1, atomic::Ordering::Release);
10430 futures::future::ready(Ok(()))
10431 });
10432 })),
10433 ..Default::default()
10434 },
10435 );
10436
10437 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10438 let _buffer = project
10439 .update(cx, |project, cx| {
10440 project.open_local_buffer("/a/main.rs", cx)
10441 })
10442 .await
10443 .unwrap();
10444 let _fake_server = fake_servers.next().await.unwrap();
10445 update_test_language_settings(cx, |language_settings| {
10446 language_settings.languages.insert(
10447 language_name.clone(),
10448 LanguageSettingsContent {
10449 tab_size: NonZeroU32::new(8),
10450 ..Default::default()
10451 },
10452 );
10453 });
10454 cx.executor().run_until_parked();
10455 assert_eq!(
10456 server_restarts.load(atomic::Ordering::Acquire),
10457 0,
10458 "Should not restart LSP server on an unrelated change"
10459 );
10460
10461 update_test_project_settings(cx, |project_settings| {
10462 project_settings.lsp.insert(
10463 "Some other server name".into(),
10464 LspSettings {
10465 binary: None,
10466 settings: None,
10467 initialization_options: Some(json!({
10468 "some other init value": false
10469 })),
10470 },
10471 );
10472 });
10473 cx.executor().run_until_parked();
10474 assert_eq!(
10475 server_restarts.load(atomic::Ordering::Acquire),
10476 0,
10477 "Should not restart LSP server on an unrelated LSP settings change"
10478 );
10479
10480 update_test_project_settings(cx, |project_settings| {
10481 project_settings.lsp.insert(
10482 language_server_name.into(),
10483 LspSettings {
10484 binary: None,
10485 settings: None,
10486 initialization_options: Some(json!({
10487 "anotherInitValue": false
10488 })),
10489 },
10490 );
10491 });
10492 cx.executor().run_until_parked();
10493 assert_eq!(
10494 server_restarts.load(atomic::Ordering::Acquire),
10495 1,
10496 "Should restart LSP server on a related LSP settings change"
10497 );
10498
10499 update_test_project_settings(cx, |project_settings| {
10500 project_settings.lsp.insert(
10501 language_server_name.into(),
10502 LspSettings {
10503 binary: None,
10504 settings: None,
10505 initialization_options: Some(json!({
10506 "anotherInitValue": false
10507 })),
10508 },
10509 );
10510 });
10511 cx.executor().run_until_parked();
10512 assert_eq!(
10513 server_restarts.load(atomic::Ordering::Acquire),
10514 1,
10515 "Should not restart LSP server on a related LSP settings change that is the same"
10516 );
10517
10518 update_test_project_settings(cx, |project_settings| {
10519 project_settings.lsp.insert(
10520 language_server_name.into(),
10521 LspSettings {
10522 binary: None,
10523 settings: None,
10524 initialization_options: None,
10525 },
10526 );
10527 });
10528 cx.executor().run_until_parked();
10529 assert_eq!(
10530 server_restarts.load(atomic::Ordering::Acquire),
10531 2,
10532 "Should restart LSP server on another related LSP settings change"
10533 );
10534}
10535
10536#[gpui::test]
10537async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10538 init_test(cx, |_| {});
10539
10540 let mut cx = EditorLspTestContext::new_rust(
10541 lsp::ServerCapabilities {
10542 completion_provider: Some(lsp::CompletionOptions {
10543 trigger_characters: Some(vec![".".to_string()]),
10544 resolve_provider: Some(true),
10545 ..Default::default()
10546 }),
10547 ..Default::default()
10548 },
10549 cx,
10550 )
10551 .await;
10552
10553 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10554 cx.simulate_keystroke(".");
10555 let completion_item = lsp::CompletionItem {
10556 label: "some".into(),
10557 kind: Some(lsp::CompletionItemKind::SNIPPET),
10558 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10559 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10560 kind: lsp::MarkupKind::Markdown,
10561 value: "```rust\nSome(2)\n```".to_string(),
10562 })),
10563 deprecated: Some(false),
10564 sort_text: Some("fffffff2".to_string()),
10565 filter_text: Some("some".to_string()),
10566 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10567 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10568 range: lsp::Range {
10569 start: lsp::Position {
10570 line: 0,
10571 character: 22,
10572 },
10573 end: lsp::Position {
10574 line: 0,
10575 character: 22,
10576 },
10577 },
10578 new_text: "Some(2)".to_string(),
10579 })),
10580 additional_text_edits: Some(vec![lsp::TextEdit {
10581 range: lsp::Range {
10582 start: lsp::Position {
10583 line: 0,
10584 character: 20,
10585 },
10586 end: lsp::Position {
10587 line: 0,
10588 character: 22,
10589 },
10590 },
10591 new_text: "".to_string(),
10592 }]),
10593 ..Default::default()
10594 };
10595
10596 let closure_completion_item = completion_item.clone();
10597 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10598 let task_completion_item = closure_completion_item.clone();
10599 async move {
10600 Ok(Some(lsp::CompletionResponse::Array(vec![
10601 task_completion_item,
10602 ])))
10603 }
10604 });
10605
10606 request.next().await;
10607
10608 cx.condition(|editor, _| editor.context_menu_visible())
10609 .await;
10610 let apply_additional_edits = cx.update_editor(|editor, cx| {
10611 editor
10612 .confirm_completion(&ConfirmCompletion::default(), cx)
10613 .unwrap()
10614 });
10615 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10616
10617 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10618 let task_completion_item = completion_item.clone();
10619 async move { Ok(task_completion_item) }
10620 })
10621 .next()
10622 .await
10623 .unwrap();
10624 apply_additional_edits.await.unwrap();
10625 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10626}
10627
10628#[gpui::test]
10629async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10630 init_test(cx, |_| {});
10631
10632 let mut cx = EditorLspTestContext::new_rust(
10633 lsp::ServerCapabilities {
10634 completion_provider: Some(lsp::CompletionOptions {
10635 trigger_characters: Some(vec![".".to_string()]),
10636 resolve_provider: Some(true),
10637 ..Default::default()
10638 }),
10639 ..Default::default()
10640 },
10641 cx,
10642 )
10643 .await;
10644
10645 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10646 cx.simulate_keystroke(".");
10647
10648 let default_commit_characters = vec!["?".to_string()];
10649 let default_data = json!({ "very": "special"});
10650 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10651 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10652 let default_edit_range = lsp::Range {
10653 start: lsp::Position {
10654 line: 0,
10655 character: 5,
10656 },
10657 end: lsp::Position {
10658 line: 0,
10659 character: 5,
10660 },
10661 };
10662
10663 let completion_data = default_data.clone();
10664 let completion_characters = default_commit_characters.clone();
10665 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10666 let default_data = completion_data.clone();
10667 let default_commit_characters = completion_characters.clone();
10668 async move {
10669 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10670 items: vec![
10671 lsp::CompletionItem {
10672 label: "Some(2)".into(),
10673 insert_text: Some("Some(2)".into()),
10674 data: Some(json!({ "very": "special"})),
10675 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10676 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10677 lsp::InsertReplaceEdit {
10678 new_text: "Some(2)".to_string(),
10679 insert: lsp::Range::default(),
10680 replace: lsp::Range::default(),
10681 },
10682 )),
10683 ..lsp::CompletionItem::default()
10684 },
10685 lsp::CompletionItem {
10686 label: "vec![2]".into(),
10687 insert_text: Some("vec![2]".into()),
10688 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10689 ..lsp::CompletionItem::default()
10690 },
10691 ],
10692 item_defaults: Some(lsp::CompletionListItemDefaults {
10693 data: Some(default_data.clone()),
10694 commit_characters: Some(default_commit_characters.clone()),
10695 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10696 default_edit_range,
10697 )),
10698 insert_text_format: Some(default_insert_text_format),
10699 insert_text_mode: Some(default_insert_text_mode),
10700 }),
10701 ..lsp::CompletionList::default()
10702 })))
10703 }
10704 })
10705 .next()
10706 .await;
10707
10708 cx.condition(|editor, _| editor.context_menu_visible())
10709 .await;
10710
10711 cx.update_editor(|editor, _| {
10712 let menu = editor.context_menu.read();
10713 match menu.as_ref().expect("should have the completions menu") {
10714 ContextMenu::Completions(completions_menu) => {
10715 assert_eq!(
10716 completions_menu
10717 .matches
10718 .iter()
10719 .map(|c| c.string.as_str())
10720 .collect::<Vec<_>>(),
10721 vec!["Some(2)", "vec![2]"]
10722 );
10723 }
10724 ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10725 }
10726 });
10727
10728 cx.update_editor(|editor, cx| {
10729 editor.context_menu_first(&ContextMenuFirst, cx);
10730 });
10731 let first_item_resolve_characters = default_commit_characters.clone();
10732 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, item_to_resolve, _| {
10733 let default_commit_characters = first_item_resolve_characters.clone();
10734
10735 async move {
10736 assert_eq!(
10737 item_to_resolve.label, "Some(2)",
10738 "Should have selected the first item"
10739 );
10740 assert_eq!(
10741 item_to_resolve.data,
10742 Some(json!({ "very": "special"})),
10743 "First item should bring its own data for resolving"
10744 );
10745 assert_eq!(
10746 item_to_resolve.commit_characters,
10747 Some(default_commit_characters),
10748 "First item had no own commit characters and should inherit the default ones"
10749 );
10750 assert!(
10751 matches!(
10752 item_to_resolve.text_edit,
10753 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10754 ),
10755 "First item should bring its own edit range for resolving"
10756 );
10757 assert_eq!(
10758 item_to_resolve.insert_text_format,
10759 Some(default_insert_text_format),
10760 "First item had no own insert text format and should inherit the default one"
10761 );
10762 assert_eq!(
10763 item_to_resolve.insert_text_mode,
10764 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10765 "First item should bring its own insert text mode for resolving"
10766 );
10767 Ok(item_to_resolve)
10768 }
10769 })
10770 .next()
10771 .await
10772 .unwrap();
10773
10774 cx.update_editor(|editor, cx| {
10775 editor.context_menu_last(&ContextMenuLast, cx);
10776 });
10777 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, item_to_resolve, _| {
10778 let default_data = default_data.clone();
10779 let default_commit_characters = default_commit_characters.clone();
10780 async move {
10781 assert_eq!(
10782 item_to_resolve.label, "vec![2]",
10783 "Should have selected the last item"
10784 );
10785 assert_eq!(
10786 item_to_resolve.data,
10787 Some(default_data),
10788 "Last item has no own resolve data and should inherit the default one"
10789 );
10790 assert_eq!(
10791 item_to_resolve.commit_characters,
10792 Some(default_commit_characters),
10793 "Last item had no own commit characters and should inherit the default ones"
10794 );
10795 assert_eq!(
10796 item_to_resolve.text_edit,
10797 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10798 range: default_edit_range,
10799 new_text: "vec![2]".to_string()
10800 })),
10801 "Last item had no own edit range and should inherit the default one"
10802 );
10803 assert_eq!(
10804 item_to_resolve.insert_text_format,
10805 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10806 "Last item should bring its own insert text format for resolving"
10807 );
10808 assert_eq!(
10809 item_to_resolve.insert_text_mode,
10810 Some(default_insert_text_mode),
10811 "Last item had no own insert text mode and should inherit the default one"
10812 );
10813
10814 Ok(item_to_resolve)
10815 }
10816 })
10817 .next()
10818 .await
10819 .unwrap();
10820}
10821
10822#[gpui::test]
10823async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10824 init_test(cx, |_| {});
10825
10826 let mut cx = EditorLspTestContext::new(
10827 Language::new(
10828 LanguageConfig {
10829 matcher: LanguageMatcher {
10830 path_suffixes: vec!["jsx".into()],
10831 ..Default::default()
10832 },
10833 overrides: [(
10834 "element".into(),
10835 LanguageConfigOverride {
10836 word_characters: Override::Set(['-'].into_iter().collect()),
10837 ..Default::default()
10838 },
10839 )]
10840 .into_iter()
10841 .collect(),
10842 ..Default::default()
10843 },
10844 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10845 )
10846 .with_override_query("(jsx_self_closing_element) @element")
10847 .unwrap(),
10848 lsp::ServerCapabilities {
10849 completion_provider: Some(lsp::CompletionOptions {
10850 trigger_characters: Some(vec![":".to_string()]),
10851 ..Default::default()
10852 }),
10853 ..Default::default()
10854 },
10855 cx,
10856 )
10857 .await;
10858
10859 cx.lsp
10860 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10861 Ok(Some(lsp::CompletionResponse::Array(vec![
10862 lsp::CompletionItem {
10863 label: "bg-blue".into(),
10864 ..Default::default()
10865 },
10866 lsp::CompletionItem {
10867 label: "bg-red".into(),
10868 ..Default::default()
10869 },
10870 lsp::CompletionItem {
10871 label: "bg-yellow".into(),
10872 ..Default::default()
10873 },
10874 ])))
10875 });
10876
10877 cx.set_state(r#"<p class="bgˇ" />"#);
10878
10879 // Trigger completion when typing a dash, because the dash is an extra
10880 // word character in the 'element' scope, which contains the cursor.
10881 cx.simulate_keystroke("-");
10882 cx.executor().run_until_parked();
10883 cx.update_editor(|editor, _| {
10884 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10885 assert_eq!(
10886 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10887 &["bg-red", "bg-blue", "bg-yellow"]
10888 );
10889 } else {
10890 panic!("expected completion menu to be open");
10891 }
10892 });
10893
10894 cx.simulate_keystroke("l");
10895 cx.executor().run_until_parked();
10896 cx.update_editor(|editor, _| {
10897 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10898 assert_eq!(
10899 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10900 &["bg-blue", "bg-yellow"]
10901 );
10902 } else {
10903 panic!("expected completion menu to be open");
10904 }
10905 });
10906
10907 // When filtering completions, consider the character after the '-' to
10908 // be the start of a subword.
10909 cx.set_state(r#"<p class="yelˇ" />"#);
10910 cx.simulate_keystroke("l");
10911 cx.executor().run_until_parked();
10912 cx.update_editor(|editor, _| {
10913 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10914 assert_eq!(
10915 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10916 &["bg-yellow"]
10917 );
10918 } else {
10919 panic!("expected completion menu to be open");
10920 }
10921 });
10922}
10923
10924#[gpui::test]
10925async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10926 init_test(cx, |settings| {
10927 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10928 FormatterList(vec![Formatter::Prettier].into()),
10929 ))
10930 });
10931
10932 let fs = FakeFs::new(cx.executor());
10933 fs.insert_file("/file.ts", Default::default()).await;
10934
10935 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10936 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10937
10938 language_registry.add(Arc::new(Language::new(
10939 LanguageConfig {
10940 name: "TypeScript".into(),
10941 matcher: LanguageMatcher {
10942 path_suffixes: vec!["ts".to_string()],
10943 ..Default::default()
10944 },
10945 ..Default::default()
10946 },
10947 Some(tree_sitter_rust::LANGUAGE.into()),
10948 )));
10949 update_test_language_settings(cx, |settings| {
10950 settings.defaults.prettier = Some(PrettierSettings {
10951 allowed: true,
10952 ..PrettierSettings::default()
10953 });
10954 });
10955
10956 let test_plugin = "test_plugin";
10957 let _ = language_registry.register_fake_lsp(
10958 "TypeScript",
10959 FakeLspAdapter {
10960 prettier_plugins: vec![test_plugin],
10961 ..Default::default()
10962 },
10963 );
10964
10965 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10966 let buffer = project
10967 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10968 .await
10969 .unwrap();
10970
10971 let buffer_text = "one\ntwo\nthree\n";
10972 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10973 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10974 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10975
10976 editor
10977 .update(cx, |editor, cx| {
10978 editor.perform_format(
10979 project.clone(),
10980 FormatTrigger::Manual,
10981 FormatTarget::Buffer,
10982 cx,
10983 )
10984 })
10985 .unwrap()
10986 .await;
10987 assert_eq!(
10988 editor.update(cx, |editor, cx| editor.text(cx)),
10989 buffer_text.to_string() + prettier_format_suffix,
10990 "Test prettier formatting was not applied to the original buffer text",
10991 );
10992
10993 update_test_language_settings(cx, |settings| {
10994 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10995 });
10996 let format = editor.update(cx, |editor, cx| {
10997 editor.perform_format(
10998 project.clone(),
10999 FormatTrigger::Manual,
11000 FormatTarget::Buffer,
11001 cx,
11002 )
11003 });
11004 format.await.unwrap();
11005 assert_eq!(
11006 editor.update(cx, |editor, cx| editor.text(cx)),
11007 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11008 "Autoformatting (via test prettier) was not applied to the original buffer text",
11009 );
11010}
11011
11012#[gpui::test]
11013async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11014 init_test(cx, |_| {});
11015 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11016 let base_text = indoc! {r#"struct Row;
11017struct Row1;
11018struct Row2;
11019
11020struct Row4;
11021struct Row5;
11022struct Row6;
11023
11024struct Row8;
11025struct Row9;
11026struct Row10;"#};
11027
11028 // When addition hunks are not adjacent to carets, no hunk revert is performed
11029 assert_hunk_revert(
11030 indoc! {r#"struct Row;
11031 struct Row1;
11032 struct Row1.1;
11033 struct Row1.2;
11034 struct Row2;ˇ
11035
11036 struct Row4;
11037 struct Row5;
11038 struct Row6;
11039
11040 struct Row8;
11041 ˇstruct Row9;
11042 struct Row9.1;
11043 struct Row9.2;
11044 struct Row9.3;
11045 struct Row10;"#},
11046 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11047 indoc! {r#"struct Row;
11048 struct Row1;
11049 struct Row1.1;
11050 struct Row1.2;
11051 struct Row2;ˇ
11052
11053 struct Row4;
11054 struct Row5;
11055 struct Row6;
11056
11057 struct Row8;
11058 ˇstruct Row9;
11059 struct Row9.1;
11060 struct Row9.2;
11061 struct Row9.3;
11062 struct Row10;"#},
11063 base_text,
11064 &mut cx,
11065 );
11066 // Same for selections
11067 assert_hunk_revert(
11068 indoc! {r#"struct Row;
11069 struct Row1;
11070 struct Row2;
11071 struct Row2.1;
11072 struct Row2.2;
11073 «ˇ
11074 struct Row4;
11075 struct» Row5;
11076 «struct Row6;
11077 ˇ»
11078 struct Row9.1;
11079 struct Row9.2;
11080 struct Row9.3;
11081 struct Row8;
11082 struct Row9;
11083 struct Row10;"#},
11084 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11085 indoc! {r#"struct Row;
11086 struct Row1;
11087 struct Row2;
11088 struct Row2.1;
11089 struct Row2.2;
11090 «ˇ
11091 struct Row4;
11092 struct» Row5;
11093 «struct Row6;
11094 ˇ»
11095 struct Row9.1;
11096 struct Row9.2;
11097 struct Row9.3;
11098 struct Row8;
11099 struct Row9;
11100 struct Row10;"#},
11101 base_text,
11102 &mut cx,
11103 );
11104
11105 // When carets and selections intersect the addition hunks, those are reverted.
11106 // Adjacent carets got merged.
11107 assert_hunk_revert(
11108 indoc! {r#"struct Row;
11109 ˇ// something on the top
11110 struct Row1;
11111 struct Row2;
11112 struct Roˇw3.1;
11113 struct Row2.2;
11114 struct Row2.3;ˇ
11115
11116 struct Row4;
11117 struct ˇRow5.1;
11118 struct Row5.2;
11119 struct «Rowˇ»5.3;
11120 struct Row5;
11121 struct Row6;
11122 ˇ
11123 struct Row9.1;
11124 struct «Rowˇ»9.2;
11125 struct «ˇRow»9.3;
11126 struct Row8;
11127 struct Row9;
11128 «ˇ// something on bottom»
11129 struct Row10;"#},
11130 vec![
11131 DiffHunkStatus::Added,
11132 DiffHunkStatus::Added,
11133 DiffHunkStatus::Added,
11134 DiffHunkStatus::Added,
11135 DiffHunkStatus::Added,
11136 ],
11137 indoc! {r#"struct Row;
11138 ˇstruct Row1;
11139 struct Row2;
11140 ˇ
11141 struct Row4;
11142 ˇstruct Row5;
11143 struct Row6;
11144 ˇ
11145 ˇstruct Row8;
11146 struct Row9;
11147 ˇstruct Row10;"#},
11148 base_text,
11149 &mut cx,
11150 );
11151}
11152
11153#[gpui::test]
11154async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11155 init_test(cx, |_| {});
11156 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11157 let base_text = indoc! {r#"struct Row;
11158struct Row1;
11159struct Row2;
11160
11161struct Row4;
11162struct Row5;
11163struct Row6;
11164
11165struct Row8;
11166struct Row9;
11167struct Row10;"#};
11168
11169 // Modification hunks behave the same as the addition ones.
11170 assert_hunk_revert(
11171 indoc! {r#"struct Row;
11172 struct Row1;
11173 struct Row33;
11174 ˇ
11175 struct Row4;
11176 struct Row5;
11177 struct Row6;
11178 ˇ
11179 struct Row99;
11180 struct Row9;
11181 struct Row10;"#},
11182 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11183 indoc! {r#"struct Row;
11184 struct Row1;
11185 struct Row33;
11186 ˇ
11187 struct Row4;
11188 struct Row5;
11189 struct Row6;
11190 ˇ
11191 struct Row99;
11192 struct Row9;
11193 struct Row10;"#},
11194 base_text,
11195 &mut cx,
11196 );
11197 assert_hunk_revert(
11198 indoc! {r#"struct Row;
11199 struct Row1;
11200 struct Row33;
11201 «ˇ
11202 struct Row4;
11203 struct» Row5;
11204 «struct Row6;
11205 ˇ»
11206 struct Row99;
11207 struct Row9;
11208 struct Row10;"#},
11209 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11210 indoc! {r#"struct Row;
11211 struct Row1;
11212 struct Row33;
11213 «ˇ
11214 struct Row4;
11215 struct» Row5;
11216 «struct Row6;
11217 ˇ»
11218 struct Row99;
11219 struct Row9;
11220 struct Row10;"#},
11221 base_text,
11222 &mut cx,
11223 );
11224
11225 assert_hunk_revert(
11226 indoc! {r#"ˇstruct Row1.1;
11227 struct Row1;
11228 «ˇstr»uct Row22;
11229
11230 struct ˇRow44;
11231 struct Row5;
11232 struct «Rˇ»ow66;ˇ
11233
11234 «struˇ»ct Row88;
11235 struct Row9;
11236 struct Row1011;ˇ"#},
11237 vec![
11238 DiffHunkStatus::Modified,
11239 DiffHunkStatus::Modified,
11240 DiffHunkStatus::Modified,
11241 DiffHunkStatus::Modified,
11242 DiffHunkStatus::Modified,
11243 DiffHunkStatus::Modified,
11244 ],
11245 indoc! {r#"struct Row;
11246 ˇstruct Row1;
11247 struct Row2;
11248 ˇ
11249 struct Row4;
11250 ˇstruct Row5;
11251 struct Row6;
11252 ˇ
11253 struct Row8;
11254 ˇstruct Row9;
11255 struct Row10;ˇ"#},
11256 base_text,
11257 &mut cx,
11258 );
11259}
11260
11261#[gpui::test]
11262async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11263 init_test(cx, |_| {});
11264 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11265 let base_text = indoc! {r#"struct Row;
11266struct Row1;
11267struct Row2;
11268
11269struct Row4;
11270struct Row5;
11271struct Row6;
11272
11273struct Row8;
11274struct Row9;
11275struct Row10;"#};
11276
11277 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11278 assert_hunk_revert(
11279 indoc! {r#"struct Row;
11280 struct Row2;
11281
11282 ˇstruct Row4;
11283 struct Row5;
11284 struct Row6;
11285 ˇ
11286 struct Row8;
11287 struct Row10;"#},
11288 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11289 indoc! {r#"struct Row;
11290 struct Row2;
11291
11292 ˇstruct Row4;
11293 struct Row5;
11294 struct Row6;
11295 ˇ
11296 struct Row8;
11297 struct Row10;"#},
11298 base_text,
11299 &mut cx,
11300 );
11301 assert_hunk_revert(
11302 indoc! {r#"struct Row;
11303 struct Row2;
11304
11305 «ˇstruct Row4;
11306 struct» Row5;
11307 «struct Row6;
11308 ˇ»
11309 struct Row8;
11310 struct Row10;"#},
11311 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11312 indoc! {r#"struct Row;
11313 struct Row2;
11314
11315 «ˇstruct Row4;
11316 struct» Row5;
11317 «struct Row6;
11318 ˇ»
11319 struct Row8;
11320 struct Row10;"#},
11321 base_text,
11322 &mut cx,
11323 );
11324
11325 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11326 assert_hunk_revert(
11327 indoc! {r#"struct Row;
11328 ˇstruct Row2;
11329
11330 struct Row4;
11331 struct Row5;
11332 struct Row6;
11333
11334 struct Row8;ˇ
11335 struct Row10;"#},
11336 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11337 indoc! {r#"struct Row;
11338 struct Row1;
11339 ˇstruct Row2;
11340
11341 struct Row4;
11342 struct Row5;
11343 struct Row6;
11344
11345 struct Row8;ˇ
11346 struct Row9;
11347 struct Row10;"#},
11348 base_text,
11349 &mut cx,
11350 );
11351 assert_hunk_revert(
11352 indoc! {r#"struct Row;
11353 struct Row2«ˇ;
11354 struct Row4;
11355 struct» Row5;
11356 «struct Row6;
11357
11358 struct Row8;ˇ»
11359 struct Row10;"#},
11360 vec![
11361 DiffHunkStatus::Removed,
11362 DiffHunkStatus::Removed,
11363 DiffHunkStatus::Removed,
11364 ],
11365 indoc! {r#"struct Row;
11366 struct Row1;
11367 struct Row2«ˇ;
11368
11369 struct Row4;
11370 struct» Row5;
11371 «struct Row6;
11372
11373 struct Row8;ˇ»
11374 struct Row9;
11375 struct Row10;"#},
11376 base_text,
11377 &mut cx,
11378 );
11379}
11380
11381#[gpui::test]
11382async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11383 init_test(cx, |_| {});
11384
11385 let cols = 4;
11386 let rows = 10;
11387 let sample_text_1 = sample_text(rows, cols, 'a');
11388 assert_eq!(
11389 sample_text_1,
11390 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11391 );
11392 let sample_text_2 = sample_text(rows, cols, 'l');
11393 assert_eq!(
11394 sample_text_2,
11395 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11396 );
11397 let sample_text_3 = sample_text(rows, cols, 'v');
11398 assert_eq!(
11399 sample_text_3,
11400 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11401 );
11402
11403 fn diff_every_buffer_row(
11404 buffer: &Model<Buffer>,
11405 sample_text: String,
11406 cols: usize,
11407 cx: &mut gpui::TestAppContext,
11408 ) {
11409 // revert first character in each row, creating one large diff hunk per buffer
11410 let is_first_char = |offset: usize| offset % cols == 0;
11411 buffer.update(cx, |buffer, cx| {
11412 buffer.set_text(
11413 sample_text
11414 .chars()
11415 .enumerate()
11416 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11417 .collect::<String>(),
11418 cx,
11419 );
11420 buffer.set_diff_base(Some(sample_text), cx);
11421 });
11422 cx.executor().run_until_parked();
11423 }
11424
11425 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11426 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11427
11428 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11429 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11430
11431 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11432 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11433
11434 let multibuffer = cx.new_model(|cx| {
11435 let mut multibuffer = MultiBuffer::new(ReadWrite);
11436 multibuffer.push_excerpts(
11437 buffer_1.clone(),
11438 [
11439 ExcerptRange {
11440 context: Point::new(0, 0)..Point::new(3, 0),
11441 primary: None,
11442 },
11443 ExcerptRange {
11444 context: Point::new(5, 0)..Point::new(7, 0),
11445 primary: None,
11446 },
11447 ExcerptRange {
11448 context: Point::new(9, 0)..Point::new(10, 4),
11449 primary: None,
11450 },
11451 ],
11452 cx,
11453 );
11454 multibuffer.push_excerpts(
11455 buffer_2.clone(),
11456 [
11457 ExcerptRange {
11458 context: Point::new(0, 0)..Point::new(3, 0),
11459 primary: None,
11460 },
11461 ExcerptRange {
11462 context: Point::new(5, 0)..Point::new(7, 0),
11463 primary: None,
11464 },
11465 ExcerptRange {
11466 context: Point::new(9, 0)..Point::new(10, 4),
11467 primary: None,
11468 },
11469 ],
11470 cx,
11471 );
11472 multibuffer.push_excerpts(
11473 buffer_3.clone(),
11474 [
11475 ExcerptRange {
11476 context: Point::new(0, 0)..Point::new(3, 0),
11477 primary: None,
11478 },
11479 ExcerptRange {
11480 context: Point::new(5, 0)..Point::new(7, 0),
11481 primary: None,
11482 },
11483 ExcerptRange {
11484 context: Point::new(9, 0)..Point::new(10, 4),
11485 primary: None,
11486 },
11487 ],
11488 cx,
11489 );
11490 multibuffer
11491 });
11492
11493 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11494 editor.update(cx, |editor, cx| {
11495 assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
11496 editor.select_all(&SelectAll, cx);
11497 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11498 });
11499 cx.executor().run_until_parked();
11500 // When all ranges are selected, all buffer hunks are reverted.
11501 editor.update(cx, |editor, cx| {
11502 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");
11503 });
11504 buffer_1.update(cx, |buffer, _| {
11505 assert_eq!(buffer.text(), sample_text_1);
11506 });
11507 buffer_2.update(cx, |buffer, _| {
11508 assert_eq!(buffer.text(), sample_text_2);
11509 });
11510 buffer_3.update(cx, |buffer, _| {
11511 assert_eq!(buffer.text(), sample_text_3);
11512 });
11513
11514 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11515 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11516 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11517 editor.update(cx, |editor, cx| {
11518 editor.change_selections(None, cx, |s| {
11519 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11520 });
11521 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11522 });
11523 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11524 // but not affect buffer_2 and its related excerpts.
11525 editor.update(cx, |editor, cx| {
11526 assert_eq!(
11527 editor.text(cx),
11528 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
11529 );
11530 });
11531 buffer_1.update(cx, |buffer, _| {
11532 assert_eq!(buffer.text(), sample_text_1);
11533 });
11534 buffer_2.update(cx, |buffer, _| {
11535 assert_eq!(
11536 buffer.text(),
11537 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11538 );
11539 });
11540 buffer_3.update(cx, |buffer, _| {
11541 assert_eq!(
11542 buffer.text(),
11543 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11544 );
11545 });
11546}
11547
11548#[gpui::test]
11549async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11550 init_test(cx, |_| {});
11551
11552 let cols = 4;
11553 let rows = 10;
11554 let sample_text_1 = sample_text(rows, cols, 'a');
11555 assert_eq!(
11556 sample_text_1,
11557 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11558 );
11559 let sample_text_2 = sample_text(rows, cols, 'l');
11560 assert_eq!(
11561 sample_text_2,
11562 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11563 );
11564 let sample_text_3 = sample_text(rows, cols, 'v');
11565 assert_eq!(
11566 sample_text_3,
11567 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11568 );
11569
11570 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11571 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11572 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11573
11574 let multi_buffer = cx.new_model(|cx| {
11575 let mut multibuffer = MultiBuffer::new(ReadWrite);
11576 multibuffer.push_excerpts(
11577 buffer_1.clone(),
11578 [
11579 ExcerptRange {
11580 context: Point::new(0, 0)..Point::new(3, 0),
11581 primary: None,
11582 },
11583 ExcerptRange {
11584 context: Point::new(5, 0)..Point::new(7, 0),
11585 primary: None,
11586 },
11587 ExcerptRange {
11588 context: Point::new(9, 0)..Point::new(10, 4),
11589 primary: None,
11590 },
11591 ],
11592 cx,
11593 );
11594 multibuffer.push_excerpts(
11595 buffer_2.clone(),
11596 [
11597 ExcerptRange {
11598 context: Point::new(0, 0)..Point::new(3, 0),
11599 primary: None,
11600 },
11601 ExcerptRange {
11602 context: Point::new(5, 0)..Point::new(7, 0),
11603 primary: None,
11604 },
11605 ExcerptRange {
11606 context: Point::new(9, 0)..Point::new(10, 4),
11607 primary: None,
11608 },
11609 ],
11610 cx,
11611 );
11612 multibuffer.push_excerpts(
11613 buffer_3.clone(),
11614 [
11615 ExcerptRange {
11616 context: Point::new(0, 0)..Point::new(3, 0),
11617 primary: None,
11618 },
11619 ExcerptRange {
11620 context: Point::new(5, 0)..Point::new(7, 0),
11621 primary: None,
11622 },
11623 ExcerptRange {
11624 context: Point::new(9, 0)..Point::new(10, 4),
11625 primary: None,
11626 },
11627 ],
11628 cx,
11629 );
11630 multibuffer
11631 });
11632
11633 let fs = FakeFs::new(cx.executor());
11634 fs.insert_tree(
11635 "/a",
11636 json!({
11637 "main.rs": sample_text_1,
11638 "other.rs": sample_text_2,
11639 "lib.rs": sample_text_3,
11640 }),
11641 )
11642 .await;
11643 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11644 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11645 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11646 let multi_buffer_editor = cx.new_view(|cx| {
11647 Editor::new(
11648 EditorMode::Full,
11649 multi_buffer,
11650 Some(project.clone()),
11651 true,
11652 cx,
11653 )
11654 });
11655 let multibuffer_item_id = workspace
11656 .update(cx, |workspace, cx| {
11657 assert!(
11658 workspace.active_item(cx).is_none(),
11659 "active item should be None before the first item is added"
11660 );
11661 workspace.add_item_to_active_pane(
11662 Box::new(multi_buffer_editor.clone()),
11663 None,
11664 true,
11665 cx,
11666 );
11667 let active_item = workspace
11668 .active_item(cx)
11669 .expect("should have an active item after adding the multi buffer");
11670 assert!(
11671 !active_item.is_singleton(cx),
11672 "A multi buffer was expected to active after adding"
11673 );
11674 active_item.item_id()
11675 })
11676 .unwrap();
11677 cx.executor().run_until_parked();
11678
11679 multi_buffer_editor.update(cx, |editor, cx| {
11680 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11681 editor.open_excerpts(&OpenExcerpts, cx);
11682 });
11683 cx.executor().run_until_parked();
11684 let first_item_id = workspace
11685 .update(cx, |workspace, cx| {
11686 let active_item = workspace
11687 .active_item(cx)
11688 .expect("should have an active item after navigating into the 1st buffer");
11689 let first_item_id = active_item.item_id();
11690 assert_ne!(
11691 first_item_id, multibuffer_item_id,
11692 "Should navigate into the 1st buffer and activate it"
11693 );
11694 assert!(
11695 active_item.is_singleton(cx),
11696 "New active item should be a singleton buffer"
11697 );
11698 assert_eq!(
11699 active_item
11700 .act_as::<Editor>(cx)
11701 .expect("should have navigated into an editor for the 1st buffer")
11702 .read(cx)
11703 .text(cx),
11704 sample_text_1
11705 );
11706
11707 workspace
11708 .go_back(workspace.active_pane().downgrade(), cx)
11709 .detach_and_log_err(cx);
11710
11711 first_item_id
11712 })
11713 .unwrap();
11714 cx.executor().run_until_parked();
11715 workspace
11716 .update(cx, |workspace, cx| {
11717 let active_item = workspace
11718 .active_item(cx)
11719 .expect("should have an active item after navigating back");
11720 assert_eq!(
11721 active_item.item_id(),
11722 multibuffer_item_id,
11723 "Should navigate back to the multi buffer"
11724 );
11725 assert!(!active_item.is_singleton(cx));
11726 })
11727 .unwrap();
11728
11729 multi_buffer_editor.update(cx, |editor, cx| {
11730 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11731 s.select_ranges(Some(39..40))
11732 });
11733 editor.open_excerpts(&OpenExcerpts, cx);
11734 });
11735 cx.executor().run_until_parked();
11736 let second_item_id = workspace
11737 .update(cx, |workspace, cx| {
11738 let active_item = workspace
11739 .active_item(cx)
11740 .expect("should have an active item after navigating into the 2nd buffer");
11741 let second_item_id = active_item.item_id();
11742 assert_ne!(
11743 second_item_id, multibuffer_item_id,
11744 "Should navigate away from the multibuffer"
11745 );
11746 assert_ne!(
11747 second_item_id, first_item_id,
11748 "Should navigate into the 2nd buffer and activate it"
11749 );
11750 assert!(
11751 active_item.is_singleton(cx),
11752 "New active item should be a singleton buffer"
11753 );
11754 assert_eq!(
11755 active_item
11756 .act_as::<Editor>(cx)
11757 .expect("should have navigated into an editor")
11758 .read(cx)
11759 .text(cx),
11760 sample_text_2
11761 );
11762
11763 workspace
11764 .go_back(workspace.active_pane().downgrade(), cx)
11765 .detach_and_log_err(cx);
11766
11767 second_item_id
11768 })
11769 .unwrap();
11770 cx.executor().run_until_parked();
11771 workspace
11772 .update(cx, |workspace, cx| {
11773 let active_item = workspace
11774 .active_item(cx)
11775 .expect("should have an active item after navigating back from the 2nd buffer");
11776 assert_eq!(
11777 active_item.item_id(),
11778 multibuffer_item_id,
11779 "Should navigate back from the 2nd buffer to the multi buffer"
11780 );
11781 assert!(!active_item.is_singleton(cx));
11782 })
11783 .unwrap();
11784
11785 multi_buffer_editor.update(cx, |editor, cx| {
11786 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11787 s.select_ranges(Some(70..70))
11788 });
11789 editor.open_excerpts(&OpenExcerpts, cx);
11790 });
11791 cx.executor().run_until_parked();
11792 workspace
11793 .update(cx, |workspace, cx| {
11794 let active_item = workspace
11795 .active_item(cx)
11796 .expect("should have an active item after navigating into the 3rd buffer");
11797 let third_item_id = active_item.item_id();
11798 assert_ne!(
11799 third_item_id, multibuffer_item_id,
11800 "Should navigate into the 3rd buffer and activate it"
11801 );
11802 assert_ne!(third_item_id, first_item_id);
11803 assert_ne!(third_item_id, second_item_id);
11804 assert!(
11805 active_item.is_singleton(cx),
11806 "New active item should be a singleton buffer"
11807 );
11808 assert_eq!(
11809 active_item
11810 .act_as::<Editor>(cx)
11811 .expect("should have navigated into an editor")
11812 .read(cx)
11813 .text(cx),
11814 sample_text_3
11815 );
11816
11817 workspace
11818 .go_back(workspace.active_pane().downgrade(), cx)
11819 .detach_and_log_err(cx);
11820 })
11821 .unwrap();
11822 cx.executor().run_until_parked();
11823 workspace
11824 .update(cx, |workspace, cx| {
11825 let active_item = workspace
11826 .active_item(cx)
11827 .expect("should have an active item after navigating back from the 3rd buffer");
11828 assert_eq!(
11829 active_item.item_id(),
11830 multibuffer_item_id,
11831 "Should navigate back from the 3rd buffer to the multi buffer"
11832 );
11833 assert!(!active_item.is_singleton(cx));
11834 })
11835 .unwrap();
11836}
11837
11838#[gpui::test]
11839async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11840 init_test(cx, |_| {});
11841
11842 let mut cx = EditorTestContext::new(cx).await;
11843
11844 let diff_base = r#"
11845 use some::mod;
11846
11847 const A: u32 = 42;
11848
11849 fn main() {
11850 println!("hello");
11851
11852 println!("world");
11853 }
11854 "#
11855 .unindent();
11856
11857 cx.set_state(
11858 &r#"
11859 use some::modified;
11860
11861 ˇ
11862 fn main() {
11863 println!("hello there");
11864
11865 println!("around the");
11866 println!("world");
11867 }
11868 "#
11869 .unindent(),
11870 );
11871
11872 cx.set_diff_base(Some(&diff_base));
11873 executor.run_until_parked();
11874
11875 cx.update_editor(|editor, cx| {
11876 editor.go_to_next_hunk(&GoToHunk, cx);
11877 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11878 });
11879 executor.run_until_parked();
11880 cx.assert_diff_hunks(
11881 r#"
11882 use some::modified;
11883
11884
11885 fn main() {
11886 - println!("hello");
11887 + println!("hello there");
11888
11889 println!("around the");
11890 println!("world");
11891 }
11892 "#
11893 .unindent(),
11894 );
11895
11896 cx.update_editor(|editor, cx| {
11897 for _ in 0..3 {
11898 editor.go_to_next_hunk(&GoToHunk, cx);
11899 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11900 }
11901 });
11902 executor.run_until_parked();
11903 cx.assert_editor_state(
11904 &r#"
11905 use some::modified;
11906
11907 ˇ
11908 fn main() {
11909 println!("hello there");
11910
11911 println!("around the");
11912 println!("world");
11913 }
11914 "#
11915 .unindent(),
11916 );
11917
11918 cx.assert_diff_hunks(
11919 r#"
11920 - use some::mod;
11921 + use some::modified;
11922
11923 - const A: u32 = 42;
11924
11925 fn main() {
11926 - println!("hello");
11927 + println!("hello there");
11928
11929 + println!("around the");
11930 println!("world");
11931 }
11932 "#
11933 .unindent(),
11934 );
11935
11936 cx.update_editor(|editor, cx| {
11937 editor.cancel(&Cancel, cx);
11938 });
11939
11940 cx.assert_diff_hunks(
11941 r#"
11942 use some::modified;
11943
11944
11945 fn main() {
11946 println!("hello there");
11947
11948 println!("around the");
11949 println!("world");
11950 }
11951 "#
11952 .unindent(),
11953 );
11954}
11955
11956#[gpui::test]
11957async fn test_diff_base_change_with_expanded_diff_hunks(
11958 executor: BackgroundExecutor,
11959 cx: &mut gpui::TestAppContext,
11960) {
11961 init_test(cx, |_| {});
11962
11963 let mut cx = EditorTestContext::new(cx).await;
11964
11965 let diff_base = r#"
11966 use some::mod1;
11967 use some::mod2;
11968
11969 const A: u32 = 42;
11970 const B: u32 = 42;
11971 const C: u32 = 42;
11972
11973 fn main() {
11974 println!("hello");
11975
11976 println!("world");
11977 }
11978 "#
11979 .unindent();
11980
11981 cx.set_state(
11982 &r#"
11983 use some::mod2;
11984
11985 const A: u32 = 42;
11986 const C: u32 = 42;
11987
11988 fn main(ˇ) {
11989 //println!("hello");
11990
11991 println!("world");
11992 //
11993 //
11994 }
11995 "#
11996 .unindent(),
11997 );
11998
11999 cx.set_diff_base(Some(&diff_base));
12000 executor.run_until_parked();
12001
12002 cx.update_editor(|editor, cx| {
12003 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12004 });
12005 executor.run_until_parked();
12006 cx.assert_diff_hunks(
12007 r#"
12008 - use some::mod1;
12009 use some::mod2;
12010
12011 const A: u32 = 42;
12012 - const B: u32 = 42;
12013 const C: u32 = 42;
12014
12015 fn main() {
12016 - println!("hello");
12017 + //println!("hello");
12018
12019 println!("world");
12020 + //
12021 + //
12022 }
12023 "#
12024 .unindent(),
12025 );
12026
12027 cx.set_diff_base(Some("new diff base!"));
12028 executor.run_until_parked();
12029 cx.assert_diff_hunks(
12030 r#"
12031 use some::mod2;
12032
12033 const A: u32 = 42;
12034 const C: u32 = 42;
12035
12036 fn main() {
12037 //println!("hello");
12038
12039 println!("world");
12040 //
12041 //
12042 }
12043 "#
12044 .unindent(),
12045 );
12046
12047 cx.update_editor(|editor, cx| {
12048 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12049 });
12050 executor.run_until_parked();
12051 cx.assert_diff_hunks(
12052 r#"
12053 - new diff base!
12054 + use some::mod2;
12055 +
12056 + const A: u32 = 42;
12057 + const C: u32 = 42;
12058 +
12059 + fn main() {
12060 + //println!("hello");
12061 +
12062 + println!("world");
12063 + //
12064 + //
12065 + }
12066 "#
12067 .unindent(),
12068 );
12069}
12070
12071#[gpui::test]
12072async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12073 init_test(cx, |_| {});
12074
12075 let mut cx = EditorTestContext::new(cx).await;
12076
12077 let diff_base = r#"
12078 use some::mod1;
12079 use some::mod2;
12080
12081 const A: u32 = 42;
12082 const B: u32 = 42;
12083 const C: u32 = 42;
12084
12085 fn main() {
12086 println!("hello");
12087
12088 println!("world");
12089 }
12090
12091 fn another() {
12092 println!("another");
12093 }
12094
12095 fn another2() {
12096 println!("another2");
12097 }
12098 "#
12099 .unindent();
12100
12101 cx.set_state(
12102 &r#"
12103 «use some::mod2;
12104
12105 const A: u32 = 42;
12106 const C: u32 = 42;
12107
12108 fn main() {
12109 //println!("hello");
12110
12111 println!("world");
12112 //
12113 //ˇ»
12114 }
12115
12116 fn another() {
12117 println!("another");
12118 println!("another");
12119 }
12120
12121 println!("another2");
12122 }
12123 "#
12124 .unindent(),
12125 );
12126
12127 cx.set_diff_base(Some(&diff_base));
12128 executor.run_until_parked();
12129
12130 cx.update_editor(|editor, cx| {
12131 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12132 });
12133 executor.run_until_parked();
12134
12135 cx.assert_diff_hunks(
12136 r#"
12137 - use some::mod1;
12138 use some::mod2;
12139
12140 const A: u32 = 42;
12141 - const B: u32 = 42;
12142 const C: u32 = 42;
12143
12144 fn main() {
12145 - println!("hello");
12146 + //println!("hello");
12147
12148 println!("world");
12149 + //
12150 + //
12151 }
12152
12153 fn another() {
12154 println!("another");
12155 + println!("another");
12156 }
12157
12158 - fn another2() {
12159 println!("another2");
12160 }
12161 "#
12162 .unindent(),
12163 );
12164
12165 // Fold across some of the diff hunks. They should no longer appear expanded.
12166 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12167 cx.executor().run_until_parked();
12168
12169 // Hunks are not shown if their position is within a fold
12170 cx.assert_diff_hunks(
12171 r#"
12172 use some::mod2;
12173
12174 const A: u32 = 42;
12175 const C: u32 = 42;
12176
12177 fn main() {
12178 //println!("hello");
12179
12180 println!("world");
12181 //
12182 //
12183 }
12184
12185 fn another() {
12186 println!("another");
12187 + println!("another");
12188 }
12189
12190 - fn another2() {
12191 println!("another2");
12192 }
12193 "#
12194 .unindent(),
12195 );
12196
12197 cx.update_editor(|editor, cx| {
12198 editor.select_all(&SelectAll, cx);
12199 editor.unfold_lines(&UnfoldLines, cx);
12200 });
12201 cx.executor().run_until_parked();
12202
12203 // The deletions reappear when unfolding.
12204 cx.assert_diff_hunks(
12205 r#"
12206 - use some::mod1;
12207 use some::mod2;
12208
12209 const A: u32 = 42;
12210 - const B: u32 = 42;
12211 const C: u32 = 42;
12212
12213 fn main() {
12214 - println!("hello");
12215 + //println!("hello");
12216
12217 println!("world");
12218 + //
12219 + //
12220 }
12221
12222 fn another() {
12223 println!("another");
12224 + println!("another");
12225 }
12226
12227 - fn another2() {
12228 println!("another2");
12229 }
12230 "#
12231 .unindent(),
12232 );
12233}
12234
12235#[gpui::test]
12236async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12237 init_test(cx, |_| {});
12238
12239 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12240 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12241 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12242 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12243 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12244 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12245
12246 let buffer_1 = cx.new_model(|cx| {
12247 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
12248 buffer.set_diff_base(Some(file_1_old.into()), cx);
12249 buffer
12250 });
12251 let buffer_2 = cx.new_model(|cx| {
12252 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
12253 buffer.set_diff_base(Some(file_2_old.into()), cx);
12254 buffer
12255 });
12256 let buffer_3 = cx.new_model(|cx| {
12257 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
12258 buffer.set_diff_base(Some(file_3_old.into()), cx);
12259 buffer
12260 });
12261
12262 let multi_buffer = cx.new_model(|cx| {
12263 let mut multibuffer = MultiBuffer::new(ReadWrite);
12264 multibuffer.push_excerpts(
12265 buffer_1.clone(),
12266 [
12267 ExcerptRange {
12268 context: Point::new(0, 0)..Point::new(3, 0),
12269 primary: None,
12270 },
12271 ExcerptRange {
12272 context: Point::new(5, 0)..Point::new(7, 0),
12273 primary: None,
12274 },
12275 ExcerptRange {
12276 context: Point::new(9, 0)..Point::new(10, 3),
12277 primary: None,
12278 },
12279 ],
12280 cx,
12281 );
12282 multibuffer.push_excerpts(
12283 buffer_2.clone(),
12284 [
12285 ExcerptRange {
12286 context: Point::new(0, 0)..Point::new(3, 0),
12287 primary: None,
12288 },
12289 ExcerptRange {
12290 context: Point::new(5, 0)..Point::new(7, 0),
12291 primary: None,
12292 },
12293 ExcerptRange {
12294 context: Point::new(9, 0)..Point::new(10, 3),
12295 primary: None,
12296 },
12297 ],
12298 cx,
12299 );
12300 multibuffer.push_excerpts(
12301 buffer_3.clone(),
12302 [
12303 ExcerptRange {
12304 context: Point::new(0, 0)..Point::new(3, 0),
12305 primary: None,
12306 },
12307 ExcerptRange {
12308 context: Point::new(5, 0)..Point::new(7, 0),
12309 primary: None,
12310 },
12311 ExcerptRange {
12312 context: Point::new(9, 0)..Point::new(10, 3),
12313 primary: None,
12314 },
12315 ],
12316 cx,
12317 );
12318 multibuffer
12319 });
12320
12321 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12322 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12323 cx.run_until_parked();
12324
12325 cx.assert_editor_state(
12326 &"
12327 ˇaaa
12328 ccc
12329 ddd
12330
12331 ggg
12332 hhh
12333
12334
12335 lll
12336 mmm
12337 NNN
12338
12339 qqq
12340 rrr
12341
12342 uuu
12343 111
12344 222
12345 333
12346
12347 666
12348 777
12349
12350 000
12351 !!!"
12352 .unindent(),
12353 );
12354
12355 cx.update_editor(|editor, cx| {
12356 editor.select_all(&SelectAll, cx);
12357 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12358 });
12359 cx.executor().run_until_parked();
12360
12361 cx.assert_diff_hunks(
12362 "
12363 aaa
12364 - bbb
12365 ccc
12366 ddd
12367
12368 ggg
12369 hhh
12370
12371
12372 lll
12373 mmm
12374 - nnn
12375 + NNN
12376
12377 qqq
12378 rrr
12379
12380 uuu
12381 111
12382 222
12383 333
12384
12385 + 666
12386 777
12387
12388 000
12389 !!!"
12390 .unindent(),
12391 );
12392}
12393
12394#[gpui::test]
12395async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12396 init_test(cx, |_| {});
12397
12398 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12399 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12400
12401 let buffer = cx.new_model(|cx| {
12402 let mut buffer = Buffer::local(text.to_string(), cx);
12403 buffer.set_diff_base(Some(base.into()), cx);
12404 buffer
12405 });
12406
12407 let multi_buffer = cx.new_model(|cx| {
12408 let mut multibuffer = MultiBuffer::new(ReadWrite);
12409 multibuffer.push_excerpts(
12410 buffer.clone(),
12411 [
12412 ExcerptRange {
12413 context: Point::new(0, 0)..Point::new(2, 0),
12414 primary: None,
12415 },
12416 ExcerptRange {
12417 context: Point::new(5, 0)..Point::new(7, 0),
12418 primary: None,
12419 },
12420 ],
12421 cx,
12422 );
12423 multibuffer
12424 });
12425
12426 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12427 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12428 cx.run_until_parked();
12429
12430 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12431 cx.executor().run_until_parked();
12432
12433 cx.assert_diff_hunks(
12434 "
12435 aaa
12436 - bbb
12437 + BBB
12438
12439 - ddd
12440 - eee
12441 + EEE
12442 fff
12443 "
12444 .unindent(),
12445 );
12446}
12447
12448#[gpui::test]
12449async fn test_edits_around_expanded_insertion_hunks(
12450 executor: BackgroundExecutor,
12451 cx: &mut gpui::TestAppContext,
12452) {
12453 init_test(cx, |_| {});
12454
12455 let mut cx = EditorTestContext::new(cx).await;
12456
12457 let diff_base = r#"
12458 use some::mod1;
12459 use some::mod2;
12460
12461 const A: u32 = 42;
12462
12463 fn main() {
12464 println!("hello");
12465
12466 println!("world");
12467 }
12468 "#
12469 .unindent();
12470 executor.run_until_parked();
12471 cx.set_state(
12472 &r#"
12473 use some::mod1;
12474 use some::mod2;
12475
12476 const A: u32 = 42;
12477 const B: u32 = 42;
12478 const C: u32 = 42;
12479 ˇ
12480
12481 fn main() {
12482 println!("hello");
12483
12484 println!("world");
12485 }
12486 "#
12487 .unindent(),
12488 );
12489
12490 cx.set_diff_base(Some(&diff_base));
12491 executor.run_until_parked();
12492
12493 cx.update_editor(|editor, cx| {
12494 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12495 });
12496 executor.run_until_parked();
12497
12498 cx.assert_diff_hunks(
12499 r#"
12500 use some::mod1;
12501 use some::mod2;
12502
12503 const A: u32 = 42;
12504 + const B: u32 = 42;
12505 + const C: u32 = 42;
12506 +
12507
12508 fn main() {
12509 println!("hello");
12510
12511 println!("world");
12512 }
12513 "#
12514 .unindent(),
12515 );
12516
12517 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12518 executor.run_until_parked();
12519
12520 cx.assert_diff_hunks(
12521 r#"
12522 use some::mod1;
12523 use some::mod2;
12524
12525 const A: u32 = 42;
12526 + const B: u32 = 42;
12527 + const C: u32 = 42;
12528 + const D: u32 = 42;
12529 +
12530
12531 fn main() {
12532 println!("hello");
12533
12534 println!("world");
12535 }
12536 "#
12537 .unindent(),
12538 );
12539
12540 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12541 executor.run_until_parked();
12542
12543 cx.assert_diff_hunks(
12544 r#"
12545 use some::mod1;
12546 use some::mod2;
12547
12548 const A: u32 = 42;
12549 + const B: u32 = 42;
12550 + const C: u32 = 42;
12551 + const D: u32 = 42;
12552 + const E: u32 = 42;
12553 +
12554
12555 fn main() {
12556 println!("hello");
12557
12558 println!("world");
12559 }
12560 "#
12561 .unindent(),
12562 );
12563
12564 cx.update_editor(|editor, cx| {
12565 editor.delete_line(&DeleteLine, cx);
12566 });
12567 executor.run_until_parked();
12568
12569 cx.assert_diff_hunks(
12570 r#"
12571 use some::mod1;
12572 use some::mod2;
12573
12574 const A: u32 = 42;
12575 + const B: u32 = 42;
12576 + const C: u32 = 42;
12577 + const D: u32 = 42;
12578 + const E: u32 = 42;
12579
12580 fn main() {
12581 println!("hello");
12582
12583 println!("world");
12584 }
12585 "#
12586 .unindent(),
12587 );
12588
12589 cx.update_editor(|editor, cx| {
12590 editor.move_up(&MoveUp, cx);
12591 editor.delete_line(&DeleteLine, cx);
12592 editor.move_up(&MoveUp, cx);
12593 editor.delete_line(&DeleteLine, cx);
12594 editor.move_up(&MoveUp, cx);
12595 editor.delete_line(&DeleteLine, cx);
12596 });
12597 executor.run_until_parked();
12598 cx.assert_editor_state(
12599 &r#"
12600 use some::mod1;
12601 use some::mod2;
12602
12603 const A: u32 = 42;
12604 const B: u32 = 42;
12605 ˇ
12606 fn main() {
12607 println!("hello");
12608
12609 println!("world");
12610 }
12611 "#
12612 .unindent(),
12613 );
12614
12615 cx.assert_diff_hunks(
12616 r#"
12617 use some::mod1;
12618 use some::mod2;
12619
12620 const A: u32 = 42;
12621 + const B: u32 = 42;
12622
12623 fn main() {
12624 println!("hello");
12625
12626 println!("world");
12627 }
12628 "#
12629 .unindent(),
12630 );
12631
12632 cx.update_editor(|editor, cx| {
12633 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12634 editor.delete_line(&DeleteLine, cx);
12635 });
12636 executor.run_until_parked();
12637 cx.assert_diff_hunks(
12638 r#"
12639 use some::mod1;
12640 - use some::mod2;
12641 -
12642 - const A: u32 = 42;
12643
12644 fn main() {
12645 println!("hello");
12646
12647 println!("world");
12648 }
12649 "#
12650 .unindent(),
12651 );
12652}
12653
12654#[gpui::test]
12655async fn test_edits_around_expanded_deletion_hunks(
12656 executor: BackgroundExecutor,
12657 cx: &mut gpui::TestAppContext,
12658) {
12659 init_test(cx, |_| {});
12660
12661 let mut cx = EditorTestContext::new(cx).await;
12662
12663 let diff_base = r#"
12664 use some::mod1;
12665 use some::mod2;
12666
12667 const A: u32 = 42;
12668 const B: u32 = 42;
12669 const C: u32 = 42;
12670
12671
12672 fn main() {
12673 println!("hello");
12674
12675 println!("world");
12676 }
12677 "#
12678 .unindent();
12679 executor.run_until_parked();
12680 cx.set_state(
12681 &r#"
12682 use some::mod1;
12683 use some::mod2;
12684
12685 ˇconst B: u32 = 42;
12686 const C: u32 = 42;
12687
12688
12689 fn main() {
12690 println!("hello");
12691
12692 println!("world");
12693 }
12694 "#
12695 .unindent(),
12696 );
12697
12698 cx.set_diff_base(Some(&diff_base));
12699 executor.run_until_parked();
12700
12701 cx.update_editor(|editor, cx| {
12702 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12703 });
12704 executor.run_until_parked();
12705
12706 cx.assert_diff_hunks(
12707 r#"
12708 use some::mod1;
12709 use some::mod2;
12710
12711 - const A: u32 = 42;
12712 const B: u32 = 42;
12713 const C: u32 = 42;
12714
12715
12716 fn main() {
12717 println!("hello");
12718
12719 println!("world");
12720 }
12721 "#
12722 .unindent(),
12723 );
12724
12725 cx.update_editor(|editor, cx| {
12726 editor.delete_line(&DeleteLine, cx);
12727 });
12728 executor.run_until_parked();
12729 cx.assert_editor_state(
12730 &r#"
12731 use some::mod1;
12732 use some::mod2;
12733
12734 ˇconst C: u32 = 42;
12735
12736
12737 fn main() {
12738 println!("hello");
12739
12740 println!("world");
12741 }
12742 "#
12743 .unindent(),
12744 );
12745 cx.assert_diff_hunks(
12746 r#"
12747 use some::mod1;
12748 use some::mod2;
12749
12750 - const A: u32 = 42;
12751 - const B: u32 = 42;
12752 const C: u32 = 42;
12753
12754
12755 fn main() {
12756 println!("hello");
12757
12758 println!("world");
12759 }
12760 "#
12761 .unindent(),
12762 );
12763
12764 cx.update_editor(|editor, cx| {
12765 editor.delete_line(&DeleteLine, cx);
12766 });
12767 executor.run_until_parked();
12768 cx.assert_editor_state(
12769 &r#"
12770 use some::mod1;
12771 use some::mod2;
12772
12773 ˇ
12774
12775 fn main() {
12776 println!("hello");
12777
12778 println!("world");
12779 }
12780 "#
12781 .unindent(),
12782 );
12783 cx.assert_diff_hunks(
12784 r#"
12785 use some::mod1;
12786 use some::mod2;
12787
12788 - const A: u32 = 42;
12789 - const B: u32 = 42;
12790 - const C: u32 = 42;
12791
12792
12793 fn main() {
12794 println!("hello");
12795
12796 println!("world");
12797 }
12798 "#
12799 .unindent(),
12800 );
12801
12802 cx.update_editor(|editor, cx| {
12803 editor.handle_input("replacement", cx);
12804 });
12805 executor.run_until_parked();
12806 cx.assert_editor_state(
12807 &r#"
12808 use some::mod1;
12809 use some::mod2;
12810
12811 replacementˇ
12812
12813 fn main() {
12814 println!("hello");
12815
12816 println!("world");
12817 }
12818 "#
12819 .unindent(),
12820 );
12821 cx.assert_diff_hunks(
12822 r#"
12823 use some::mod1;
12824 use some::mod2;
12825
12826 - const A: u32 = 42;
12827 - const B: u32 = 42;
12828 - const C: u32 = 42;
12829 -
12830 + replacement
12831
12832 fn main() {
12833 println!("hello");
12834
12835 println!("world");
12836 }
12837 "#
12838 .unindent(),
12839 );
12840}
12841
12842#[gpui::test]
12843async fn test_edit_after_expanded_modification_hunk(
12844 executor: BackgroundExecutor,
12845 cx: &mut gpui::TestAppContext,
12846) {
12847 init_test(cx, |_| {});
12848
12849 let mut cx = EditorTestContext::new(cx).await;
12850
12851 let diff_base = r#"
12852 use some::mod1;
12853 use some::mod2;
12854
12855 const A: u32 = 42;
12856 const B: u32 = 42;
12857 const C: u32 = 42;
12858 const D: u32 = 42;
12859
12860
12861 fn main() {
12862 println!("hello");
12863
12864 println!("world");
12865 }"#
12866 .unindent();
12867
12868 cx.set_state(
12869 &r#"
12870 use some::mod1;
12871 use some::mod2;
12872
12873 const A: u32 = 42;
12874 const B: u32 = 42;
12875 const C: u32 = 43ˇ
12876 const D: u32 = 42;
12877
12878
12879 fn main() {
12880 println!("hello");
12881
12882 println!("world");
12883 }"#
12884 .unindent(),
12885 );
12886
12887 cx.set_diff_base(Some(&diff_base));
12888 executor.run_until_parked();
12889 cx.update_editor(|editor, cx| {
12890 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12891 });
12892 executor.run_until_parked();
12893
12894 cx.assert_diff_hunks(
12895 r#"
12896 use some::mod1;
12897 use some::mod2;
12898
12899 const A: u32 = 42;
12900 const B: u32 = 42;
12901 - const C: u32 = 42;
12902 + const C: u32 = 43
12903 const D: u32 = 42;
12904
12905
12906 fn main() {
12907 println!("hello");
12908
12909 println!("world");
12910 }"#
12911 .unindent(),
12912 );
12913
12914 cx.update_editor(|editor, cx| {
12915 editor.handle_input("\nnew_line\n", cx);
12916 });
12917 executor.run_until_parked();
12918
12919 cx.assert_diff_hunks(
12920 r#"
12921 use some::mod1;
12922 use some::mod2;
12923
12924 const A: u32 = 42;
12925 const B: u32 = 42;
12926 - const C: u32 = 42;
12927 + const C: u32 = 43
12928 + new_line
12929 +
12930 const D: u32 = 42;
12931
12932
12933 fn main() {
12934 println!("hello");
12935
12936 println!("world");
12937 }"#
12938 .unindent(),
12939 );
12940}
12941
12942async fn setup_indent_guides_editor(
12943 text: &str,
12944 cx: &mut gpui::TestAppContext,
12945) -> (BufferId, EditorTestContext) {
12946 init_test(cx, |_| {});
12947
12948 let mut cx = EditorTestContext::new(cx).await;
12949
12950 let buffer_id = cx.update_editor(|editor, cx| {
12951 editor.set_text(text, cx);
12952 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12953
12954 buffer_ids[0]
12955 });
12956
12957 (buffer_id, cx)
12958}
12959
12960fn assert_indent_guides(
12961 range: Range<u32>,
12962 expected: Vec<IndentGuide>,
12963 active_indices: Option<Vec<usize>>,
12964 cx: &mut EditorTestContext,
12965) {
12966 let indent_guides = cx.update_editor(|editor, cx| {
12967 let snapshot = editor.snapshot(cx).display_snapshot;
12968 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12969 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12970 true,
12971 &snapshot,
12972 cx,
12973 );
12974
12975 indent_guides.sort_by(|a, b| {
12976 a.depth.cmp(&b.depth).then(
12977 a.start_row
12978 .cmp(&b.start_row)
12979 .then(a.end_row.cmp(&b.end_row)),
12980 )
12981 });
12982 indent_guides
12983 });
12984
12985 if let Some(expected) = active_indices {
12986 let active_indices = cx.update_editor(|editor, cx| {
12987 let snapshot = editor.snapshot(cx).display_snapshot;
12988 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12989 });
12990
12991 assert_eq!(
12992 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12993 expected,
12994 "Active indent guide indices do not match"
12995 );
12996 }
12997
12998 let expected: Vec<_> = expected
12999 .into_iter()
13000 .map(|guide| MultiBufferIndentGuide {
13001 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13002 buffer: guide,
13003 })
13004 .collect();
13005
13006 assert_eq!(indent_guides, expected, "Indent guides do not match");
13007}
13008
13009fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13010 IndentGuide {
13011 buffer_id,
13012 start_row,
13013 end_row,
13014 depth,
13015 tab_size: 4,
13016 settings: IndentGuideSettings {
13017 enabled: true,
13018 line_width: 1,
13019 active_line_width: 1,
13020 ..Default::default()
13021 },
13022 }
13023}
13024
13025#[gpui::test]
13026async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13027 let (buffer_id, mut cx) = setup_indent_guides_editor(
13028 &"
13029 fn main() {
13030 let a = 1;
13031 }"
13032 .unindent(),
13033 cx,
13034 )
13035 .await;
13036
13037 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13038}
13039
13040#[gpui::test]
13041async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13042 let (buffer_id, mut cx) = setup_indent_guides_editor(
13043 &"
13044 fn main() {
13045 let a = 1;
13046 let b = 2;
13047 }"
13048 .unindent(),
13049 cx,
13050 )
13051 .await;
13052
13053 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13054}
13055
13056#[gpui::test]
13057async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13058 let (buffer_id, mut cx) = setup_indent_guides_editor(
13059 &"
13060 fn main() {
13061 let a = 1;
13062 if a == 3 {
13063 let b = 2;
13064 } else {
13065 let c = 3;
13066 }
13067 }"
13068 .unindent(),
13069 cx,
13070 )
13071 .await;
13072
13073 assert_indent_guides(
13074 0..8,
13075 vec![
13076 indent_guide(buffer_id, 1, 6, 0),
13077 indent_guide(buffer_id, 3, 3, 1),
13078 indent_guide(buffer_id, 5, 5, 1),
13079 ],
13080 None,
13081 &mut cx,
13082 );
13083}
13084
13085#[gpui::test]
13086async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13087 let (buffer_id, mut cx) = setup_indent_guides_editor(
13088 &"
13089 fn main() {
13090 let a = 1;
13091 let b = 2;
13092 let c = 3;
13093 }"
13094 .unindent(),
13095 cx,
13096 )
13097 .await;
13098
13099 assert_indent_guides(
13100 0..5,
13101 vec![
13102 indent_guide(buffer_id, 1, 3, 0),
13103 indent_guide(buffer_id, 2, 2, 1),
13104 ],
13105 None,
13106 &mut cx,
13107 );
13108}
13109
13110#[gpui::test]
13111async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13112 let (buffer_id, mut cx) = setup_indent_guides_editor(
13113 &"
13114 fn main() {
13115 let a = 1;
13116
13117 let c = 3;
13118 }"
13119 .unindent(),
13120 cx,
13121 )
13122 .await;
13123
13124 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13125}
13126
13127#[gpui::test]
13128async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13129 let (buffer_id, mut cx) = setup_indent_guides_editor(
13130 &"
13131 fn main() {
13132 let a = 1;
13133
13134 let c = 3;
13135
13136 if a == 3 {
13137 let b = 2;
13138 } else {
13139 let c = 3;
13140 }
13141 }"
13142 .unindent(),
13143 cx,
13144 )
13145 .await;
13146
13147 assert_indent_guides(
13148 0..11,
13149 vec![
13150 indent_guide(buffer_id, 1, 9, 0),
13151 indent_guide(buffer_id, 6, 6, 1),
13152 indent_guide(buffer_id, 8, 8, 1),
13153 ],
13154 None,
13155 &mut cx,
13156 );
13157}
13158
13159#[gpui::test]
13160async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13161 let (buffer_id, mut cx) = setup_indent_guides_editor(
13162 &"
13163 fn main() {
13164 let a = 1;
13165
13166 let c = 3;
13167
13168 if a == 3 {
13169 let b = 2;
13170 } else {
13171 let c = 3;
13172 }
13173 }"
13174 .unindent(),
13175 cx,
13176 )
13177 .await;
13178
13179 assert_indent_guides(
13180 1..11,
13181 vec![
13182 indent_guide(buffer_id, 1, 9, 0),
13183 indent_guide(buffer_id, 6, 6, 1),
13184 indent_guide(buffer_id, 8, 8, 1),
13185 ],
13186 None,
13187 &mut cx,
13188 );
13189}
13190
13191#[gpui::test]
13192async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13193 let (buffer_id, mut cx) = setup_indent_guides_editor(
13194 &"
13195 fn main() {
13196 let a = 1;
13197
13198 let c = 3;
13199
13200 if a == 3 {
13201 let b = 2;
13202 } else {
13203 let c = 3;
13204 }
13205 }"
13206 .unindent(),
13207 cx,
13208 )
13209 .await;
13210
13211 assert_indent_guides(
13212 1..10,
13213 vec![
13214 indent_guide(buffer_id, 1, 9, 0),
13215 indent_guide(buffer_id, 6, 6, 1),
13216 indent_guide(buffer_id, 8, 8, 1),
13217 ],
13218 None,
13219 &mut cx,
13220 );
13221}
13222
13223#[gpui::test]
13224async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13225 let (buffer_id, mut cx) = setup_indent_guides_editor(
13226 &"
13227 block1
13228 block2
13229 block3
13230 block4
13231 block2
13232 block1
13233 block1"
13234 .unindent(),
13235 cx,
13236 )
13237 .await;
13238
13239 assert_indent_guides(
13240 1..10,
13241 vec![
13242 indent_guide(buffer_id, 1, 4, 0),
13243 indent_guide(buffer_id, 2, 3, 1),
13244 indent_guide(buffer_id, 3, 3, 2),
13245 ],
13246 None,
13247 &mut cx,
13248 );
13249}
13250
13251#[gpui::test]
13252async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13253 let (buffer_id, mut cx) = setup_indent_guides_editor(
13254 &"
13255 block1
13256 block2
13257 block3
13258
13259 block1
13260 block1"
13261 .unindent(),
13262 cx,
13263 )
13264 .await;
13265
13266 assert_indent_guides(
13267 0..6,
13268 vec![
13269 indent_guide(buffer_id, 1, 2, 0),
13270 indent_guide(buffer_id, 2, 2, 1),
13271 ],
13272 None,
13273 &mut cx,
13274 );
13275}
13276
13277#[gpui::test]
13278async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13279 let (buffer_id, mut cx) = setup_indent_guides_editor(
13280 &"
13281 block1
13282
13283
13284
13285 block2
13286 "
13287 .unindent(),
13288 cx,
13289 )
13290 .await;
13291
13292 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13293}
13294
13295#[gpui::test]
13296async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13297 let (buffer_id, mut cx) = setup_indent_guides_editor(
13298 &"
13299 def a:
13300 \tb = 3
13301 \tif True:
13302 \t\tc = 4
13303 \t\td = 5
13304 \tprint(b)
13305 "
13306 .unindent(),
13307 cx,
13308 )
13309 .await;
13310
13311 assert_indent_guides(
13312 0..6,
13313 vec![
13314 indent_guide(buffer_id, 1, 6, 0),
13315 indent_guide(buffer_id, 3, 4, 1),
13316 ],
13317 None,
13318 &mut cx,
13319 );
13320}
13321
13322#[gpui::test]
13323async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13324 let (buffer_id, mut cx) = setup_indent_guides_editor(
13325 &"
13326 fn main() {
13327 let a = 1;
13328 }"
13329 .unindent(),
13330 cx,
13331 )
13332 .await;
13333
13334 cx.update_editor(|editor, cx| {
13335 editor.change_selections(None, cx, |s| {
13336 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13337 });
13338 });
13339
13340 assert_indent_guides(
13341 0..3,
13342 vec![indent_guide(buffer_id, 1, 1, 0)],
13343 Some(vec![0]),
13344 &mut cx,
13345 );
13346}
13347
13348#[gpui::test]
13349async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13350 let (buffer_id, mut cx) = setup_indent_guides_editor(
13351 &"
13352 fn main() {
13353 if 1 == 2 {
13354 let a = 1;
13355 }
13356 }"
13357 .unindent(),
13358 cx,
13359 )
13360 .await;
13361
13362 cx.update_editor(|editor, cx| {
13363 editor.change_selections(None, cx, |s| {
13364 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13365 });
13366 });
13367
13368 assert_indent_guides(
13369 0..4,
13370 vec![
13371 indent_guide(buffer_id, 1, 3, 0),
13372 indent_guide(buffer_id, 2, 2, 1),
13373 ],
13374 Some(vec![1]),
13375 &mut cx,
13376 );
13377
13378 cx.update_editor(|editor, cx| {
13379 editor.change_selections(None, cx, |s| {
13380 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13381 });
13382 });
13383
13384 assert_indent_guides(
13385 0..4,
13386 vec![
13387 indent_guide(buffer_id, 1, 3, 0),
13388 indent_guide(buffer_id, 2, 2, 1),
13389 ],
13390 Some(vec![1]),
13391 &mut cx,
13392 );
13393
13394 cx.update_editor(|editor, cx| {
13395 editor.change_selections(None, cx, |s| {
13396 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13397 });
13398 });
13399
13400 assert_indent_guides(
13401 0..4,
13402 vec![
13403 indent_guide(buffer_id, 1, 3, 0),
13404 indent_guide(buffer_id, 2, 2, 1),
13405 ],
13406 Some(vec![0]),
13407 &mut cx,
13408 );
13409}
13410
13411#[gpui::test]
13412async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13413 let (buffer_id, mut cx) = setup_indent_guides_editor(
13414 &"
13415 fn main() {
13416 let a = 1;
13417
13418 let b = 2;
13419 }"
13420 .unindent(),
13421 cx,
13422 )
13423 .await;
13424
13425 cx.update_editor(|editor, cx| {
13426 editor.change_selections(None, cx, |s| {
13427 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13428 });
13429 });
13430
13431 assert_indent_guides(
13432 0..5,
13433 vec![indent_guide(buffer_id, 1, 3, 0)],
13434 Some(vec![0]),
13435 &mut cx,
13436 );
13437}
13438
13439#[gpui::test]
13440async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13441 let (buffer_id, mut cx) = setup_indent_guides_editor(
13442 &"
13443 def m:
13444 a = 1
13445 pass"
13446 .unindent(),
13447 cx,
13448 )
13449 .await;
13450
13451 cx.update_editor(|editor, cx| {
13452 editor.change_selections(None, cx, |s| {
13453 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13454 });
13455 });
13456
13457 assert_indent_guides(
13458 0..3,
13459 vec![indent_guide(buffer_id, 1, 2, 0)],
13460 Some(vec![0]),
13461 &mut cx,
13462 );
13463}
13464
13465#[gpui::test]
13466fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13467 init_test(cx, |_| {});
13468
13469 let editor = cx.add_window(|cx| {
13470 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13471 build_editor(buffer, cx)
13472 });
13473
13474 let render_args = Arc::new(Mutex::new(None));
13475 let snapshot = editor
13476 .update(cx, |editor, cx| {
13477 let snapshot = editor.buffer().read(cx).snapshot(cx);
13478 let range =
13479 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13480
13481 struct RenderArgs {
13482 row: MultiBufferRow,
13483 folded: bool,
13484 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13485 }
13486
13487 let crease = Crease::inline(
13488 range,
13489 FoldPlaceholder::test(),
13490 {
13491 let toggle_callback = render_args.clone();
13492 move |row, folded, callback, _cx| {
13493 *toggle_callback.lock() = Some(RenderArgs {
13494 row,
13495 folded,
13496 callback,
13497 });
13498 div()
13499 }
13500 },
13501 |_row, _folded, _cx| div(),
13502 );
13503
13504 editor.insert_creases(Some(crease), cx);
13505 let snapshot = editor.snapshot(cx);
13506 let _div =
13507 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13508 snapshot
13509 })
13510 .unwrap();
13511
13512 let render_args = render_args.lock().take().unwrap();
13513 assert_eq!(render_args.row, MultiBufferRow(1));
13514 assert!(!render_args.folded);
13515 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13516
13517 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13518 .unwrap();
13519 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13520 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13521
13522 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13523 .unwrap();
13524 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13525 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13526}
13527
13528#[gpui::test]
13529async fn test_input_text(cx: &mut gpui::TestAppContext) {
13530 init_test(cx, |_| {});
13531 let mut cx = EditorTestContext::new(cx).await;
13532
13533 cx.set_state(
13534 &r#"ˇone
13535 two
13536
13537 three
13538 fourˇ
13539 five
13540
13541 siˇx"#
13542 .unindent(),
13543 );
13544
13545 cx.dispatch_action(HandleInput(String::new()));
13546 cx.assert_editor_state(
13547 &r#"ˇone
13548 two
13549
13550 three
13551 fourˇ
13552 five
13553
13554 siˇx"#
13555 .unindent(),
13556 );
13557
13558 cx.dispatch_action(HandleInput("AAAA".to_string()));
13559 cx.assert_editor_state(
13560 &r#"AAAAˇone
13561 two
13562
13563 three
13564 fourAAAAˇ
13565 five
13566
13567 siAAAAˇx"#
13568 .unindent(),
13569 );
13570}
13571
13572#[gpui::test]
13573async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13574 init_test(cx, |_| {});
13575
13576 let mut cx = EditorTestContext::new(cx).await;
13577 cx.set_state(
13578 r#"let foo = 1;
13579let foo = 2;
13580let foo = 3;
13581let fooˇ = 4;
13582let foo = 5;
13583let foo = 6;
13584let foo = 7;
13585let foo = 8;
13586let foo = 9;
13587let foo = 10;
13588let foo = 11;
13589let foo = 12;
13590let foo = 13;
13591let foo = 14;
13592let foo = 15;"#,
13593 );
13594
13595 cx.update_editor(|e, cx| {
13596 assert_eq!(
13597 e.next_scroll_position,
13598 NextScrollCursorCenterTopBottom::Center,
13599 "Default next scroll direction is center",
13600 );
13601
13602 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13603 assert_eq!(
13604 e.next_scroll_position,
13605 NextScrollCursorCenterTopBottom::Top,
13606 "After center, next scroll direction should be top",
13607 );
13608
13609 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13610 assert_eq!(
13611 e.next_scroll_position,
13612 NextScrollCursorCenterTopBottom::Bottom,
13613 "After top, next scroll direction should be bottom",
13614 );
13615
13616 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13617 assert_eq!(
13618 e.next_scroll_position,
13619 NextScrollCursorCenterTopBottom::Center,
13620 "After bottom, scrolling should start over",
13621 );
13622
13623 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13624 assert_eq!(
13625 e.next_scroll_position,
13626 NextScrollCursorCenterTopBottom::Top,
13627 "Scrolling continues if retriggered fast enough"
13628 );
13629 });
13630
13631 cx.executor()
13632 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13633 cx.executor().run_until_parked();
13634 cx.update_editor(|e, _| {
13635 assert_eq!(
13636 e.next_scroll_position,
13637 NextScrollCursorCenterTopBottom::Center,
13638 "If scrolling is not triggered fast enough, it should reset"
13639 );
13640 });
13641}
13642
13643#[gpui::test]
13644async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13645 init_test(cx, |_| {});
13646 let mut cx = EditorLspTestContext::new_rust(
13647 lsp::ServerCapabilities {
13648 definition_provider: Some(lsp::OneOf::Left(true)),
13649 references_provider: Some(lsp::OneOf::Left(true)),
13650 ..lsp::ServerCapabilities::default()
13651 },
13652 cx,
13653 )
13654 .await;
13655
13656 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13657 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13658 move |params, _| async move {
13659 if empty_go_to_definition {
13660 Ok(None)
13661 } else {
13662 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13663 uri: params.text_document_position_params.text_document.uri,
13664 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13665 })))
13666 }
13667 },
13668 );
13669 let references =
13670 cx.lsp
13671 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13672 Ok(Some(vec![lsp::Location {
13673 uri: params.text_document_position.text_document.uri,
13674 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13675 }]))
13676 });
13677 (go_to_definition, references)
13678 };
13679
13680 cx.set_state(
13681 &r#"fn one() {
13682 let mut a = ˇtwo();
13683 }
13684
13685 fn two() {}"#
13686 .unindent(),
13687 );
13688 set_up_lsp_handlers(false, &mut cx);
13689 let navigated = cx
13690 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13691 .await
13692 .expect("Failed to navigate to definition");
13693 assert_eq!(
13694 navigated,
13695 Navigated::Yes,
13696 "Should have navigated to definition from the GetDefinition response"
13697 );
13698 cx.assert_editor_state(
13699 &r#"fn one() {
13700 let mut a = two();
13701 }
13702
13703 fn «twoˇ»() {}"#
13704 .unindent(),
13705 );
13706
13707 let editors = cx.update_workspace(|workspace, cx| {
13708 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13709 });
13710 cx.update_editor(|_, test_editor_cx| {
13711 assert_eq!(
13712 editors.len(),
13713 1,
13714 "Initially, only one, test, editor should be open in the workspace"
13715 );
13716 assert_eq!(
13717 test_editor_cx.view(),
13718 editors.last().expect("Asserted len is 1")
13719 );
13720 });
13721
13722 set_up_lsp_handlers(true, &mut cx);
13723 let navigated = cx
13724 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13725 .await
13726 .expect("Failed to navigate to lookup references");
13727 assert_eq!(
13728 navigated,
13729 Navigated::Yes,
13730 "Should have navigated to references as a fallback after empty GoToDefinition response"
13731 );
13732 // We should not change the selections in the existing file,
13733 // if opening another milti buffer with the references
13734 cx.assert_editor_state(
13735 &r#"fn one() {
13736 let mut a = two();
13737 }
13738
13739 fn «twoˇ»() {}"#
13740 .unindent(),
13741 );
13742 let editors = cx.update_workspace(|workspace, cx| {
13743 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13744 });
13745 cx.update_editor(|_, test_editor_cx| {
13746 assert_eq!(
13747 editors.len(),
13748 2,
13749 "After falling back to references search, we open a new editor with the results"
13750 );
13751 let references_fallback_text = editors
13752 .into_iter()
13753 .find(|new_editor| new_editor != test_editor_cx.view())
13754 .expect("Should have one non-test editor now")
13755 .read(test_editor_cx)
13756 .text(test_editor_cx);
13757 assert_eq!(
13758 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13759 "Should use the range from the references response and not the GoToDefinition one"
13760 );
13761 });
13762}
13763
13764#[gpui::test]
13765async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13766 init_test(cx, |_| {});
13767
13768 let language = Arc::new(Language::new(
13769 LanguageConfig::default(),
13770 Some(tree_sitter_rust::LANGUAGE.into()),
13771 ));
13772
13773 let text = r#"
13774 #[cfg(test)]
13775 mod tests() {
13776 #[test]
13777 fn runnable_1() {
13778 let a = 1;
13779 }
13780
13781 #[test]
13782 fn runnable_2() {
13783 let a = 1;
13784 let b = 2;
13785 }
13786 }
13787 "#
13788 .unindent();
13789
13790 let fs = FakeFs::new(cx.executor());
13791 fs.insert_file("/file.rs", Default::default()).await;
13792
13793 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13794 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13795 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13796 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13797 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13798
13799 let editor = cx.new_view(|cx| {
13800 Editor::new(
13801 EditorMode::Full,
13802 multi_buffer,
13803 Some(project.clone()),
13804 true,
13805 cx,
13806 )
13807 });
13808
13809 editor.update(cx, |editor, cx| {
13810 editor.tasks.insert(
13811 (buffer.read(cx).remote_id(), 3),
13812 RunnableTasks {
13813 templates: vec![],
13814 offset: MultiBufferOffset(43),
13815 column: 0,
13816 extra_variables: HashMap::default(),
13817 context_range: BufferOffset(43)..BufferOffset(85),
13818 },
13819 );
13820 editor.tasks.insert(
13821 (buffer.read(cx).remote_id(), 8),
13822 RunnableTasks {
13823 templates: vec![],
13824 offset: MultiBufferOffset(86),
13825 column: 0,
13826 extra_variables: HashMap::default(),
13827 context_range: BufferOffset(86)..BufferOffset(191),
13828 },
13829 );
13830
13831 // Test finding task when cursor is inside function body
13832 editor.change_selections(None, cx, |s| {
13833 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13834 });
13835 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13836 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13837
13838 // Test finding task when cursor is on function name
13839 editor.change_selections(None, cx, |s| {
13840 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13841 });
13842 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13843 assert_eq!(row, 8, "Should find task when cursor is on function name");
13844 });
13845}
13846
13847fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13848 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13849 point..point
13850}
13851
13852fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13853 let (text, ranges) = marked_text_ranges(marked_text, true);
13854 assert_eq!(view.text(cx), text);
13855 assert_eq!(
13856 view.selections.ranges(cx),
13857 ranges,
13858 "Assert selections are {}",
13859 marked_text
13860 );
13861}
13862
13863pub fn handle_signature_help_request(
13864 cx: &mut EditorLspTestContext,
13865 mocked_response: lsp::SignatureHelp,
13866) -> impl Future<Output = ()> {
13867 let mut request =
13868 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13869 let mocked_response = mocked_response.clone();
13870 async move { Ok(Some(mocked_response)) }
13871 });
13872
13873 async move {
13874 request.next().await;
13875 }
13876}
13877
13878/// Handle completion request passing a marked string specifying where the completion
13879/// should be triggered from using '|' character, what range should be replaced, and what completions
13880/// should be returned using '<' and '>' to delimit the range
13881pub fn handle_completion_request(
13882 cx: &mut EditorLspTestContext,
13883 marked_string: &str,
13884 completions: Vec<&'static str>,
13885 counter: Arc<AtomicUsize>,
13886) -> impl Future<Output = ()> {
13887 let complete_from_marker: TextRangeMarker = '|'.into();
13888 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13889 let (_, mut marked_ranges) = marked_text_ranges_by(
13890 marked_string,
13891 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13892 );
13893
13894 let complete_from_position =
13895 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13896 let replace_range =
13897 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13898
13899 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13900 let completions = completions.clone();
13901 counter.fetch_add(1, atomic::Ordering::Release);
13902 async move {
13903 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13904 assert_eq!(
13905 params.text_document_position.position,
13906 complete_from_position
13907 );
13908 Ok(Some(lsp::CompletionResponse::Array(
13909 completions
13910 .iter()
13911 .map(|completion_text| lsp::CompletionItem {
13912 label: completion_text.to_string(),
13913 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13914 range: replace_range,
13915 new_text: completion_text.to_string(),
13916 })),
13917 ..Default::default()
13918 })
13919 .collect(),
13920 )))
13921 }
13922 });
13923
13924 async move {
13925 request.next().await;
13926 }
13927}
13928
13929fn handle_resolve_completion_request(
13930 cx: &mut EditorLspTestContext,
13931 edits: Option<Vec<(&'static str, &'static str)>>,
13932) -> impl Future<Output = ()> {
13933 let edits = edits.map(|edits| {
13934 edits
13935 .iter()
13936 .map(|(marked_string, new_text)| {
13937 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13938 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13939 lsp::TextEdit::new(replace_range, new_text.to_string())
13940 })
13941 .collect::<Vec<_>>()
13942 });
13943
13944 let mut request =
13945 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13946 let edits = edits.clone();
13947 async move {
13948 Ok(lsp::CompletionItem {
13949 additional_text_edits: edits,
13950 ..Default::default()
13951 })
13952 }
13953 });
13954
13955 async move {
13956 request.next().await;
13957 }
13958}
13959
13960pub(crate) fn update_test_language_settings(
13961 cx: &mut TestAppContext,
13962 f: impl Fn(&mut AllLanguageSettingsContent),
13963) {
13964 cx.update(|cx| {
13965 SettingsStore::update_global(cx, |store, cx| {
13966 store.update_user_settings::<AllLanguageSettings>(cx, f);
13967 });
13968 });
13969}
13970
13971pub(crate) fn update_test_project_settings(
13972 cx: &mut TestAppContext,
13973 f: impl Fn(&mut ProjectSettings),
13974) {
13975 cx.update(|cx| {
13976 SettingsStore::update_global(cx, |store, cx| {
13977 store.update_user_settings::<ProjectSettings>(cx, f);
13978 });
13979 });
13980}
13981
13982pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13983 cx.update(|cx| {
13984 assets::Assets.load_test_fonts(cx);
13985 let store = SettingsStore::test(cx);
13986 cx.set_global(store);
13987 theme::init(theme::LoadThemes::JustBase, cx);
13988 release_channel::init(SemanticVersion::default(), cx);
13989 client::init_settings(cx);
13990 language::init(cx);
13991 Project::init_settings(cx);
13992 workspace::init_settings(cx);
13993 crate::init(cx);
13994 });
13995
13996 update_test_language_settings(cx, f);
13997}
13998
13999#[track_caller]
14000fn assert_hunk_revert(
14001 not_reverted_text_with_selections: &str,
14002 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14003 expected_reverted_text_with_selections: &str,
14004 base_text: &str,
14005 cx: &mut EditorLspTestContext,
14006) {
14007 cx.set_state(not_reverted_text_with_selections);
14008 cx.update_editor(|editor, cx| {
14009 editor
14010 .buffer()
14011 .read(cx)
14012 .as_singleton()
14013 .unwrap()
14014 .update(cx, |buffer, cx| {
14015 buffer.set_diff_base(Some(base_text.into()), cx);
14016 });
14017 });
14018 cx.executor().run_until_parked();
14019
14020 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14021 let snapshot = editor.buffer().read(cx).snapshot(cx);
14022 let reverted_hunk_statuses = snapshot
14023 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
14024 .map(|hunk| hunk_status(&hunk))
14025 .collect::<Vec<_>>();
14026
14027 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14028 reverted_hunk_statuses
14029 });
14030 cx.executor().run_until_parked();
14031 cx.assert_editor_state(expected_reverted_text_with_selections);
14032 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14033}