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 unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
173 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
174 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
175 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
176
177 _ = editor.update(cx, |editor, cx| {
178 editor.start_transaction_at(now, cx);
179 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
180
181 editor.insert("cd", cx);
182 editor.end_transaction_at(now, cx);
183 assert_eq!(editor.text(cx), "12cd56");
184 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
185
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
188 editor.insert("e", cx);
189 editor.end_transaction_at(now, cx);
190 assert_eq!(editor.text(cx), "12cde6");
191 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
192
193 now += group_interval + Duration::from_millis(1);
194 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
195
196 // Simulate an edit in another editor
197 buffer.update(cx, |buffer, cx| {
198 buffer.start_transaction_at(now, cx);
199 buffer.edit([(0..1, "a")], None, cx);
200 buffer.edit([(1..1, "b")], None, cx);
201 buffer.end_transaction_at(now, cx);
202 });
203
204 assert_eq!(editor.text(cx), "ab2cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
206
207 // Last transaction happened past the group interval in a different editor.
208 // Undo it individually and don't restore selections.
209 editor.undo(&Undo, cx);
210 assert_eq!(editor.text(cx), "12cde6");
211 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
212
213 // First two transactions happened within the group interval in this editor.
214 // Undo them together and restore selections.
215 editor.undo(&Undo, cx);
216 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
217 assert_eq!(editor.text(cx), "123456");
218 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
219
220 // Redo the first two transactions together.
221 editor.redo(&Redo, cx);
222 assert_eq!(editor.text(cx), "12cde6");
223 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
224
225 // Redo the last transaction on its own.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "ab2cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
229
230 // Test empty transactions.
231 editor.start_transaction_at(now, cx);
232 editor.end_transaction_at(now, cx);
233 editor.undo(&Undo, cx);
234 assert_eq!(editor.text(cx), "12cde6");
235 });
236}
237
238#[gpui::test]
239fn test_ime_composition(cx: &mut TestAppContext) {
240 init_test(cx, |_| {});
241
242 let buffer = cx.new_model(|cx| {
243 let mut buffer = language::Buffer::local("abcde", cx);
244 // Ensure automatic grouping doesn't occur.
245 buffer.set_group_interval(Duration::ZERO);
246 buffer
247 });
248
249 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
250 cx.add_window(|cx| {
251 let mut editor = build_editor(buffer.clone(), cx);
252
253 // Start a new IME composition.
254 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
255 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
257 assert_eq!(editor.text(cx), "äbcde");
258 assert_eq!(
259 editor.marked_text_ranges(cx),
260 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
261 );
262
263 // Finalize IME composition.
264 editor.replace_text_in_range(None, "ā", cx);
265 assert_eq!(editor.text(cx), "ābcde");
266 assert_eq!(editor.marked_text_ranges(cx), None);
267
268 // IME composition edits are grouped and are undone/redone at once.
269 editor.undo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "abcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272 editor.redo(&Default::default(), cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // Start a new IME composition.
277 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
281 );
282
283 // Undoing during an IME composition cancels it.
284 editor.undo(&Default::default(), cx);
285 assert_eq!(editor.text(cx), "ābcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
289 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
290 assert_eq!(editor.text(cx), "ābcdè");
291 assert_eq!(
292 editor.marked_text_ranges(cx),
293 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
294 );
295
296 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
297 editor.replace_text_in_range(Some(4..999), "ę", cx);
298 assert_eq!(editor.text(cx), "ābcdę");
299 assert_eq!(editor.marked_text_ranges(cx), None);
300
301 // Start a new IME composition with multiple cursors.
302 editor.change_selections(None, cx, |s| {
303 s.select_ranges([
304 OffsetUtf16(1)..OffsetUtf16(1),
305 OffsetUtf16(3)..OffsetUtf16(3),
306 OffsetUtf16(5)..OffsetUtf16(5),
307 ])
308 });
309 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
310 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
311 assert_eq!(
312 editor.marked_text_ranges(cx),
313 Some(vec![
314 OffsetUtf16(0)..OffsetUtf16(3),
315 OffsetUtf16(4)..OffsetUtf16(7),
316 OffsetUtf16(8)..OffsetUtf16(11)
317 ])
318 );
319
320 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
321 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
322 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
323 assert_eq!(
324 editor.marked_text_ranges(cx),
325 Some(vec![
326 OffsetUtf16(1)..OffsetUtf16(2),
327 OffsetUtf16(5)..OffsetUtf16(6),
328 OffsetUtf16(9)..OffsetUtf16(10)
329 ])
330 );
331
332 // Finalize IME composition with multiple cursors.
333 editor.replace_text_in_range(Some(9..10), "2", cx);
334 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
335 assert_eq!(editor.marked_text_ranges(cx), None);
336
337 editor
338 });
339}
340
341#[gpui::test]
342fn test_selection_with_mouse(cx: &mut TestAppContext) {
343 init_test(cx, |_| {});
344
345 let editor = cx.add_window(|cx| {
346 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
347 build_editor(buffer, cx)
348 });
349
350 _ = editor.update(cx, |view, cx| {
351 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
352 });
353 assert_eq!(
354 editor
355 .update(cx, |view, cx| view.selections.display_ranges(cx))
356 .unwrap(),
357 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
358 );
359
360 _ = editor.update(cx, |view, cx| {
361 view.update_selection(
362 DisplayPoint::new(DisplayRow(3), 3),
363 0,
364 gpui::Point::<f32>::default(),
365 cx,
366 );
367 });
368
369 assert_eq!(
370 editor
371 .update(cx, |view, cx| view.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
374 );
375
376 _ = editor.update(cx, |view, cx| {
377 view.update_selection(
378 DisplayPoint::new(DisplayRow(1), 1),
379 0,
380 gpui::Point::<f32>::default(),
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |view, cx| view.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
390 );
391
392 _ = editor.update(cx, |view, cx| {
393 view.end_selection(cx);
394 view.update_selection(
395 DisplayPoint::new(DisplayRow(3), 3),
396 0,
397 gpui::Point::<f32>::default(),
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |view, cx| view.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |view, cx| {
410 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
411 view.update_selection(
412 DisplayPoint::new(DisplayRow(0), 0),
413 0,
414 gpui::Point::<f32>::default(),
415 cx,
416 );
417 });
418
419 assert_eq!(
420 editor
421 .update(cx, |view, cx| view.selections.display_ranges(cx))
422 .unwrap(),
423 [
424 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
425 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
426 ]
427 );
428
429 _ = editor.update(cx, |view, cx| {
430 view.end_selection(cx);
431 });
432
433 assert_eq!(
434 editor
435 .update(cx, |view, cx| view.selections.display_ranges(cx))
436 .unwrap(),
437 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
438 );
439}
440
441#[gpui::test]
442fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
443 init_test(cx, |_| {});
444
445 let editor = cx.add_window(|cx| {
446 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
447 build_editor(buffer, cx)
448 });
449
450 _ = editor.update(cx, |view, cx| {
451 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.end_selection(cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 assert_eq!(
467 editor
468 .update(cx, |view, cx| view.selections.display_ranges(cx))
469 .unwrap(),
470 [
471 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
472 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
473 ]
474 );
475
476 _ = editor.update(cx, |view, cx| {
477 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
478 });
479
480 _ = editor.update(cx, |view, cx| {
481 view.end_selection(cx);
482 });
483
484 assert_eq!(
485 editor
486 .update(cx, |view, cx| view.selections.display_ranges(cx))
487 .unwrap(),
488 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
489 );
490}
491
492#[gpui::test]
493fn test_canceling_pending_selection(cx: &mut TestAppContext) {
494 init_test(cx, |_| {});
495
496 let view = cx.add_window(|cx| {
497 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
498 build_editor(buffer, cx)
499 });
500
501 _ = view.update(cx, |view, cx| {
502 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
503 assert_eq!(
504 view.selections.display_ranges(cx),
505 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
506 );
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.update_selection(
511 DisplayPoint::new(DisplayRow(3), 3),
512 0,
513 gpui::Point::<f32>::default(),
514 cx,
515 );
516 assert_eq!(
517 view.selections.display_ranges(cx),
518 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
519 );
520 });
521
522 _ = view.update(cx, |view, cx| {
523 view.cancel(&Cancel, cx);
524 view.update_selection(
525 DisplayPoint::new(DisplayRow(1), 1),
526 0,
527 gpui::Point::<f32>::default(),
528 cx,
529 );
530 assert_eq!(
531 view.selections.display_ranges(cx),
532 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
533 );
534 });
535}
536
537#[gpui::test]
538fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
539 init_test(cx, |_| {});
540
541 let view = cx.add_window(|cx| {
542 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
543 build_editor(buffer, cx)
544 });
545
546 _ = view.update(cx, |view, cx| {
547 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
548 assert_eq!(
549 view.selections.display_ranges(cx),
550 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
551 );
552
553 view.move_down(&Default::default(), cx);
554 assert_eq!(
555 view.selections.display_ranges(cx),
556 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
557 );
558
559 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
560 assert_eq!(
561 view.selections.display_ranges(cx),
562 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
563 );
564
565 view.move_up(&Default::default(), cx);
566 assert_eq!(
567 view.selections.display_ranges(cx),
568 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
569 );
570 });
571}
572
573#[gpui::test]
574fn test_clone(cx: &mut TestAppContext) {
575 init_test(cx, |_| {});
576
577 let (text, selection_ranges) = marked_text_ranges(
578 indoc! {"
579 one
580 two
581 threeˇ
582 four
583 fiveˇ
584 "},
585 true,
586 );
587
588 let editor = cx.add_window(|cx| {
589 let buffer = MultiBuffer::build_simple(&text, cx);
590 build_editor(buffer, cx)
591 });
592
593 _ = editor.update(cx, |editor, cx| {
594 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
595 editor.fold_ranges(
596 [
597 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
598 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
599 ],
600 true,
601 cx,
602 );
603 });
604
605 let cloned_editor = editor
606 .update(cx, |editor, cx| {
607 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
608 })
609 .unwrap()
610 .unwrap();
611
612 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
613 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614
615 assert_eq!(
616 cloned_editor
617 .update(cx, |e, cx| e.display_text(cx))
618 .unwrap(),
619 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
620 );
621 assert_eq!(
622 cloned_snapshot
623 .folds_in_range(0..text.len())
624 .collect::<Vec<_>>(),
625 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
626 );
627 assert_set_eq!(
628 cloned_editor
629 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
630 .unwrap(),
631 editor
632 .update(cx, |editor, cx| editor.selections.ranges(cx))
633 .unwrap()
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |e, cx| e.selections.display_ranges(cx))
638 .unwrap(),
639 editor
640 .update(cx, |e, cx| e.selections.display_ranges(cx))
641 .unwrap()
642 );
643}
644
645#[gpui::test]
646async fn test_navigation_history(cx: &mut TestAppContext) {
647 init_test(cx, |_| {});
648
649 use workspace::item::Item;
650
651 let fs = FakeFs::new(cx.executor());
652 let project = Project::test(fs, [], cx).await;
653 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
654 let pane = workspace
655 .update(cx, |workspace, _| workspace.active_pane().clone())
656 .unwrap();
657
658 _ = workspace.update(cx, |_v, cx| {
659 cx.new_view(|cx| {
660 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
661 let mut editor = build_editor(buffer.clone(), cx);
662 let handle = cx.view();
663 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
664
665 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
666 editor.nav_history.as_mut().unwrap().pop_backward(cx)
667 }
668
669 // Move the cursor a small distance.
670 // Nothing is added to the navigation history.
671 editor.change_selections(None, cx, |s| {
672 s.select_display_ranges([
673 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
674 ])
675 });
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
679 ])
680 });
681 assert!(pop_history(&mut editor, cx).is_none());
682
683 // Move the cursor a large distance.
684 // The history can jump back to the previous position.
685 editor.change_selections(None, cx, |s| {
686 s.select_display_ranges([
687 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
688 ])
689 });
690 let nav_entry = pop_history(&mut editor, cx).unwrap();
691 editor.navigate(nav_entry.data.unwrap(), cx);
692 assert_eq!(nav_entry.item.id(), cx.entity_id());
693 assert_eq!(
694 editor.selections.display_ranges(cx),
695 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
696 );
697 assert!(pop_history(&mut editor, cx).is_none());
698
699 // Move the cursor a small distance via the mouse.
700 // Nothing is added to the navigation history.
701 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
702 editor.end_selection(cx);
703 assert_eq!(
704 editor.selections.display_ranges(cx),
705 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
706 );
707 assert!(pop_history(&mut editor, cx).is_none());
708
709 // Move the cursor a large distance via the mouse.
710 // The history can jump back to the previous position.
711 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
712 editor.end_selection(cx);
713 assert_eq!(
714 editor.selections.display_ranges(cx),
715 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
716 );
717 let nav_entry = pop_history(&mut editor, cx).unwrap();
718 editor.navigate(nav_entry.data.unwrap(), cx);
719 assert_eq!(nav_entry.item.id(), cx.entity_id());
720 assert_eq!(
721 editor.selections.display_ranges(cx),
722 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
723 );
724 assert!(pop_history(&mut editor, cx).is_none());
725
726 // Set scroll position to check later
727 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
728 let original_scroll_position = editor.scroll_manager.anchor();
729
730 // Jump to the end of the document and adjust scroll
731 editor.move_to_end(&MoveToEnd, cx);
732 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
733 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
734
735 let nav_entry = pop_history(&mut editor, cx).unwrap();
736 editor.navigate(nav_entry.data.unwrap(), cx);
737 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
740 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
741 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
742 let invalid_point = Point::new(9999, 0);
743 editor.navigate(
744 Box::new(NavigationData {
745 cursor_anchor: invalid_anchor,
746 cursor_position: invalid_point,
747 scroll_anchor: ScrollAnchor {
748 anchor: invalid_anchor,
749 offset: Default::default(),
750 },
751 scroll_top_row: invalid_point.row,
752 }),
753 cx,
754 );
755 assert_eq!(
756 editor.selections.display_ranges(cx),
757 &[editor.max_point(cx)..editor.max_point(cx)]
758 );
759 assert_eq!(
760 editor.scroll_position(cx),
761 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
762 );
763
764 editor
765 })
766 });
767}
768
769#[gpui::test]
770fn test_cancel(cx: &mut TestAppContext) {
771 init_test(cx, |_| {});
772
773 let view = cx.add_window(|cx| {
774 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
775 build_editor(buffer, cx)
776 });
777
778 _ = view.update(cx, |view, cx| {
779 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
780 view.update_selection(
781 DisplayPoint::new(DisplayRow(1), 1),
782 0,
783 gpui::Point::<f32>::default(),
784 cx,
785 );
786 view.end_selection(cx);
787
788 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
789 view.update_selection(
790 DisplayPoint::new(DisplayRow(0), 3),
791 0,
792 gpui::Point::<f32>::default(),
793 cx,
794 );
795 view.end_selection(cx);
796 assert_eq!(
797 view.selections.display_ranges(cx),
798 [
799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
800 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
801 ]
802 );
803 });
804
805 _ = view.update(cx, |view, cx| {
806 view.cancel(&Cancel, cx);
807 assert_eq!(
808 view.selections.display_ranges(cx),
809 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820}
821
822#[gpui::test]
823fn test_fold_action(cx: &mut TestAppContext) {
824 init_test(cx, |_| {});
825
826 let view = cx.add_window(|cx| {
827 let buffer = MultiBuffer::build_simple(
828 &"
829 impl Foo {
830 // Hello!
831
832 fn a() {
833 1
834 }
835
836 fn b() {
837 2
838 }
839
840 fn c() {
841 3
842 }
843 }
844 "
845 .unindent(),
846 cx,
847 );
848 build_editor(buffer.clone(), cx)
849 });
850
851 _ = view.update(cx, |view, cx| {
852 view.change_selections(None, cx, |s| {
853 s.select_display_ranges([
854 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
855 ]);
856 });
857 view.fold(&Fold, cx);
858 assert_eq!(
859 view.display_text(cx),
860 "
861 impl Foo {
862 // Hello!
863
864 fn a() {
865 1
866 }
867
868 fn b() {⋯
869 }
870
871 fn c() {⋯
872 }
873 }
874 "
875 .unindent(),
876 );
877
878 view.fold(&Fold, cx);
879 assert_eq!(
880 view.display_text(cx),
881 "
882 impl Foo {⋯
883 }
884 "
885 .unindent(),
886 );
887
888 view.unfold_lines(&UnfoldLines, cx);
889 assert_eq!(
890 view.display_text(cx),
891 "
892 impl Foo {
893 // Hello!
894
895 fn a() {
896 1
897 }
898
899 fn b() {⋯
900 }
901
902 fn c() {⋯
903 }
904 }
905 "
906 .unindent(),
907 );
908
909 view.unfold_lines(&UnfoldLines, cx);
910 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
911 });
912}
913
914#[gpui::test]
915fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
916 init_test(cx, |_| {});
917
918 let view = cx.add_window(|cx| {
919 let buffer = MultiBuffer::build_simple(
920 &"
921 class Foo:
922 # Hello!
923
924 def a():
925 print(1)
926
927 def b():
928 print(2)
929
930 def c():
931 print(3)
932 "
933 .unindent(),
934 cx,
935 );
936 build_editor(buffer.clone(), cx)
937 });
938
939 _ = view.update(cx, |view, cx| {
940 view.change_selections(None, cx, |s| {
941 s.select_display_ranges([
942 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
943 ]);
944 });
945 view.fold(&Fold, cx);
946 assert_eq!(
947 view.display_text(cx),
948 "
949 class Foo:
950 # Hello!
951
952 def a():
953 print(1)
954
955 def b():⋯
956
957 def c():⋯
958 "
959 .unindent(),
960 );
961
962 view.fold(&Fold, cx);
963 assert_eq!(
964 view.display_text(cx),
965 "
966 class Foo:⋯
967 "
968 .unindent(),
969 );
970
971 view.unfold_lines(&UnfoldLines, cx);
972 assert_eq!(
973 view.display_text(cx),
974 "
975 class Foo:
976 # Hello!
977
978 def a():
979 print(1)
980
981 def b():⋯
982
983 def c():⋯
984 "
985 .unindent(),
986 );
987
988 view.unfold_lines(&UnfoldLines, cx);
989 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
990 });
991}
992
993#[gpui::test]
994fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
995 init_test(cx, |_| {});
996
997 let view = cx.add_window(|cx| {
998 let buffer = MultiBuffer::build_simple(
999 &"
1000 class Foo:
1001 # Hello!
1002
1003 def a():
1004 print(1)
1005
1006 def b():
1007 print(2)
1008
1009
1010 def c():
1011 print(3)
1012
1013
1014 "
1015 .unindent(),
1016 cx,
1017 );
1018 build_editor(buffer.clone(), cx)
1019 });
1020
1021 _ = view.update(cx, |view, cx| {
1022 view.change_selections(None, cx, |s| {
1023 s.select_display_ranges([
1024 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1025 ]);
1026 });
1027 view.fold(&Fold, cx);
1028 assert_eq!(
1029 view.display_text(cx),
1030 "
1031 class Foo:
1032 # Hello!
1033
1034 def a():
1035 print(1)
1036
1037 def b():⋯
1038
1039
1040 def c():⋯
1041
1042
1043 "
1044 .unindent(),
1045 );
1046
1047 view.fold(&Fold, cx);
1048 assert_eq!(
1049 view.display_text(cx),
1050 "
1051 class Foo:⋯
1052
1053
1054 "
1055 .unindent(),
1056 );
1057
1058 view.unfold_lines(&UnfoldLines, cx);
1059 assert_eq!(
1060 view.display_text(cx),
1061 "
1062 class Foo:
1063 # Hello!
1064
1065 def a():
1066 print(1)
1067
1068 def b():⋯
1069
1070
1071 def c():⋯
1072
1073
1074 "
1075 .unindent(),
1076 );
1077
1078 view.unfold_lines(&UnfoldLines, cx);
1079 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1080 });
1081}
1082
1083#[gpui::test]
1084fn test_move_cursor(cx: &mut TestAppContext) {
1085 init_test(cx, |_| {});
1086
1087 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1088 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1089
1090 buffer.update(cx, |buffer, cx| {
1091 buffer.edit(
1092 vec![
1093 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1094 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1095 ],
1096 None,
1097 cx,
1098 );
1099 });
1100 _ = view.update(cx, |view, cx| {
1101 assert_eq!(
1102 view.selections.display_ranges(cx),
1103 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1104 );
1105
1106 view.move_down(&MoveDown, cx);
1107 assert_eq!(
1108 view.selections.display_ranges(cx),
1109 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1110 );
1111
1112 view.move_right(&MoveRight, cx);
1113 assert_eq!(
1114 view.selections.display_ranges(cx),
1115 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1116 );
1117
1118 view.move_left(&MoveLeft, cx);
1119 assert_eq!(
1120 view.selections.display_ranges(cx),
1121 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1122 );
1123
1124 view.move_up(&MoveUp, cx);
1125 assert_eq!(
1126 view.selections.display_ranges(cx),
1127 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1128 );
1129
1130 view.move_to_end(&MoveToEnd, cx);
1131 assert_eq!(
1132 view.selections.display_ranges(cx),
1133 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1134 );
1135
1136 view.move_to_beginning(&MoveToBeginning, cx);
1137 assert_eq!(
1138 view.selections.display_ranges(cx),
1139 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1140 );
1141
1142 view.change_selections(None, cx, |s| {
1143 s.select_display_ranges([
1144 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1145 ]);
1146 });
1147 view.select_to_beginning(&SelectToBeginning, cx);
1148 assert_eq!(
1149 view.selections.display_ranges(cx),
1150 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1151 );
1152
1153 view.select_to_end(&SelectToEnd, cx);
1154 assert_eq!(
1155 view.selections.display_ranges(cx),
1156 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1157 );
1158 });
1159}
1160
1161// TODO: Re-enable this test
1162#[cfg(target_os = "macos")]
1163#[gpui::test]
1164fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1165 init_test(cx, |_| {});
1166
1167 let view = cx.add_window(|cx| {
1168 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1169 build_editor(buffer.clone(), cx)
1170 });
1171
1172 assert_eq!('ⓐ'.len_utf8(), 3);
1173 assert_eq!('α'.len_utf8(), 2);
1174
1175 _ = view.update(cx, |view, cx| {
1176 view.fold_ranges(
1177 vec![
1178 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1179 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1180 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1181 ],
1182 true,
1183 cx,
1184 );
1185 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1186
1187 view.move_right(&MoveRight, cx);
1188 assert_eq!(
1189 view.selections.display_ranges(cx),
1190 &[empty_range(0, "ⓐ".len())]
1191 );
1192 view.move_right(&MoveRight, cx);
1193 assert_eq!(
1194 view.selections.display_ranges(cx),
1195 &[empty_range(0, "ⓐⓑ".len())]
1196 );
1197 view.move_right(&MoveRight, cx);
1198 assert_eq!(
1199 view.selections.display_ranges(cx),
1200 &[empty_range(0, "ⓐⓑ⋯".len())]
1201 );
1202
1203 view.move_down(&MoveDown, cx);
1204 assert_eq!(
1205 view.selections.display_ranges(cx),
1206 &[empty_range(1, "ab⋯e".len())]
1207 );
1208 view.move_left(&MoveLeft, cx);
1209 assert_eq!(
1210 view.selections.display_ranges(cx),
1211 &[empty_range(1, "ab⋯".len())]
1212 );
1213 view.move_left(&MoveLeft, cx);
1214 assert_eq!(
1215 view.selections.display_ranges(cx),
1216 &[empty_range(1, "ab".len())]
1217 );
1218 view.move_left(&MoveLeft, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[empty_range(1, "a".len())]
1222 );
1223
1224 view.move_down(&MoveDown, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[empty_range(2, "α".len())]
1228 );
1229 view.move_right(&MoveRight, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[empty_range(2, "αβ".len())]
1233 );
1234 view.move_right(&MoveRight, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[empty_range(2, "αβ⋯".len())]
1238 );
1239 view.move_right(&MoveRight, cx);
1240 assert_eq!(
1241 view.selections.display_ranges(cx),
1242 &[empty_range(2, "αβ⋯ε".len())]
1243 );
1244
1245 view.move_up(&MoveUp, cx);
1246 assert_eq!(
1247 view.selections.display_ranges(cx),
1248 &[empty_range(1, "ab⋯e".len())]
1249 );
1250 view.move_down(&MoveDown, cx);
1251 assert_eq!(
1252 view.selections.display_ranges(cx),
1253 &[empty_range(2, "αβ⋯ε".len())]
1254 );
1255 view.move_up(&MoveUp, cx);
1256 assert_eq!(
1257 view.selections.display_ranges(cx),
1258 &[empty_range(1, "ab⋯e".len())]
1259 );
1260
1261 view.move_up(&MoveUp, cx);
1262 assert_eq!(
1263 view.selections.display_ranges(cx),
1264 &[empty_range(0, "ⓐⓑ".len())]
1265 );
1266 view.move_left(&MoveLeft, cx);
1267 assert_eq!(
1268 view.selections.display_ranges(cx),
1269 &[empty_range(0, "ⓐ".len())]
1270 );
1271 view.move_left(&MoveLeft, cx);
1272 assert_eq!(
1273 view.selections.display_ranges(cx),
1274 &[empty_range(0, "".len())]
1275 );
1276 });
1277}
1278
1279#[gpui::test]
1280fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1281 init_test(cx, |_| {});
1282
1283 let view = cx.add_window(|cx| {
1284 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1285 build_editor(buffer.clone(), cx)
1286 });
1287 _ = view.update(cx, |view, cx| {
1288 view.change_selections(None, cx, |s| {
1289 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1290 });
1291 view.move_down(&MoveDown, cx);
1292 assert_eq!(
1293 view.selections.display_ranges(cx),
1294 &[empty_range(1, "abcd".len())]
1295 );
1296
1297 view.move_down(&MoveDown, cx);
1298 assert_eq!(
1299 view.selections.display_ranges(cx),
1300 &[empty_range(2, "αβγ".len())]
1301 );
1302
1303 view.move_down(&MoveDown, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(3, "abcd".len())]
1307 );
1308
1309 view.move_down(&MoveDown, cx);
1310 assert_eq!(
1311 view.selections.display_ranges(cx),
1312 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1313 );
1314
1315 view.move_up(&MoveUp, cx);
1316 assert_eq!(
1317 view.selections.display_ranges(cx),
1318 &[empty_range(3, "abcd".len())]
1319 );
1320
1321 view.move_up(&MoveUp, cx);
1322 assert_eq!(
1323 view.selections.display_ranges(cx),
1324 &[empty_range(2, "αβγ".len())]
1325 );
1326 });
1327}
1328
1329#[gpui::test]
1330fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1331 init_test(cx, |_| {});
1332 let move_to_beg = MoveToBeginningOfLine {
1333 stop_at_soft_wraps: true,
1334 };
1335
1336 let move_to_end = MoveToEndOfLine {
1337 stop_at_soft_wraps: true,
1338 };
1339
1340 let view = cx.add_window(|cx| {
1341 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1342 build_editor(buffer, cx)
1343 });
1344 _ = view.update(cx, |view, cx| {
1345 view.change_selections(None, cx, |s| {
1346 s.select_display_ranges([
1347 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1348 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1349 ]);
1350 });
1351 });
1352
1353 _ = view.update(cx, |view, cx| {
1354 view.move_to_beginning_of_line(&move_to_beg, cx);
1355 assert_eq!(
1356 view.selections.display_ranges(cx),
1357 &[
1358 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1359 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1360 ]
1361 );
1362 });
1363
1364 _ = view.update(cx, |view, cx| {
1365 view.move_to_beginning_of_line(&move_to_beg, cx);
1366 assert_eq!(
1367 view.selections.display_ranges(cx),
1368 &[
1369 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1370 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1371 ]
1372 );
1373 });
1374
1375 _ = view.update(cx, |view, cx| {
1376 view.move_to_beginning_of_line(&move_to_beg, cx);
1377 assert_eq!(
1378 view.selections.display_ranges(cx),
1379 &[
1380 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1381 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1382 ]
1383 );
1384 });
1385
1386 _ = view.update(cx, |view, cx| {
1387 view.move_to_end_of_line(&move_to_end, cx);
1388 assert_eq!(
1389 view.selections.display_ranges(cx),
1390 &[
1391 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1392 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1393 ]
1394 );
1395 });
1396
1397 // Moving to the end of line again is a no-op.
1398 _ = view.update(cx, |view, cx| {
1399 view.move_to_end_of_line(&move_to_end, cx);
1400 assert_eq!(
1401 view.selections.display_ranges(cx),
1402 &[
1403 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1404 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1405 ]
1406 );
1407 });
1408
1409 _ = view.update(cx, |view, cx| {
1410 view.move_left(&MoveLeft, cx);
1411 view.select_to_beginning_of_line(
1412 &SelectToBeginningOfLine {
1413 stop_at_soft_wraps: true,
1414 },
1415 cx,
1416 );
1417 assert_eq!(
1418 view.selections.display_ranges(cx),
1419 &[
1420 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1421 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1422 ]
1423 );
1424 });
1425
1426 _ = view.update(cx, |view, cx| {
1427 view.select_to_beginning_of_line(
1428 &SelectToBeginningOfLine {
1429 stop_at_soft_wraps: true,
1430 },
1431 cx,
1432 );
1433 assert_eq!(
1434 view.selections.display_ranges(cx),
1435 &[
1436 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1437 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1438 ]
1439 );
1440 });
1441
1442 _ = view.update(cx, |view, cx| {
1443 view.select_to_beginning_of_line(
1444 &SelectToBeginningOfLine {
1445 stop_at_soft_wraps: true,
1446 },
1447 cx,
1448 );
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[
1452 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1453 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1454 ]
1455 );
1456 });
1457
1458 _ = view.update(cx, |view, cx| {
1459 view.select_to_end_of_line(
1460 &SelectToEndOfLine {
1461 stop_at_soft_wraps: true,
1462 },
1463 cx,
1464 );
1465 assert_eq!(
1466 view.selections.display_ranges(cx),
1467 &[
1468 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1469 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1470 ]
1471 );
1472 });
1473
1474 _ = view.update(cx, |view, cx| {
1475 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1476 assert_eq!(view.display_text(cx), "ab\n de");
1477 assert_eq!(
1478 view.selections.display_ranges(cx),
1479 &[
1480 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1481 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1482 ]
1483 );
1484 });
1485
1486 _ = view.update(cx, |view, cx| {
1487 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1488 assert_eq!(view.display_text(cx), "\n");
1489 assert_eq!(
1490 view.selections.display_ranges(cx),
1491 &[
1492 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1493 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1494 ]
1495 );
1496 });
1497}
1498
1499#[gpui::test]
1500fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1501 init_test(cx, |_| {});
1502 let move_to_beg = MoveToBeginningOfLine {
1503 stop_at_soft_wraps: false,
1504 };
1505
1506 let move_to_end = MoveToEndOfLine {
1507 stop_at_soft_wraps: false,
1508 };
1509
1510 let view = cx.add_window(|cx| {
1511 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1512 build_editor(buffer, cx)
1513 });
1514
1515 _ = view.update(cx, |view, cx| {
1516 view.set_wrap_width(Some(140.0.into()), cx);
1517
1518 // We expect the following lines after wrapping
1519 // ```
1520 // thequickbrownfox
1521 // jumpedoverthelazydo
1522 // gs
1523 // ```
1524 // The final `gs` was soft-wrapped onto a new line.
1525 assert_eq!(
1526 "thequickbrownfox\njumpedoverthelaz\nydogs",
1527 view.display_text(cx),
1528 );
1529
1530 // First, let's assert behavior on the first line, that was not soft-wrapped.
1531 // Start the cursor at the `k` on the first line
1532 view.change_selections(None, cx, |s| {
1533 s.select_display_ranges([
1534 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1535 ]);
1536 });
1537
1538 // Moving to the beginning of the line should put us at the beginning of the line.
1539 view.move_to_beginning_of_line(&move_to_beg, cx);
1540 assert_eq!(
1541 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1542 view.selections.display_ranges(cx)
1543 );
1544
1545 // Moving to the end of the line should put us at the end of the line.
1546 view.move_to_end_of_line(&move_to_end, cx);
1547 assert_eq!(
1548 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1549 view.selections.display_ranges(cx)
1550 );
1551
1552 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1553 // Start the cursor at the last line (`y` that was wrapped to a new line)
1554 view.change_selections(None, cx, |s| {
1555 s.select_display_ranges([
1556 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1557 ]);
1558 });
1559
1560 // Moving to the beginning of the line should put us at the start of the second line of
1561 // display text, i.e., the `j`.
1562 view.move_to_beginning_of_line(&move_to_beg, cx);
1563 assert_eq!(
1564 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1565 view.selections.display_ranges(cx)
1566 );
1567
1568 // Moving to the beginning of the line again should be a no-op.
1569 view.move_to_beginning_of_line(&move_to_beg, cx);
1570 assert_eq!(
1571 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1572 view.selections.display_ranges(cx)
1573 );
1574
1575 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1576 // next display line.
1577 view.move_to_end_of_line(&move_to_end, cx);
1578 assert_eq!(
1579 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1580 view.selections.display_ranges(cx)
1581 );
1582
1583 // Moving to the end of the line again should be a no-op.
1584 view.move_to_end_of_line(&move_to_end, cx);
1585 assert_eq!(
1586 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1587 view.selections.display_ranges(cx)
1588 );
1589 });
1590}
1591
1592#[gpui::test]
1593fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1594 init_test(cx, |_| {});
1595
1596 let view = cx.add_window(|cx| {
1597 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1598 build_editor(buffer, cx)
1599 });
1600 _ = view.update(cx, |view, cx| {
1601 view.change_selections(None, cx, |s| {
1602 s.select_display_ranges([
1603 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1604 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1605 ])
1606 });
1607
1608 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1609 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1610
1611 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1612 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1613
1614 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1615 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1616
1617 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1618 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1619
1620 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1621 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1622
1623 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1624 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1625
1626 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1627 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1628
1629 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1630 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1631
1632 view.move_right(&MoveRight, cx);
1633 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1634 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1635
1636 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1637 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1638
1639 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1640 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1641 });
1642}
1643
1644#[gpui::test]
1645fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1646 init_test(cx, |_| {});
1647
1648 let view = cx.add_window(|cx| {
1649 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1650 build_editor(buffer, cx)
1651 });
1652
1653 _ = view.update(cx, |view, cx| {
1654 view.set_wrap_width(Some(140.0.into()), cx);
1655 assert_eq!(
1656 view.display_text(cx),
1657 "use one::{\n two::three::\n four::five\n};"
1658 );
1659
1660 view.change_selections(None, cx, |s| {
1661 s.select_display_ranges([
1662 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1663 ]);
1664 });
1665
1666 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1667 assert_eq!(
1668 view.selections.display_ranges(cx),
1669 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1670 );
1671
1672 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1673 assert_eq!(
1674 view.selections.display_ranges(cx),
1675 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1676 );
1677
1678 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1679 assert_eq!(
1680 view.selections.display_ranges(cx),
1681 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1682 );
1683
1684 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1685 assert_eq!(
1686 view.selections.display_ranges(cx),
1687 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1688 );
1689
1690 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1691 assert_eq!(
1692 view.selections.display_ranges(cx),
1693 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1694 );
1695
1696 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1697 assert_eq!(
1698 view.selections.display_ranges(cx),
1699 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1700 );
1701 });
1702}
1703
1704#[gpui::test]
1705async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1706 init_test(cx, |_| {});
1707 let mut cx = EditorTestContext::new(cx).await;
1708
1709 let line_height = cx.editor(|editor, cx| {
1710 editor
1711 .style()
1712 .unwrap()
1713 .text
1714 .line_height_in_pixels(cx.rem_size())
1715 });
1716 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1717
1718 cx.set_state(
1719 &r#"ˇone
1720 two
1721
1722 three
1723 fourˇ
1724 five
1725
1726 six"#
1727 .unindent(),
1728 );
1729
1730 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1731 cx.assert_editor_state(
1732 &r#"one
1733 two
1734 ˇ
1735 three
1736 four
1737 five
1738 ˇ
1739 six"#
1740 .unindent(),
1741 );
1742
1743 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1744 cx.assert_editor_state(
1745 &r#"one
1746 two
1747
1748 three
1749 four
1750 five
1751 ˇ
1752 sixˇ"#
1753 .unindent(),
1754 );
1755
1756 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1757 cx.assert_editor_state(
1758 &r#"one
1759 two
1760
1761 three
1762 four
1763 five
1764
1765 sixˇ"#
1766 .unindent(),
1767 );
1768
1769 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1770 cx.assert_editor_state(
1771 &r#"one
1772 two
1773
1774 three
1775 four
1776 five
1777 ˇ
1778 six"#
1779 .unindent(),
1780 );
1781
1782 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1783 cx.assert_editor_state(
1784 &r#"one
1785 two
1786 ˇ
1787 three
1788 four
1789 five
1790
1791 six"#
1792 .unindent(),
1793 );
1794
1795 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1796 cx.assert_editor_state(
1797 &r#"ˇone
1798 two
1799
1800 three
1801 four
1802 five
1803
1804 six"#
1805 .unindent(),
1806 );
1807}
1808
1809#[gpui::test]
1810async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1811 init_test(cx, |_| {});
1812 let mut cx = EditorTestContext::new(cx).await;
1813 let line_height = cx.editor(|editor, cx| {
1814 editor
1815 .style()
1816 .unwrap()
1817 .text
1818 .line_height_in_pixels(cx.rem_size())
1819 });
1820 let window = cx.window;
1821 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1822
1823 cx.set_state(
1824 r#"ˇone
1825 two
1826 three
1827 four
1828 five
1829 six
1830 seven
1831 eight
1832 nine
1833 ten
1834 "#,
1835 );
1836
1837 cx.update_editor(|editor, cx| {
1838 assert_eq!(
1839 editor.snapshot(cx).scroll_position(),
1840 gpui::Point::new(0., 0.)
1841 );
1842 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1843 assert_eq!(
1844 editor.snapshot(cx).scroll_position(),
1845 gpui::Point::new(0., 3.)
1846 );
1847 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1848 assert_eq!(
1849 editor.snapshot(cx).scroll_position(),
1850 gpui::Point::new(0., 6.)
1851 );
1852 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1853 assert_eq!(
1854 editor.snapshot(cx).scroll_position(),
1855 gpui::Point::new(0., 3.)
1856 );
1857
1858 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1859 assert_eq!(
1860 editor.snapshot(cx).scroll_position(),
1861 gpui::Point::new(0., 1.)
1862 );
1863 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1864 assert_eq!(
1865 editor.snapshot(cx).scroll_position(),
1866 gpui::Point::new(0., 3.)
1867 );
1868 });
1869}
1870
1871#[gpui::test]
1872async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1873 init_test(cx, |_| {});
1874 let mut cx = EditorTestContext::new(cx).await;
1875
1876 let line_height = cx.update_editor(|editor, cx| {
1877 editor.set_vertical_scroll_margin(2, cx);
1878 editor
1879 .style()
1880 .unwrap()
1881 .text
1882 .line_height_in_pixels(cx.rem_size())
1883 });
1884 let window = cx.window;
1885 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1886
1887 cx.set_state(
1888 r#"ˇone
1889 two
1890 three
1891 four
1892 five
1893 six
1894 seven
1895 eight
1896 nine
1897 ten
1898 "#,
1899 );
1900 cx.update_editor(|editor, cx| {
1901 assert_eq!(
1902 editor.snapshot(cx).scroll_position(),
1903 gpui::Point::new(0., 0.0)
1904 );
1905 });
1906
1907 // Add a cursor below the visible area. Since both cursors cannot fit
1908 // on screen, the editor autoscrolls to reveal the newest cursor, and
1909 // allows the vertical scroll margin below that cursor.
1910 cx.update_editor(|editor, cx| {
1911 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1912 selections.select_ranges([
1913 Point::new(0, 0)..Point::new(0, 0),
1914 Point::new(6, 0)..Point::new(6, 0),
1915 ]);
1916 })
1917 });
1918 cx.update_editor(|editor, cx| {
1919 assert_eq!(
1920 editor.snapshot(cx).scroll_position(),
1921 gpui::Point::new(0., 3.0)
1922 );
1923 });
1924
1925 // Move down. The editor cursor scrolls down to track the newest cursor.
1926 cx.update_editor(|editor, cx| {
1927 editor.move_down(&Default::default(), cx);
1928 });
1929 cx.update_editor(|editor, cx| {
1930 assert_eq!(
1931 editor.snapshot(cx).scroll_position(),
1932 gpui::Point::new(0., 4.0)
1933 );
1934 });
1935
1936 // Add a cursor above the visible area. Since both cursors fit on screen,
1937 // the editor scrolls to show both.
1938 cx.update_editor(|editor, cx| {
1939 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1940 selections.select_ranges([
1941 Point::new(1, 0)..Point::new(1, 0),
1942 Point::new(6, 0)..Point::new(6, 0),
1943 ]);
1944 })
1945 });
1946 cx.update_editor(|editor, cx| {
1947 assert_eq!(
1948 editor.snapshot(cx).scroll_position(),
1949 gpui::Point::new(0., 1.0)
1950 );
1951 });
1952}
1953
1954#[gpui::test]
1955async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1956 init_test(cx, |_| {});
1957 let mut cx = EditorTestContext::new(cx).await;
1958
1959 let line_height = cx.editor(|editor, cx| {
1960 editor
1961 .style()
1962 .unwrap()
1963 .text
1964 .line_height_in_pixels(cx.rem_size())
1965 });
1966 let window = cx.window;
1967 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1968 cx.set_state(
1969 &r#"
1970 ˇone
1971 two
1972 threeˇ
1973 four
1974 five
1975 six
1976 seven
1977 eight
1978 nine
1979 ten
1980 "#
1981 .unindent(),
1982 );
1983
1984 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1985 cx.assert_editor_state(
1986 &r#"
1987 one
1988 two
1989 three
1990 ˇfour
1991 five
1992 sixˇ
1993 seven
1994 eight
1995 nine
1996 ten
1997 "#
1998 .unindent(),
1999 );
2000
2001 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2002 cx.assert_editor_state(
2003 &r#"
2004 one
2005 two
2006 three
2007 four
2008 five
2009 six
2010 ˇseven
2011 eight
2012 nineˇ
2013 ten
2014 "#
2015 .unindent(),
2016 );
2017
2018 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2019 cx.assert_editor_state(
2020 &r#"
2021 one
2022 two
2023 three
2024 ˇfour
2025 five
2026 sixˇ
2027 seven
2028 eight
2029 nine
2030 ten
2031 "#
2032 .unindent(),
2033 );
2034
2035 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2036 cx.assert_editor_state(
2037 &r#"
2038 ˇone
2039 two
2040 threeˇ
2041 four
2042 five
2043 six
2044 seven
2045 eight
2046 nine
2047 ten
2048 "#
2049 .unindent(),
2050 );
2051
2052 // Test select collapsing
2053 cx.update_editor(|editor, cx| {
2054 editor.move_page_down(&MovePageDown::default(), cx);
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 });
2058 cx.assert_editor_state(
2059 &r#"
2060 one
2061 two
2062 three
2063 four
2064 five
2065 six
2066 seven
2067 eight
2068 nine
2069 ˇten
2070 ˇ"#
2071 .unindent(),
2072 );
2073}
2074
2075#[gpui::test]
2076async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2077 init_test(cx, |_| {});
2078 let mut cx = EditorTestContext::new(cx).await;
2079 cx.set_state("one «two threeˇ» four");
2080 cx.update_editor(|editor, cx| {
2081 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2082 assert_eq!(editor.text(cx), " four");
2083 });
2084}
2085
2086#[gpui::test]
2087fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2088 init_test(cx, |_| {});
2089
2090 let view = cx.add_window(|cx| {
2091 let buffer = MultiBuffer::build_simple("one two three four", cx);
2092 build_editor(buffer.clone(), cx)
2093 });
2094
2095 _ = view.update(cx, |view, cx| {
2096 view.change_selections(None, cx, |s| {
2097 s.select_display_ranges([
2098 // an empty selection - the preceding word fragment is deleted
2099 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2100 // characters selected - they are deleted
2101 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2102 ])
2103 });
2104 view.delete_to_previous_word_start(
2105 &DeleteToPreviousWordStart {
2106 ignore_newlines: false,
2107 },
2108 cx,
2109 );
2110 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2111 });
2112
2113 _ = view.update(cx, |view, cx| {
2114 view.change_selections(None, cx, |s| {
2115 s.select_display_ranges([
2116 // an empty selection - the following word fragment is deleted
2117 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2118 // characters selected - they are deleted
2119 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2120 ])
2121 });
2122 view.delete_to_next_word_end(
2123 &DeleteToNextWordEnd {
2124 ignore_newlines: false,
2125 },
2126 cx,
2127 );
2128 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2129 });
2130}
2131
2132#[gpui::test]
2133fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2134 init_test(cx, |_| {});
2135
2136 let view = cx.add_window(|cx| {
2137 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2138 build_editor(buffer.clone(), cx)
2139 });
2140 let del_to_prev_word_start = DeleteToPreviousWordStart {
2141 ignore_newlines: false,
2142 };
2143 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2144 ignore_newlines: true,
2145 };
2146
2147 _ = view.update(cx, |view, cx| {
2148 view.change_selections(None, cx, |s| {
2149 s.select_display_ranges([
2150 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2151 ])
2152 });
2153 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2154 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2155 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2156 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2157 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2158 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2159 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2160 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2161 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2162 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2163 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2164 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2165 });
2166}
2167
2168#[gpui::test]
2169fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2170 init_test(cx, |_| {});
2171
2172 let view = cx.add_window(|cx| {
2173 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2174 build_editor(buffer.clone(), cx)
2175 });
2176 let del_to_next_word_end = DeleteToNextWordEnd {
2177 ignore_newlines: false,
2178 };
2179 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2180 ignore_newlines: true,
2181 };
2182
2183 _ = view.update(cx, |view, cx| {
2184 view.change_selections(None, cx, |s| {
2185 s.select_display_ranges([
2186 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2187 ])
2188 });
2189 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2190 assert_eq!(
2191 view.buffer.read(cx).read(cx).text(),
2192 "one\n two\nthree\n four"
2193 );
2194 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2195 assert_eq!(
2196 view.buffer.read(cx).read(cx).text(),
2197 "\n two\nthree\n four"
2198 );
2199 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2200 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2201 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2202 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2203 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2204 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2205 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2206 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2207 });
2208}
2209
2210#[gpui::test]
2211fn test_newline(cx: &mut TestAppContext) {
2212 init_test(cx, |_| {});
2213
2214 let view = cx.add_window(|cx| {
2215 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2216 build_editor(buffer.clone(), cx)
2217 });
2218
2219 _ = view.update(cx, |view, cx| {
2220 view.change_selections(None, cx, |s| {
2221 s.select_display_ranges([
2222 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2223 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2224 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2225 ])
2226 });
2227
2228 view.newline(&Newline, cx);
2229 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2230 });
2231}
2232
2233#[gpui::test]
2234fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2235 init_test(cx, |_| {});
2236
2237 let editor = cx.add_window(|cx| {
2238 let buffer = MultiBuffer::build_simple(
2239 "
2240 a
2241 b(
2242 X
2243 )
2244 c(
2245 X
2246 )
2247 "
2248 .unindent()
2249 .as_str(),
2250 cx,
2251 );
2252 let mut editor = build_editor(buffer.clone(), cx);
2253 editor.change_selections(None, cx, |s| {
2254 s.select_ranges([
2255 Point::new(2, 4)..Point::new(2, 5),
2256 Point::new(5, 4)..Point::new(5, 5),
2257 ])
2258 });
2259 editor
2260 });
2261
2262 _ = editor.update(cx, |editor, cx| {
2263 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2264 editor.buffer.update(cx, |buffer, cx| {
2265 buffer.edit(
2266 [
2267 (Point::new(1, 2)..Point::new(3, 0), ""),
2268 (Point::new(4, 2)..Point::new(6, 0), ""),
2269 ],
2270 None,
2271 cx,
2272 );
2273 assert_eq!(
2274 buffer.read(cx).text(),
2275 "
2276 a
2277 b()
2278 c()
2279 "
2280 .unindent()
2281 );
2282 });
2283 assert_eq!(
2284 editor.selections.ranges(cx),
2285 &[
2286 Point::new(1, 2)..Point::new(1, 2),
2287 Point::new(2, 2)..Point::new(2, 2),
2288 ],
2289 );
2290
2291 editor.newline(&Newline, cx);
2292 assert_eq!(
2293 editor.text(cx),
2294 "
2295 a
2296 b(
2297 )
2298 c(
2299 )
2300 "
2301 .unindent()
2302 );
2303
2304 // The selections are moved after the inserted newlines
2305 assert_eq!(
2306 editor.selections.ranges(cx),
2307 &[
2308 Point::new(2, 0)..Point::new(2, 0),
2309 Point::new(4, 0)..Point::new(4, 0),
2310 ],
2311 );
2312 });
2313}
2314
2315#[gpui::test]
2316async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2317 init_test(cx, |settings| {
2318 settings.defaults.tab_size = NonZeroU32::new(4)
2319 });
2320
2321 let language = Arc::new(
2322 Language::new(
2323 LanguageConfig::default(),
2324 Some(tree_sitter_rust::LANGUAGE.into()),
2325 )
2326 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2327 .unwrap(),
2328 );
2329
2330 let mut cx = EditorTestContext::new(cx).await;
2331 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2332 cx.set_state(indoc! {"
2333 const a: ˇA = (
2334 (ˇ
2335 «const_functionˇ»(ˇ),
2336 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2337 )ˇ
2338 ˇ);ˇ
2339 "});
2340
2341 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2342 cx.assert_editor_state(indoc! {"
2343 ˇ
2344 const a: A = (
2345 ˇ
2346 (
2347 ˇ
2348 ˇ
2349 const_function(),
2350 ˇ
2351 ˇ
2352 ˇ
2353 ˇ
2354 something_else,
2355 ˇ
2356 )
2357 ˇ
2358 ˇ
2359 );
2360 "});
2361}
2362
2363#[gpui::test]
2364async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2365 init_test(cx, |settings| {
2366 settings.defaults.tab_size = NonZeroU32::new(4)
2367 });
2368
2369 let language = Arc::new(
2370 Language::new(
2371 LanguageConfig::default(),
2372 Some(tree_sitter_rust::LANGUAGE.into()),
2373 )
2374 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2375 .unwrap(),
2376 );
2377
2378 let mut cx = EditorTestContext::new(cx).await;
2379 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2380 cx.set_state(indoc! {"
2381 const a: ˇA = (
2382 (ˇ
2383 «const_functionˇ»(ˇ),
2384 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2385 )ˇ
2386 ˇ);ˇ
2387 "});
2388
2389 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2390 cx.assert_editor_state(indoc! {"
2391 const a: A = (
2392 ˇ
2393 (
2394 ˇ
2395 const_function(),
2396 ˇ
2397 ˇ
2398 something_else,
2399 ˇ
2400 ˇ
2401 ˇ
2402 ˇ
2403 )
2404 ˇ
2405 );
2406 ˇ
2407 ˇ
2408 "});
2409}
2410
2411#[gpui::test]
2412async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2413 init_test(cx, |settings| {
2414 settings.defaults.tab_size = NonZeroU32::new(4)
2415 });
2416
2417 let language = Arc::new(Language::new(
2418 LanguageConfig {
2419 line_comments: vec!["//".into()],
2420 ..LanguageConfig::default()
2421 },
2422 None,
2423 ));
2424 {
2425 let mut cx = EditorTestContext::new(cx).await;
2426 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2427 cx.set_state(indoc! {"
2428 // Fooˇ
2429 "});
2430
2431 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2432 cx.assert_editor_state(indoc! {"
2433 // Foo
2434 //ˇ
2435 "});
2436 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2437 cx.set_state(indoc! {"
2438 ˇ// Foo
2439 "});
2440 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2441 cx.assert_editor_state(indoc! {"
2442
2443 ˇ// Foo
2444 "});
2445 }
2446 // Ensure that comment continuations can be disabled.
2447 update_test_language_settings(cx, |settings| {
2448 settings.defaults.extend_comment_on_newline = Some(false);
2449 });
2450 let mut cx = EditorTestContext::new(cx).await;
2451 cx.set_state(indoc! {"
2452 // Fooˇ
2453 "});
2454 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2455 cx.assert_editor_state(indoc! {"
2456 // Foo
2457 ˇ
2458 "});
2459}
2460
2461#[gpui::test]
2462fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2463 init_test(cx, |_| {});
2464
2465 let editor = cx.add_window(|cx| {
2466 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2467 let mut editor = build_editor(buffer.clone(), cx);
2468 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2469 editor
2470 });
2471
2472 _ = editor.update(cx, |editor, cx| {
2473 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2474 editor.buffer.update(cx, |buffer, cx| {
2475 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2476 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2477 });
2478 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2479
2480 editor.insert("Z", cx);
2481 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2482
2483 // The selections are moved after the inserted characters
2484 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2485 });
2486}
2487
2488#[gpui::test]
2489async fn test_tab(cx: &mut gpui::TestAppContext) {
2490 init_test(cx, |settings| {
2491 settings.defaults.tab_size = NonZeroU32::new(3)
2492 });
2493
2494 let mut cx = EditorTestContext::new(cx).await;
2495 cx.set_state(indoc! {"
2496 ˇabˇc
2497 ˇ🏀ˇ🏀ˇefg
2498 dˇ
2499 "});
2500 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2501 cx.assert_editor_state(indoc! {"
2502 ˇab ˇc
2503 ˇ🏀 ˇ🏀 ˇefg
2504 d ˇ
2505 "});
2506
2507 cx.set_state(indoc! {"
2508 a
2509 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2510 "});
2511 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2512 cx.assert_editor_state(indoc! {"
2513 a
2514 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2515 "});
2516}
2517
2518#[gpui::test]
2519async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2520 init_test(cx, |_| {});
2521
2522 let mut cx = EditorTestContext::new(cx).await;
2523 let language = Arc::new(
2524 Language::new(
2525 LanguageConfig::default(),
2526 Some(tree_sitter_rust::LANGUAGE.into()),
2527 )
2528 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2529 .unwrap(),
2530 );
2531 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2532
2533 // cursors that are already at the suggested indent level insert
2534 // a soft tab. cursors that are to the left of the suggested indent
2535 // auto-indent their line.
2536 cx.set_state(indoc! {"
2537 ˇ
2538 const a: B = (
2539 c(
2540 d(
2541 ˇ
2542 )
2543 ˇ
2544 ˇ )
2545 );
2546 "});
2547 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2548 cx.assert_editor_state(indoc! {"
2549 ˇ
2550 const a: B = (
2551 c(
2552 d(
2553 ˇ
2554 )
2555 ˇ
2556 ˇ)
2557 );
2558 "});
2559
2560 // handle auto-indent when there are multiple cursors on the same line
2561 cx.set_state(indoc! {"
2562 const a: B = (
2563 c(
2564 ˇ ˇ
2565 ˇ )
2566 );
2567 "});
2568 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2569 cx.assert_editor_state(indoc! {"
2570 const a: B = (
2571 c(
2572 ˇ
2573 ˇ)
2574 );
2575 "});
2576}
2577
2578#[gpui::test]
2579async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2580 init_test(cx, |settings| {
2581 settings.defaults.tab_size = NonZeroU32::new(4)
2582 });
2583
2584 let language = Arc::new(
2585 Language::new(
2586 LanguageConfig::default(),
2587 Some(tree_sitter_rust::LANGUAGE.into()),
2588 )
2589 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2590 .unwrap(),
2591 );
2592
2593 let mut cx = EditorTestContext::new(cx).await;
2594 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2595 cx.set_state(indoc! {"
2596 fn a() {
2597 if b {
2598 \t ˇc
2599 }
2600 }
2601 "});
2602
2603 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2604 cx.assert_editor_state(indoc! {"
2605 fn a() {
2606 if b {
2607 ˇc
2608 }
2609 }
2610 "});
2611}
2612
2613#[gpui::test]
2614async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2615 init_test(cx, |settings| {
2616 settings.defaults.tab_size = NonZeroU32::new(4);
2617 });
2618
2619 let mut cx = EditorTestContext::new(cx).await;
2620
2621 cx.set_state(indoc! {"
2622 «oneˇ» «twoˇ»
2623 three
2624 four
2625 "});
2626 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2627 cx.assert_editor_state(indoc! {"
2628 «oneˇ» «twoˇ»
2629 three
2630 four
2631 "});
2632
2633 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2634 cx.assert_editor_state(indoc! {"
2635 «oneˇ» «twoˇ»
2636 three
2637 four
2638 "});
2639
2640 // select across line ending
2641 cx.set_state(indoc! {"
2642 one two
2643 t«hree
2644 ˇ» four
2645 "});
2646 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2647 cx.assert_editor_state(indoc! {"
2648 one two
2649 t«hree
2650 ˇ» four
2651 "});
2652
2653 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2654 cx.assert_editor_state(indoc! {"
2655 one two
2656 t«hree
2657 ˇ» four
2658 "});
2659
2660 // Ensure that indenting/outdenting works when the cursor is at column 0.
2661 cx.set_state(indoc! {"
2662 one two
2663 ˇthree
2664 four
2665 "});
2666 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2667 cx.assert_editor_state(indoc! {"
2668 one two
2669 ˇthree
2670 four
2671 "});
2672
2673 cx.set_state(indoc! {"
2674 one two
2675 ˇ three
2676 four
2677 "});
2678 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2679 cx.assert_editor_state(indoc! {"
2680 one two
2681 ˇthree
2682 four
2683 "});
2684}
2685
2686#[gpui::test]
2687async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2688 init_test(cx, |settings| {
2689 settings.defaults.hard_tabs = Some(true);
2690 });
2691
2692 let mut cx = EditorTestContext::new(cx).await;
2693
2694 // select two ranges on one line
2695 cx.set_state(indoc! {"
2696 «oneˇ» «twoˇ»
2697 three
2698 four
2699 "});
2700 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2701 cx.assert_editor_state(indoc! {"
2702 \t«oneˇ» «twoˇ»
2703 three
2704 four
2705 "});
2706 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2707 cx.assert_editor_state(indoc! {"
2708 \t\t«oneˇ» «twoˇ»
2709 three
2710 four
2711 "});
2712 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2713 cx.assert_editor_state(indoc! {"
2714 \t«oneˇ» «twoˇ»
2715 three
2716 four
2717 "});
2718 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2719 cx.assert_editor_state(indoc! {"
2720 «oneˇ» «twoˇ»
2721 three
2722 four
2723 "});
2724
2725 // select across a line ending
2726 cx.set_state(indoc! {"
2727 one two
2728 t«hree
2729 ˇ»four
2730 "});
2731 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2732 cx.assert_editor_state(indoc! {"
2733 one two
2734 \tt«hree
2735 ˇ»four
2736 "});
2737 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2738 cx.assert_editor_state(indoc! {"
2739 one two
2740 \t\tt«hree
2741 ˇ»four
2742 "});
2743 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2744 cx.assert_editor_state(indoc! {"
2745 one two
2746 \tt«hree
2747 ˇ»four
2748 "});
2749 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2750 cx.assert_editor_state(indoc! {"
2751 one two
2752 t«hree
2753 ˇ»four
2754 "});
2755
2756 // Ensure that indenting/outdenting works when the cursor is at column 0.
2757 cx.set_state(indoc! {"
2758 one two
2759 ˇthree
2760 four
2761 "});
2762 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2763 cx.assert_editor_state(indoc! {"
2764 one two
2765 ˇthree
2766 four
2767 "});
2768 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2769 cx.assert_editor_state(indoc! {"
2770 one two
2771 \tˇthree
2772 four
2773 "});
2774 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2775 cx.assert_editor_state(indoc! {"
2776 one two
2777 ˇthree
2778 four
2779 "});
2780}
2781
2782#[gpui::test]
2783fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2784 init_test(cx, |settings| {
2785 settings.languages.extend([
2786 (
2787 "TOML".into(),
2788 LanguageSettingsContent {
2789 tab_size: NonZeroU32::new(2),
2790 ..Default::default()
2791 },
2792 ),
2793 (
2794 "Rust".into(),
2795 LanguageSettingsContent {
2796 tab_size: NonZeroU32::new(4),
2797 ..Default::default()
2798 },
2799 ),
2800 ]);
2801 });
2802
2803 let toml_language = Arc::new(Language::new(
2804 LanguageConfig {
2805 name: "TOML".into(),
2806 ..Default::default()
2807 },
2808 None,
2809 ));
2810 let rust_language = Arc::new(Language::new(
2811 LanguageConfig {
2812 name: "Rust".into(),
2813 ..Default::default()
2814 },
2815 None,
2816 ));
2817
2818 let toml_buffer =
2819 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2820 let rust_buffer = cx.new_model(|cx| {
2821 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2822 });
2823 let multibuffer = cx.new_model(|cx| {
2824 let mut multibuffer = MultiBuffer::new(ReadWrite);
2825 multibuffer.push_excerpts(
2826 toml_buffer.clone(),
2827 [ExcerptRange {
2828 context: Point::new(0, 0)..Point::new(2, 0),
2829 primary: None,
2830 }],
2831 cx,
2832 );
2833 multibuffer.push_excerpts(
2834 rust_buffer.clone(),
2835 [ExcerptRange {
2836 context: Point::new(0, 0)..Point::new(1, 0),
2837 primary: None,
2838 }],
2839 cx,
2840 );
2841 multibuffer
2842 });
2843
2844 cx.add_window(|cx| {
2845 let mut editor = build_editor(multibuffer, cx);
2846
2847 assert_eq!(
2848 editor.text(cx),
2849 indoc! {"
2850 a = 1
2851 b = 2
2852
2853 const c: usize = 3;
2854 "}
2855 );
2856
2857 select_ranges(
2858 &mut editor,
2859 indoc! {"
2860 «aˇ» = 1
2861 b = 2
2862
2863 «const c:ˇ» usize = 3;
2864 "},
2865 cx,
2866 );
2867
2868 editor.tab(&Tab, cx);
2869 assert_text_with_selections(
2870 &mut editor,
2871 indoc! {"
2872 «aˇ» = 1
2873 b = 2
2874
2875 «const c:ˇ» usize = 3;
2876 "},
2877 cx,
2878 );
2879 editor.tab_prev(&TabPrev, cx);
2880 assert_text_with_selections(
2881 &mut editor,
2882 indoc! {"
2883 «aˇ» = 1
2884 b = 2
2885
2886 «const c:ˇ» usize = 3;
2887 "},
2888 cx,
2889 );
2890
2891 editor
2892 });
2893}
2894
2895#[gpui::test]
2896async fn test_backspace(cx: &mut gpui::TestAppContext) {
2897 init_test(cx, |_| {});
2898
2899 let mut cx = EditorTestContext::new(cx).await;
2900
2901 // Basic backspace
2902 cx.set_state(indoc! {"
2903 onˇe two three
2904 fou«rˇ» five six
2905 seven «ˇeight nine
2906 »ten
2907 "});
2908 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2909 cx.assert_editor_state(indoc! {"
2910 oˇe two three
2911 fouˇ five six
2912 seven ˇten
2913 "});
2914
2915 // Test backspace inside and around indents
2916 cx.set_state(indoc! {"
2917 zero
2918 ˇone
2919 ˇtwo
2920 ˇ ˇ ˇ three
2921 ˇ ˇ four
2922 "});
2923 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2924 cx.assert_editor_state(indoc! {"
2925 zero
2926 ˇone
2927 ˇtwo
2928 ˇ threeˇ four
2929 "});
2930
2931 // Test backspace with line_mode set to true
2932 cx.update_editor(|e, _| e.selections.line_mode = true);
2933 cx.set_state(indoc! {"
2934 The ˇquick ˇbrown
2935 fox jumps over
2936 the lazy dog
2937 ˇThe qu«ick bˇ»rown"});
2938 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2939 cx.assert_editor_state(indoc! {"
2940 ˇfox jumps over
2941 the lazy dogˇ"});
2942}
2943
2944#[gpui::test]
2945async fn test_delete(cx: &mut gpui::TestAppContext) {
2946 init_test(cx, |_| {});
2947
2948 let mut cx = EditorTestContext::new(cx).await;
2949 cx.set_state(indoc! {"
2950 onˇe two three
2951 fou«rˇ» five six
2952 seven «ˇeight nine
2953 »ten
2954 "});
2955 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2956 cx.assert_editor_state(indoc! {"
2957 onˇ two three
2958 fouˇ five six
2959 seven ˇten
2960 "});
2961
2962 // Test backspace with line_mode set to true
2963 cx.update_editor(|e, _| e.selections.line_mode = true);
2964 cx.set_state(indoc! {"
2965 The ˇquick ˇbrown
2966 fox «ˇjum»ps over
2967 the lazy dog
2968 ˇThe qu«ick bˇ»rown"});
2969 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2970 cx.assert_editor_state("ˇthe lazy dogˇ");
2971}
2972
2973#[gpui::test]
2974fn test_delete_line(cx: &mut TestAppContext) {
2975 init_test(cx, |_| {});
2976
2977 let view = cx.add_window(|cx| {
2978 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2979 build_editor(buffer, cx)
2980 });
2981 _ = view.update(cx, |view, cx| {
2982 view.change_selections(None, cx, |s| {
2983 s.select_display_ranges([
2984 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2985 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2986 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2987 ])
2988 });
2989 view.delete_line(&DeleteLine, cx);
2990 assert_eq!(view.display_text(cx), "ghi");
2991 assert_eq!(
2992 view.selections.display_ranges(cx),
2993 vec![
2994 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2995 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2996 ]
2997 );
2998 });
2999
3000 let view = cx.add_window(|cx| {
3001 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3002 build_editor(buffer, cx)
3003 });
3004 _ = view.update(cx, |view, cx| {
3005 view.change_selections(None, cx, |s| {
3006 s.select_display_ranges([
3007 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3008 ])
3009 });
3010 view.delete_line(&DeleteLine, cx);
3011 assert_eq!(view.display_text(cx), "ghi\n");
3012 assert_eq!(
3013 view.selections.display_ranges(cx),
3014 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3015 );
3016 });
3017}
3018
3019#[gpui::test]
3020fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3021 init_test(cx, |_| {});
3022
3023 cx.add_window(|cx| {
3024 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3025 let mut editor = build_editor(buffer.clone(), cx);
3026 let buffer = buffer.read(cx).as_singleton().unwrap();
3027
3028 assert_eq!(
3029 editor.selections.ranges::<Point>(cx),
3030 &[Point::new(0, 0)..Point::new(0, 0)]
3031 );
3032
3033 // When on single line, replace newline at end by space
3034 editor.join_lines(&JoinLines, cx);
3035 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3036 assert_eq!(
3037 editor.selections.ranges::<Point>(cx),
3038 &[Point::new(0, 3)..Point::new(0, 3)]
3039 );
3040
3041 // When multiple lines are selected, remove newlines that are spanned by the selection
3042 editor.change_selections(None, cx, |s| {
3043 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3044 });
3045 editor.join_lines(&JoinLines, cx);
3046 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3047 assert_eq!(
3048 editor.selections.ranges::<Point>(cx),
3049 &[Point::new(0, 11)..Point::new(0, 11)]
3050 );
3051
3052 // Undo should be transactional
3053 editor.undo(&Undo, cx);
3054 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3055 assert_eq!(
3056 editor.selections.ranges::<Point>(cx),
3057 &[Point::new(0, 5)..Point::new(2, 2)]
3058 );
3059
3060 // When joining an empty line don't insert a space
3061 editor.change_selections(None, cx, |s| {
3062 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3063 });
3064 editor.join_lines(&JoinLines, cx);
3065 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3066 assert_eq!(
3067 editor.selections.ranges::<Point>(cx),
3068 [Point::new(2, 3)..Point::new(2, 3)]
3069 );
3070
3071 // We can remove trailing newlines
3072 editor.join_lines(&JoinLines, cx);
3073 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3074 assert_eq!(
3075 editor.selections.ranges::<Point>(cx),
3076 [Point::new(2, 3)..Point::new(2, 3)]
3077 );
3078
3079 // We don't blow up on the last line
3080 editor.join_lines(&JoinLines, cx);
3081 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3082 assert_eq!(
3083 editor.selections.ranges::<Point>(cx),
3084 [Point::new(2, 3)..Point::new(2, 3)]
3085 );
3086
3087 // reset to test indentation
3088 editor.buffer.update(cx, |buffer, cx| {
3089 buffer.edit(
3090 [
3091 (Point::new(1, 0)..Point::new(1, 2), " "),
3092 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3093 ],
3094 None,
3095 cx,
3096 )
3097 });
3098
3099 // We remove any leading spaces
3100 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3101 editor.change_selections(None, cx, |s| {
3102 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3103 });
3104 editor.join_lines(&JoinLines, cx);
3105 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3106
3107 // We don't insert a space for a line containing only spaces
3108 editor.join_lines(&JoinLines, cx);
3109 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3110
3111 // We ignore any leading tabs
3112 editor.join_lines(&JoinLines, cx);
3113 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3114
3115 editor
3116 });
3117}
3118
3119#[gpui::test]
3120fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3121 init_test(cx, |_| {});
3122
3123 cx.add_window(|cx| {
3124 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3125 let mut editor = build_editor(buffer.clone(), cx);
3126 let buffer = buffer.read(cx).as_singleton().unwrap();
3127
3128 editor.change_selections(None, cx, |s| {
3129 s.select_ranges([
3130 Point::new(0, 2)..Point::new(1, 1),
3131 Point::new(1, 2)..Point::new(1, 2),
3132 Point::new(3, 1)..Point::new(3, 2),
3133 ])
3134 });
3135
3136 editor.join_lines(&JoinLines, cx);
3137 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3138
3139 assert_eq!(
3140 editor.selections.ranges::<Point>(cx),
3141 [
3142 Point::new(0, 7)..Point::new(0, 7),
3143 Point::new(1, 3)..Point::new(1, 3)
3144 ]
3145 );
3146 editor
3147 });
3148}
3149
3150#[gpui::test]
3151async fn test_join_lines_with_git_diff_base(
3152 executor: BackgroundExecutor,
3153 cx: &mut gpui::TestAppContext,
3154) {
3155 init_test(cx, |_| {});
3156
3157 let mut cx = EditorTestContext::new(cx).await;
3158
3159 let diff_base = r#"
3160 Line 0
3161 Line 1
3162 Line 2
3163 Line 3
3164 "#
3165 .unindent();
3166
3167 cx.set_state(
3168 &r#"
3169 ˇLine 0
3170 Line 1
3171 Line 2
3172 Line 3
3173 "#
3174 .unindent(),
3175 );
3176
3177 cx.set_diff_base(Some(&diff_base));
3178 executor.run_until_parked();
3179
3180 // Join lines
3181 cx.update_editor(|editor, cx| {
3182 editor.join_lines(&JoinLines, cx);
3183 });
3184 executor.run_until_parked();
3185
3186 cx.assert_editor_state(
3187 &r#"
3188 Line 0ˇ Line 1
3189 Line 2
3190 Line 3
3191 "#
3192 .unindent(),
3193 );
3194 // Join again
3195 cx.update_editor(|editor, cx| {
3196 editor.join_lines(&JoinLines, cx);
3197 });
3198 executor.run_until_parked();
3199
3200 cx.assert_editor_state(
3201 &r#"
3202 Line 0 Line 1ˇ Line 2
3203 Line 3
3204 "#
3205 .unindent(),
3206 );
3207}
3208
3209#[gpui::test]
3210async fn test_custom_newlines_cause_no_false_positive_diffs(
3211 executor: BackgroundExecutor,
3212 cx: &mut gpui::TestAppContext,
3213) {
3214 init_test(cx, |_| {});
3215 let mut cx = EditorTestContext::new(cx).await;
3216 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3217 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3218 executor.run_until_parked();
3219
3220 cx.update_editor(|editor, cx| {
3221 assert_eq!(
3222 editor
3223 .buffer()
3224 .read(cx)
3225 .snapshot(cx)
3226 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3227 .collect::<Vec<_>>(),
3228 Vec::new(),
3229 "Should not have any diffs for files with custom newlines"
3230 );
3231 });
3232}
3233
3234#[gpui::test]
3235async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3236 init_test(cx, |_| {});
3237
3238 let mut cx = EditorTestContext::new(cx).await;
3239
3240 // Test sort_lines_case_insensitive()
3241 cx.set_state(indoc! {"
3242 «z
3243 y
3244 x
3245 Z
3246 Y
3247 Xˇ»
3248 "});
3249 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3250 cx.assert_editor_state(indoc! {"
3251 «x
3252 X
3253 y
3254 Y
3255 z
3256 Zˇ»
3257 "});
3258
3259 // Test reverse_lines()
3260 cx.set_state(indoc! {"
3261 «5
3262 4
3263 3
3264 2
3265 1ˇ»
3266 "});
3267 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3268 cx.assert_editor_state(indoc! {"
3269 «1
3270 2
3271 3
3272 4
3273 5ˇ»
3274 "});
3275
3276 // Skip testing shuffle_line()
3277
3278 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3279 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3280
3281 // Don't manipulate when cursor is on single line, but expand the selection
3282 cx.set_state(indoc! {"
3283 ddˇdd
3284 ccc
3285 bb
3286 a
3287 "});
3288 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3289 cx.assert_editor_state(indoc! {"
3290 «ddddˇ»
3291 ccc
3292 bb
3293 a
3294 "});
3295
3296 // Basic manipulate case
3297 // Start selection moves to column 0
3298 // End of selection shrinks to fit shorter line
3299 cx.set_state(indoc! {"
3300 dd«d
3301 ccc
3302 bb
3303 aaaaaˇ»
3304 "});
3305 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3306 cx.assert_editor_state(indoc! {"
3307 «aaaaa
3308 bb
3309 ccc
3310 dddˇ»
3311 "});
3312
3313 // Manipulate case with newlines
3314 cx.set_state(indoc! {"
3315 dd«d
3316 ccc
3317
3318 bb
3319 aaaaa
3320
3321 ˇ»
3322 "});
3323 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3324 cx.assert_editor_state(indoc! {"
3325 «
3326
3327 aaaaa
3328 bb
3329 ccc
3330 dddˇ»
3331
3332 "});
3333
3334 // Adding new line
3335 cx.set_state(indoc! {"
3336 aa«a
3337 bbˇ»b
3338 "});
3339 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3340 cx.assert_editor_state(indoc! {"
3341 «aaa
3342 bbb
3343 added_lineˇ»
3344 "});
3345
3346 // Removing line
3347 cx.set_state(indoc! {"
3348 aa«a
3349 bbbˇ»
3350 "});
3351 cx.update_editor(|e, cx| {
3352 e.manipulate_lines(cx, |lines| {
3353 lines.pop();
3354 })
3355 });
3356 cx.assert_editor_state(indoc! {"
3357 «aaaˇ»
3358 "});
3359
3360 // Removing all lines
3361 cx.set_state(indoc! {"
3362 aa«a
3363 bbbˇ»
3364 "});
3365 cx.update_editor(|e, cx| {
3366 e.manipulate_lines(cx, |lines| {
3367 lines.drain(..);
3368 })
3369 });
3370 cx.assert_editor_state(indoc! {"
3371 ˇ
3372 "});
3373}
3374
3375#[gpui::test]
3376async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3377 init_test(cx, |_| {});
3378
3379 let mut cx = EditorTestContext::new(cx).await;
3380
3381 // Consider continuous selection as single selection
3382 cx.set_state(indoc! {"
3383 Aaa«aa
3384 cˇ»c«c
3385 bb
3386 aaaˇ»aa
3387 "});
3388 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3389 cx.assert_editor_state(indoc! {"
3390 «Aaaaa
3391 ccc
3392 bb
3393 aaaaaˇ»
3394 "});
3395
3396 cx.set_state(indoc! {"
3397 Aaa«aa
3398 cˇ»c«c
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «Aaaaa
3405 ccc
3406 bbˇ»
3407 "});
3408
3409 // Consider non continuous selection as distinct dedup operations
3410 cx.set_state(indoc! {"
3411 «aaaaa
3412 bb
3413 aaaaa
3414 aaaaaˇ»
3415
3416 aaa«aaˇ»
3417 "});
3418 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3419 cx.assert_editor_state(indoc! {"
3420 «aaaaa
3421 bbˇ»
3422
3423 «aaaaaˇ»
3424 "});
3425}
3426
3427#[gpui::test]
3428async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3429 init_test(cx, |_| {});
3430
3431 let mut cx = EditorTestContext::new(cx).await;
3432
3433 cx.set_state(indoc! {"
3434 «Aaa
3435 aAa
3436 Aaaˇ»
3437 "});
3438 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3439 cx.assert_editor_state(indoc! {"
3440 «Aaa
3441 aAaˇ»
3442 "});
3443
3444 cx.set_state(indoc! {"
3445 «Aaa
3446 aAa
3447 aaAˇ»
3448 "});
3449 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3450 cx.assert_editor_state(indoc! {"
3451 «Aaaˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Manipulate with multiple selections on a single line
3462 cx.set_state(indoc! {"
3463 dd«dd
3464 cˇ»c«c
3465 bb
3466 aaaˇ»aa
3467 "});
3468 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3469 cx.assert_editor_state(indoc! {"
3470 «aaaaa
3471 bb
3472 ccc
3473 ddddˇ»
3474 "});
3475
3476 // Manipulate with multiple disjoin selections
3477 cx.set_state(indoc! {"
3478 5«
3479 4
3480 3
3481 2
3482 1ˇ»
3483
3484 dd«dd
3485 ccc
3486 bb
3487 aaaˇ»aa
3488 "});
3489 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3490 cx.assert_editor_state(indoc! {"
3491 «1
3492 2
3493 3
3494 4
3495 5ˇ»
3496
3497 «aaaaa
3498 bb
3499 ccc
3500 ddddˇ»
3501 "});
3502
3503 // Adding lines on each selection
3504 cx.set_state(indoc! {"
3505 2«
3506 1ˇ»
3507
3508 bb«bb
3509 aaaˇ»aa
3510 "});
3511 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3512 cx.assert_editor_state(indoc! {"
3513 «2
3514 1
3515 added lineˇ»
3516
3517 «bbbb
3518 aaaaa
3519 added lineˇ»
3520 "});
3521
3522 // Removing lines on each selection
3523 cx.set_state(indoc! {"
3524 2«
3525 1ˇ»
3526
3527 bb«bb
3528 aaaˇ»aa
3529 "});
3530 cx.update_editor(|e, cx| {
3531 e.manipulate_lines(cx, |lines| {
3532 lines.pop();
3533 })
3534 });
3535 cx.assert_editor_state(indoc! {"
3536 «2ˇ»
3537
3538 «bbbbˇ»
3539 "});
3540}
3541
3542#[gpui::test]
3543async fn test_manipulate_text(cx: &mut TestAppContext) {
3544 init_test(cx, |_| {});
3545
3546 let mut cx = EditorTestContext::new(cx).await;
3547
3548 // Test convert_to_upper_case()
3549 cx.set_state(indoc! {"
3550 «hello worldˇ»
3551 "});
3552 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3553 cx.assert_editor_state(indoc! {"
3554 «HELLO WORLDˇ»
3555 "});
3556
3557 // Test convert_to_lower_case()
3558 cx.set_state(indoc! {"
3559 «HELLO WORLDˇ»
3560 "});
3561 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3562 cx.assert_editor_state(indoc! {"
3563 «hello worldˇ»
3564 "});
3565
3566 // Test multiple line, single selection case
3567 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3568 cx.set_state(indoc! {"
3569 «The quick brown
3570 fox jumps over
3571 the lazy dogˇ»
3572 "});
3573 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3574 cx.assert_editor_state(indoc! {"
3575 «The Quick Brown
3576 Fox Jumps Over
3577 The Lazy Dogˇ»
3578 "});
3579
3580 // Test multiple line, single selection case
3581 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3582 cx.set_state(indoc! {"
3583 «The quick brown
3584 fox jumps over
3585 the lazy dogˇ»
3586 "});
3587 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3588 cx.assert_editor_state(indoc! {"
3589 «TheQuickBrown
3590 FoxJumpsOver
3591 TheLazyDogˇ»
3592 "});
3593
3594 // From here on out, test more complex cases of manipulate_text()
3595
3596 // Test no selection case - should affect words cursors are in
3597 // Cursor at beginning, middle, and end of word
3598 cx.set_state(indoc! {"
3599 ˇhello big beauˇtiful worldˇ
3600 "});
3601 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3602 cx.assert_editor_state(indoc! {"
3603 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3604 "});
3605
3606 // Test multiple selections on a single line and across multiple lines
3607 cx.set_state(indoc! {"
3608 «Theˇ» quick «brown
3609 foxˇ» jumps «overˇ»
3610 the «lazyˇ» dog
3611 "});
3612 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3613 cx.assert_editor_state(indoc! {"
3614 «THEˇ» quick «BROWN
3615 FOXˇ» jumps «OVERˇ»
3616 the «LAZYˇ» dog
3617 "});
3618
3619 // Test case where text length grows
3620 cx.set_state(indoc! {"
3621 «tschüߡ»
3622 "});
3623 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3624 cx.assert_editor_state(indoc! {"
3625 «TSCHÜSSˇ»
3626 "});
3627
3628 // Test to make sure we don't crash when text shrinks
3629 cx.set_state(indoc! {"
3630 aaa_bbbˇ
3631 "});
3632 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3633 cx.assert_editor_state(indoc! {"
3634 «aaaBbbˇ»
3635 "});
3636
3637 // Test to make sure we all aware of the fact that each word can grow and shrink
3638 // Final selections should be aware of this fact
3639 cx.set_state(indoc! {"
3640 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3641 "});
3642 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3643 cx.assert_editor_state(indoc! {"
3644 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3645 "});
3646
3647 cx.set_state(indoc! {"
3648 «hElLo, WoRld!ˇ»
3649 "});
3650 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3651 cx.assert_editor_state(indoc! {"
3652 «HeLlO, wOrLD!ˇ»
3653 "});
3654}
3655
3656#[gpui::test]
3657fn test_duplicate_line(cx: &mut TestAppContext) {
3658 init_test(cx, |_| {});
3659
3660 let view = cx.add_window(|cx| {
3661 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3662 build_editor(buffer, cx)
3663 });
3664 _ = view.update(cx, |view, cx| {
3665 view.change_selections(None, cx, |s| {
3666 s.select_display_ranges([
3667 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3668 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3669 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3670 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3671 ])
3672 });
3673 view.duplicate_line_down(&DuplicateLineDown, cx);
3674 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3675 assert_eq!(
3676 view.selections.display_ranges(cx),
3677 vec![
3678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3679 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3680 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3681 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3682 ]
3683 );
3684 });
3685
3686 let view = cx.add_window(|cx| {
3687 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3688 build_editor(buffer, cx)
3689 });
3690 _ = view.update(cx, |view, cx| {
3691 view.change_selections(None, cx, |s| {
3692 s.select_display_ranges([
3693 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3694 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3695 ])
3696 });
3697 view.duplicate_line_down(&DuplicateLineDown, cx);
3698 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3699 assert_eq!(
3700 view.selections.display_ranges(cx),
3701 vec![
3702 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3703 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3704 ]
3705 );
3706 });
3707
3708 // With `move_upwards` the selections stay in place, except for
3709 // the lines inserted above them
3710 let view = cx.add_window(|cx| {
3711 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3712 build_editor(buffer, cx)
3713 });
3714 _ = view.update(cx, |view, cx| {
3715 view.change_selections(None, cx, |s| {
3716 s.select_display_ranges([
3717 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3718 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3719 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3720 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3721 ])
3722 });
3723 view.duplicate_line_up(&DuplicateLineUp, cx);
3724 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3729 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3730 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3731 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3732 ]
3733 );
3734 });
3735
3736 let view = cx.add_window(|cx| {
3737 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3738 build_editor(buffer, cx)
3739 });
3740 _ = view.update(cx, |view, cx| {
3741 view.change_selections(None, cx, |s| {
3742 s.select_display_ranges([
3743 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3744 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3745 ])
3746 });
3747 view.duplicate_line_up(&DuplicateLineUp, cx);
3748 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3749 assert_eq!(
3750 view.selections.display_ranges(cx),
3751 vec![
3752 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3753 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3754 ]
3755 );
3756 });
3757}
3758
3759#[gpui::test]
3760fn test_move_line_up_down(cx: &mut TestAppContext) {
3761 init_test(cx, |_| {});
3762
3763 let view = cx.add_window(|cx| {
3764 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3765 build_editor(buffer, cx)
3766 });
3767 _ = view.update(cx, |view, cx| {
3768 view.fold_ranges(
3769 vec![
3770 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3771 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3772 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3773 ],
3774 true,
3775 cx,
3776 );
3777 view.change_selections(None, cx, |s| {
3778 s.select_display_ranges([
3779 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3780 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3781 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3782 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3783 ])
3784 });
3785 assert_eq!(
3786 view.display_text(cx),
3787 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3788 );
3789
3790 view.move_line_up(&MoveLineUp, cx);
3791 assert_eq!(
3792 view.display_text(cx),
3793 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3794 );
3795 assert_eq!(
3796 view.selections.display_ranges(cx),
3797 vec![
3798 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3799 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3800 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3801 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3802 ]
3803 );
3804 });
3805
3806 _ = view.update(cx, |view, cx| {
3807 view.move_line_down(&MoveLineDown, cx);
3808 assert_eq!(
3809 view.display_text(cx),
3810 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3811 );
3812 assert_eq!(
3813 view.selections.display_ranges(cx),
3814 vec![
3815 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3816 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3817 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3818 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3819 ]
3820 );
3821 });
3822
3823 _ = view.update(cx, |view, cx| {
3824 view.move_line_down(&MoveLineDown, cx);
3825 assert_eq!(
3826 view.display_text(cx),
3827 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3828 );
3829 assert_eq!(
3830 view.selections.display_ranges(cx),
3831 vec![
3832 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3833 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3834 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3835 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3836 ]
3837 );
3838 });
3839
3840 _ = view.update(cx, |view, cx| {
3841 view.move_line_up(&MoveLineUp, cx);
3842 assert_eq!(
3843 view.display_text(cx),
3844 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3845 );
3846 assert_eq!(
3847 view.selections.display_ranges(cx),
3848 vec![
3849 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3850 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3851 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3852 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3853 ]
3854 );
3855 });
3856}
3857
3858#[gpui::test]
3859fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3860 init_test(cx, |_| {});
3861
3862 let editor = cx.add_window(|cx| {
3863 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3864 build_editor(buffer, cx)
3865 });
3866 _ = editor.update(cx, |editor, cx| {
3867 let snapshot = editor.buffer.read(cx).snapshot(cx);
3868 editor.insert_blocks(
3869 [BlockProperties {
3870 style: BlockStyle::Fixed,
3871 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3872 height: 1,
3873 render: Box::new(|_| div().into_any()),
3874 priority: 0,
3875 }],
3876 Some(Autoscroll::fit()),
3877 cx,
3878 );
3879 editor.change_selections(None, cx, |s| {
3880 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3881 });
3882 editor.move_line_down(&MoveLineDown, cx);
3883 });
3884}
3885
3886#[gpui::test]
3887fn test_transpose(cx: &mut TestAppContext) {
3888 init_test(cx, |_| {});
3889
3890 _ = cx.add_window(|cx| {
3891 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3892 editor.set_style(EditorStyle::default(), cx);
3893 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3894 editor.transpose(&Default::default(), cx);
3895 assert_eq!(editor.text(cx), "bac");
3896 assert_eq!(editor.selections.ranges(cx), [2..2]);
3897
3898 editor.transpose(&Default::default(), cx);
3899 assert_eq!(editor.text(cx), "bca");
3900 assert_eq!(editor.selections.ranges(cx), [3..3]);
3901
3902 editor.transpose(&Default::default(), cx);
3903 assert_eq!(editor.text(cx), "bac");
3904 assert_eq!(editor.selections.ranges(cx), [3..3]);
3905
3906 editor
3907 });
3908
3909 _ = cx.add_window(|cx| {
3910 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3911 editor.set_style(EditorStyle::default(), cx);
3912 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3913 editor.transpose(&Default::default(), cx);
3914 assert_eq!(editor.text(cx), "acb\nde");
3915 assert_eq!(editor.selections.ranges(cx), [3..3]);
3916
3917 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3918 editor.transpose(&Default::default(), cx);
3919 assert_eq!(editor.text(cx), "acbd\ne");
3920 assert_eq!(editor.selections.ranges(cx), [5..5]);
3921
3922 editor.transpose(&Default::default(), cx);
3923 assert_eq!(editor.text(cx), "acbde\n");
3924 assert_eq!(editor.selections.ranges(cx), [6..6]);
3925
3926 editor.transpose(&Default::default(), cx);
3927 assert_eq!(editor.text(cx), "acbd\ne");
3928 assert_eq!(editor.selections.ranges(cx), [6..6]);
3929
3930 editor
3931 });
3932
3933 _ = cx.add_window(|cx| {
3934 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3935 editor.set_style(EditorStyle::default(), cx);
3936 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3937 editor.transpose(&Default::default(), cx);
3938 assert_eq!(editor.text(cx), "bacd\ne");
3939 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3940
3941 editor.transpose(&Default::default(), cx);
3942 assert_eq!(editor.text(cx), "bcade\n");
3943 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3944
3945 editor.transpose(&Default::default(), cx);
3946 assert_eq!(editor.text(cx), "bcda\ne");
3947 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3948
3949 editor.transpose(&Default::default(), cx);
3950 assert_eq!(editor.text(cx), "bcade\n");
3951 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3952
3953 editor.transpose(&Default::default(), cx);
3954 assert_eq!(editor.text(cx), "bcaed\n");
3955 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3956
3957 editor
3958 });
3959
3960 _ = cx.add_window(|cx| {
3961 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3962 editor.set_style(EditorStyle::default(), cx);
3963 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3964 editor.transpose(&Default::default(), cx);
3965 assert_eq!(editor.text(cx), "🏀🍐✋");
3966 assert_eq!(editor.selections.ranges(cx), [8..8]);
3967
3968 editor.transpose(&Default::default(), cx);
3969 assert_eq!(editor.text(cx), "🏀✋🍐");
3970 assert_eq!(editor.selections.ranges(cx), [11..11]);
3971
3972 editor.transpose(&Default::default(), cx);
3973 assert_eq!(editor.text(cx), "🏀🍐✋");
3974 assert_eq!(editor.selections.ranges(cx), [11..11]);
3975
3976 editor
3977 });
3978}
3979
3980#[gpui::test]
3981async fn test_rewrap(cx: &mut TestAppContext) {
3982 init_test(cx, |_| {});
3983
3984 let mut cx = EditorTestContext::new(cx).await;
3985
3986 {
3987 let language = Arc::new(Language::new(
3988 LanguageConfig {
3989 line_comments: vec!["// ".into()],
3990 ..LanguageConfig::default()
3991 },
3992 None,
3993 ));
3994 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3995
3996 let unwrapped_text = indoc! {"
3997 // ˇ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.
3998 "};
3999
4000 let wrapped_text = indoc! {"
4001 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4002 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4003 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4004 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4005 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4006 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4007 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4008 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4009 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4010 // porttitor id. Aliquam id accumsan eros.ˇ
4011 "};
4012
4013 cx.set_state(unwrapped_text);
4014 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4015 cx.assert_editor_state(wrapped_text);
4016 }
4017
4018 // Test that rewrapping works inside of a selection
4019 {
4020 let language = Arc::new(Language::new(
4021 LanguageConfig {
4022 line_comments: vec!["// ".into()],
4023 ..LanguageConfig::default()
4024 },
4025 None,
4026 ));
4027 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4028
4029 let unwrapped_text = indoc! {"
4030 «// 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.ˇ»
4031 "};
4032
4033 let wrapped_text = indoc! {"
4034 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4035 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4036 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4037 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4038 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4039 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4040 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4041 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4042 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4043 // porttitor id. Aliquam id accumsan eros.ˇ
4044 "};
4045
4046 cx.set_state(unwrapped_text);
4047 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4048 cx.assert_editor_state(wrapped_text);
4049 }
4050
4051 // Test that cursors that expand to the same region are collapsed.
4052 {
4053 let language = Arc::new(Language::new(
4054 LanguageConfig {
4055 line_comments: vec!["// ".into()],
4056 ..LanguageConfig::default()
4057 },
4058 None,
4059 ));
4060 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4061
4062 let unwrapped_text = indoc! {"
4063 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4064 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4065 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4066 // ˇ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.
4067 "};
4068
4069 let wrapped_text = indoc! {"
4070 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4071 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4072 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4073 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4074 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4075 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4076 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4077 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4078 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4079 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4080 "};
4081
4082 cx.set_state(unwrapped_text);
4083 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4084 cx.assert_editor_state(wrapped_text);
4085 }
4086
4087 // Test that non-contiguous selections are treated separately.
4088 {
4089 let language = Arc::new(Language::new(
4090 LanguageConfig {
4091 line_comments: vec!["// ".into()],
4092 ..LanguageConfig::default()
4093 },
4094 None,
4095 ));
4096 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4097
4098 let unwrapped_text = indoc! {"
4099 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4100 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4101 //
4102 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4103 // ˇ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.
4104 "};
4105
4106 let wrapped_text = indoc! {"
4107 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4108 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4109 // auctor, eu lacinia sapien scelerisque.ˇˇ
4110 //
4111 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4112 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4113 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4114 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4115 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4116 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4117 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4118 "};
4119
4120 cx.set_state(unwrapped_text);
4121 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4122 cx.assert_editor_state(wrapped_text);
4123 }
4124
4125 // Test that different comment prefixes are supported.
4126 {
4127 let language = Arc::new(Language::new(
4128 LanguageConfig {
4129 line_comments: vec!["# ".into()],
4130 ..LanguageConfig::default()
4131 },
4132 None,
4133 ));
4134 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4135
4136 let unwrapped_text = indoc! {"
4137 # ˇ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.
4138 "};
4139
4140 let wrapped_text = indoc! {"
4141 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4142 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4143 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4144 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4145 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4146 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4147 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4148 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4149 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4150 # accumsan eros.ˇ
4151 "};
4152
4153 cx.set_state(unwrapped_text);
4154 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4155 cx.assert_editor_state(wrapped_text);
4156 }
4157
4158 // Test that rewrapping is ignored outside of comments in most languages.
4159 {
4160 let language = Arc::new(Language::new(
4161 LanguageConfig {
4162 line_comments: vec!["// ".into(), "/// ".into()],
4163 ..LanguageConfig::default()
4164 },
4165 Some(tree_sitter_rust::LANGUAGE.into()),
4166 ));
4167 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4168
4169 let unwrapped_text = indoc! {"
4170 /// Adds two numbers.
4171 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4172 fn add(a: u32, b: u32) -> u32 {
4173 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ˇ
4174 }
4175 "};
4176
4177 let wrapped_text = indoc! {"
4178 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4179 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4180 fn add(a: u32, b: u32) -> u32 {
4181 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ˇ
4182 }
4183 "};
4184
4185 cx.set_state(unwrapped_text);
4186 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4187 cx.assert_editor_state(wrapped_text);
4188 }
4189
4190 // Test that rewrapping works in Markdown and Plain Text languages.
4191 {
4192 let markdown_language = Arc::new(Language::new(
4193 LanguageConfig {
4194 name: "Markdown".into(),
4195 ..LanguageConfig::default()
4196 },
4197 None,
4198 ));
4199 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4200
4201 let unwrapped_text = indoc! {"
4202 # Hello
4203
4204 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.
4205 "};
4206
4207 let wrapped_text = indoc! {"
4208 # Hello
4209
4210 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4211 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4212 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4213 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4214 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4215 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4216 Integer sit amet scelerisque nisi.ˇ
4217 "};
4218
4219 cx.set_state(unwrapped_text);
4220 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4221 cx.assert_editor_state(wrapped_text);
4222
4223 let plaintext_language = Arc::new(Language::new(
4224 LanguageConfig {
4225 name: "Plain Text".into(),
4226 ..LanguageConfig::default()
4227 },
4228 None,
4229 ));
4230 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4231
4232 let unwrapped_text = indoc! {"
4233 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.
4234 "};
4235
4236 let wrapped_text = 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 auctor,
4239 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4240 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4241 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4242 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4243 Integer sit amet scelerisque nisi.ˇ
4244 "};
4245
4246 cx.set_state(unwrapped_text);
4247 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4248 cx.assert_editor_state(wrapped_text);
4249 }
4250
4251 // Test rewrapping unaligned comments in a selection.
4252 {
4253 let language = Arc::new(Language::new(
4254 LanguageConfig {
4255 line_comments: vec!["// ".into(), "/// ".into()],
4256 ..LanguageConfig::default()
4257 },
4258 Some(tree_sitter_rust::LANGUAGE.into()),
4259 ));
4260 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4261
4262 let unwrapped_text = indoc! {"
4263 fn foo() {
4264 if true {
4265 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4266 // Praesent semper egestas tellus id dignissim.ˇ»
4267 do_something();
4268 } else {
4269 //
4270 }
4271 }
4272 "};
4273
4274 let wrapped_text = indoc! {"
4275 fn foo() {
4276 if true {
4277 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4278 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4279 // egestas tellus id dignissim.ˇ
4280 do_something();
4281 } else {
4282 //
4283 }
4284 }
4285 "};
4286
4287 cx.set_state(unwrapped_text);
4288 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4289 cx.assert_editor_state(wrapped_text);
4290
4291 let unwrapped_text = indoc! {"
4292 fn foo() {
4293 if true {
4294 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4295 // Praesent semper egestas tellus id dignissim.»
4296 do_something();
4297 } else {
4298 //
4299 }
4300
4301 }
4302 "};
4303
4304 let wrapped_text = indoc! {"
4305 fn foo() {
4306 if true {
4307 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4308 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4309 // egestas tellus id dignissim.ˇ
4310 do_something();
4311 } else {
4312 //
4313 }
4314
4315 }
4316 "};
4317
4318 cx.set_state(unwrapped_text);
4319 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4320 cx.assert_editor_state(wrapped_text);
4321 }
4322}
4323
4324#[gpui::test]
4325async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4326 init_test(cx, |_| {});
4327
4328 let mut cx = EditorTestContext::new(cx).await;
4329
4330 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4331 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4332 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4333
4334 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4335 cx.set_state("two ˇfour ˇsix ˇ");
4336 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4337 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4338
4339 // Paste again but with only two cursors. Since the number of cursors doesn't
4340 // match the number of slices in the clipboard, the entire clipboard text
4341 // is pasted at each cursor.
4342 cx.set_state("ˇtwo one✅ four three six five ˇ");
4343 cx.update_editor(|e, cx| {
4344 e.handle_input("( ", cx);
4345 e.paste(&Paste, cx);
4346 e.handle_input(") ", cx);
4347 });
4348 cx.assert_editor_state(
4349 &([
4350 "( one✅ ",
4351 "three ",
4352 "five ) ˇtwo one✅ four three six five ( one✅ ",
4353 "three ",
4354 "five ) ˇ",
4355 ]
4356 .join("\n")),
4357 );
4358
4359 // Cut with three selections, one of which is full-line.
4360 cx.set_state(indoc! {"
4361 1«2ˇ»3
4362 4ˇ567
4363 «8ˇ»9"});
4364 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4365 cx.assert_editor_state(indoc! {"
4366 1ˇ3
4367 ˇ9"});
4368
4369 // Paste with three selections, noticing how the copied selection that was full-line
4370 // gets inserted before the second cursor.
4371 cx.set_state(indoc! {"
4372 1ˇ3
4373 9ˇ
4374 «oˇ»ne"});
4375 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4376 cx.assert_editor_state(indoc! {"
4377 12ˇ3
4378 4567
4379 9ˇ
4380 8ˇne"});
4381
4382 // Copy with a single cursor only, which writes the whole line into the clipboard.
4383 cx.set_state(indoc! {"
4384 The quick brown
4385 fox juˇmps over
4386 the lazy dog"});
4387 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4388 assert_eq!(
4389 cx.read_from_clipboard()
4390 .and_then(|item| item.text().as_deref().map(str::to_string)),
4391 Some("fox jumps over\n".to_string())
4392 );
4393
4394 // Paste with three selections, noticing how the copied full-line selection is inserted
4395 // before the empty selections but replaces the selection that is non-empty.
4396 cx.set_state(indoc! {"
4397 Tˇhe quick brown
4398 «foˇ»x jumps over
4399 tˇhe lazy dog"});
4400 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4401 cx.assert_editor_state(indoc! {"
4402 fox jumps over
4403 Tˇhe quick brown
4404 fox jumps over
4405 ˇx jumps over
4406 fox jumps over
4407 tˇhe lazy dog"});
4408}
4409
4410#[gpui::test]
4411async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4412 init_test(cx, |_| {});
4413
4414 let mut cx = EditorTestContext::new(cx).await;
4415 let language = Arc::new(Language::new(
4416 LanguageConfig::default(),
4417 Some(tree_sitter_rust::LANGUAGE.into()),
4418 ));
4419 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4420
4421 // Cut an indented block, without the leading whitespace.
4422 cx.set_state(indoc! {"
4423 const a: B = (
4424 c(),
4425 «d(
4426 e,
4427 f
4428 )ˇ»
4429 );
4430 "});
4431 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4432 cx.assert_editor_state(indoc! {"
4433 const a: B = (
4434 c(),
4435 ˇ
4436 );
4437 "});
4438
4439 // Paste it at the same position.
4440 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4441 cx.assert_editor_state(indoc! {"
4442 const a: B = (
4443 c(),
4444 d(
4445 e,
4446 f
4447 )ˇ
4448 );
4449 "});
4450
4451 // Paste it at a line with a lower indent level.
4452 cx.set_state(indoc! {"
4453 ˇ
4454 const a: B = (
4455 c(),
4456 );
4457 "});
4458 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4459 cx.assert_editor_state(indoc! {"
4460 d(
4461 e,
4462 f
4463 )ˇ
4464 const a: B = (
4465 c(),
4466 );
4467 "});
4468
4469 // Cut an indented block, with the leading whitespace.
4470 cx.set_state(indoc! {"
4471 const a: B = (
4472 c(),
4473 « d(
4474 e,
4475 f
4476 )
4477 ˇ»);
4478 "});
4479 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4480 cx.assert_editor_state(indoc! {"
4481 const a: B = (
4482 c(),
4483 ˇ);
4484 "});
4485
4486 // Paste it at the same position.
4487 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4488 cx.assert_editor_state(indoc! {"
4489 const a: B = (
4490 c(),
4491 d(
4492 e,
4493 f
4494 )
4495 ˇ);
4496 "});
4497
4498 // Paste it at a line with a higher indent level.
4499 cx.set_state(indoc! {"
4500 const a: B = (
4501 c(),
4502 d(
4503 e,
4504 fˇ
4505 )
4506 );
4507 "});
4508 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4509 cx.assert_editor_state(indoc! {"
4510 const a: B = (
4511 c(),
4512 d(
4513 e,
4514 f d(
4515 e,
4516 f
4517 )
4518 ˇ
4519 )
4520 );
4521 "});
4522}
4523
4524#[gpui::test]
4525fn test_select_all(cx: &mut TestAppContext) {
4526 init_test(cx, |_| {});
4527
4528 let view = cx.add_window(|cx| {
4529 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4530 build_editor(buffer, cx)
4531 });
4532 _ = view.update(cx, |view, cx| {
4533 view.select_all(&SelectAll, cx);
4534 assert_eq!(
4535 view.selections.display_ranges(cx),
4536 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4537 );
4538 });
4539}
4540
4541#[gpui::test]
4542fn test_select_line(cx: &mut TestAppContext) {
4543 init_test(cx, |_| {});
4544
4545 let view = cx.add_window(|cx| {
4546 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4547 build_editor(buffer, cx)
4548 });
4549 _ = view.update(cx, |view, cx| {
4550 view.change_selections(None, cx, |s| {
4551 s.select_display_ranges([
4552 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4553 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4554 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4555 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4556 ])
4557 });
4558 view.select_line(&SelectLine, cx);
4559 assert_eq!(
4560 view.selections.display_ranges(cx),
4561 vec![
4562 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4563 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4564 ]
4565 );
4566 });
4567
4568 _ = view.update(cx, |view, cx| {
4569 view.select_line(&SelectLine, cx);
4570 assert_eq!(
4571 view.selections.display_ranges(cx),
4572 vec![
4573 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4574 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4575 ]
4576 );
4577 });
4578
4579 _ = view.update(cx, |view, cx| {
4580 view.select_line(&SelectLine, cx);
4581 assert_eq!(
4582 view.selections.display_ranges(cx),
4583 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4584 );
4585 });
4586}
4587
4588#[gpui::test]
4589fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4590 init_test(cx, |_| {});
4591
4592 let view = cx.add_window(|cx| {
4593 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4594 build_editor(buffer, cx)
4595 });
4596 _ = view.update(cx, |view, cx| {
4597 view.fold_ranges(
4598 vec![
4599 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4600 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4601 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4602 ],
4603 true,
4604 cx,
4605 );
4606 view.change_selections(None, cx, |s| {
4607 s.select_display_ranges([
4608 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4609 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4610 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4611 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4612 ])
4613 });
4614 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4615 });
4616
4617 _ = view.update(cx, |view, cx| {
4618 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4619 assert_eq!(
4620 view.display_text(cx),
4621 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4622 );
4623 assert_eq!(
4624 view.selections.display_ranges(cx),
4625 [
4626 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4627 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4628 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4629 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4630 ]
4631 );
4632 });
4633
4634 _ = view.update(cx, |view, cx| {
4635 view.change_selections(None, cx, |s| {
4636 s.select_display_ranges([
4637 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4638 ])
4639 });
4640 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4641 assert_eq!(
4642 view.display_text(cx),
4643 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4644 );
4645 assert_eq!(
4646 view.selections.display_ranges(cx),
4647 [
4648 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4649 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4650 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4651 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4652 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4653 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4654 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4655 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4656 ]
4657 );
4658 });
4659}
4660
4661#[gpui::test]
4662async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4663 init_test(cx, |_| {});
4664
4665 let mut cx = EditorTestContext::new(cx).await;
4666
4667 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4668 cx.set_state(indoc!(
4669 r#"abc
4670 defˇghi
4671
4672 jk
4673 nlmo
4674 "#
4675 ));
4676
4677 cx.update_editor(|editor, cx| {
4678 editor.add_selection_above(&Default::default(), cx);
4679 });
4680
4681 cx.assert_editor_state(indoc!(
4682 r#"abcˇ
4683 defˇghi
4684
4685 jk
4686 nlmo
4687 "#
4688 ));
4689
4690 cx.update_editor(|editor, cx| {
4691 editor.add_selection_above(&Default::default(), cx);
4692 });
4693
4694 cx.assert_editor_state(indoc!(
4695 r#"abcˇ
4696 defˇghi
4697
4698 jk
4699 nlmo
4700 "#
4701 ));
4702
4703 cx.update_editor(|view, cx| {
4704 view.add_selection_below(&Default::default(), cx);
4705 });
4706
4707 cx.assert_editor_state(indoc!(
4708 r#"abc
4709 defˇghi
4710
4711 jk
4712 nlmo
4713 "#
4714 ));
4715
4716 cx.update_editor(|view, cx| {
4717 view.undo_selection(&Default::default(), cx);
4718 });
4719
4720 cx.assert_editor_state(indoc!(
4721 r#"abcˇ
4722 defˇghi
4723
4724 jk
4725 nlmo
4726 "#
4727 ));
4728
4729 cx.update_editor(|view, cx| {
4730 view.redo_selection(&Default::default(), cx);
4731 });
4732
4733 cx.assert_editor_state(indoc!(
4734 r#"abc
4735 defˇghi
4736
4737 jk
4738 nlmo
4739 "#
4740 ));
4741
4742 cx.update_editor(|view, cx| {
4743 view.add_selection_below(&Default::default(), cx);
4744 });
4745
4746 cx.assert_editor_state(indoc!(
4747 r#"abc
4748 defˇghi
4749
4750 jk
4751 nlmˇo
4752 "#
4753 ));
4754
4755 cx.update_editor(|view, cx| {
4756 view.add_selection_below(&Default::default(), cx);
4757 });
4758
4759 cx.assert_editor_state(indoc!(
4760 r#"abc
4761 defˇghi
4762
4763 jk
4764 nlmˇo
4765 "#
4766 ));
4767
4768 // change selections
4769 cx.set_state(indoc!(
4770 r#"abc
4771 def«ˇg»hi
4772
4773 jk
4774 nlmo
4775 "#
4776 ));
4777
4778 cx.update_editor(|view, cx| {
4779 view.add_selection_below(&Default::default(), cx);
4780 });
4781
4782 cx.assert_editor_state(indoc!(
4783 r#"abc
4784 def«ˇg»hi
4785
4786 jk
4787 nlm«ˇo»
4788 "#
4789 ));
4790
4791 cx.update_editor(|view, cx| {
4792 view.add_selection_below(&Default::default(), cx);
4793 });
4794
4795 cx.assert_editor_state(indoc!(
4796 r#"abc
4797 def«ˇg»hi
4798
4799 jk
4800 nlm«ˇo»
4801 "#
4802 ));
4803
4804 cx.update_editor(|view, cx| {
4805 view.add_selection_above(&Default::default(), cx);
4806 });
4807
4808 cx.assert_editor_state(indoc!(
4809 r#"abc
4810 def«ˇg»hi
4811
4812 jk
4813 nlmo
4814 "#
4815 ));
4816
4817 cx.update_editor(|view, cx| {
4818 view.add_selection_above(&Default::default(), cx);
4819 });
4820
4821 cx.assert_editor_state(indoc!(
4822 r#"abc
4823 def«ˇg»hi
4824
4825 jk
4826 nlmo
4827 "#
4828 ));
4829
4830 // Change selections again
4831 cx.set_state(indoc!(
4832 r#"a«bc
4833 defgˇ»hi
4834
4835 jk
4836 nlmo
4837 "#
4838 ));
4839
4840 cx.update_editor(|view, cx| {
4841 view.add_selection_below(&Default::default(), cx);
4842 });
4843
4844 cx.assert_editor_state(indoc!(
4845 r#"a«bcˇ»
4846 d«efgˇ»hi
4847
4848 j«kˇ»
4849 nlmo
4850 "#
4851 ));
4852
4853 cx.update_editor(|view, cx| {
4854 view.add_selection_below(&Default::default(), cx);
4855 });
4856 cx.assert_editor_state(indoc!(
4857 r#"a«bcˇ»
4858 d«efgˇ»hi
4859
4860 j«kˇ»
4861 n«lmoˇ»
4862 "#
4863 ));
4864 cx.update_editor(|view, cx| {
4865 view.add_selection_above(&Default::default(), cx);
4866 });
4867
4868 cx.assert_editor_state(indoc!(
4869 r#"a«bcˇ»
4870 d«efgˇ»hi
4871
4872 j«kˇ»
4873 nlmo
4874 "#
4875 ));
4876
4877 // Change selections again
4878 cx.set_state(indoc!(
4879 r#"abc
4880 d«ˇefghi
4881
4882 jk
4883 nlm»o
4884 "#
4885 ));
4886
4887 cx.update_editor(|view, cx| {
4888 view.add_selection_above(&Default::default(), cx);
4889 });
4890
4891 cx.assert_editor_state(indoc!(
4892 r#"a«ˇbc»
4893 d«ˇef»ghi
4894
4895 j«ˇk»
4896 n«ˇlm»o
4897 "#
4898 ));
4899
4900 cx.update_editor(|view, cx| {
4901 view.add_selection_below(&Default::default(), cx);
4902 });
4903
4904 cx.assert_editor_state(indoc!(
4905 r#"abc
4906 d«ˇef»ghi
4907
4908 j«ˇk»
4909 n«ˇlm»o
4910 "#
4911 ));
4912}
4913
4914#[gpui::test]
4915async fn test_select_next(cx: &mut gpui::TestAppContext) {
4916 init_test(cx, |_| {});
4917
4918 let mut cx = EditorTestContext::new(cx).await;
4919 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4920
4921 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4922 .unwrap();
4923 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4924
4925 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4926 .unwrap();
4927 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4928
4929 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4930 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4931
4932 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4933 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4934
4935 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4936 .unwrap();
4937 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4938
4939 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4940 .unwrap();
4941 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4942}
4943
4944#[gpui::test]
4945async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4946 init_test(cx, |_| {});
4947
4948 let mut cx = EditorTestContext::new(cx).await;
4949 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4950
4951 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4952 .unwrap();
4953 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4954}
4955
4956#[gpui::test]
4957async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4958 init_test(cx, |_| {});
4959
4960 let mut cx = EditorTestContext::new(cx).await;
4961 cx.set_state(
4962 r#"let foo = 2;
4963lˇet foo = 2;
4964let fooˇ = 2;
4965let foo = 2;
4966let foo = ˇ2;"#,
4967 );
4968
4969 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4970 .unwrap();
4971 cx.assert_editor_state(
4972 r#"let foo = 2;
4973«letˇ» foo = 2;
4974let «fooˇ» = 2;
4975let foo = 2;
4976let foo = «2ˇ»;"#,
4977 );
4978
4979 // noop for multiple selections with different contents
4980 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4981 .unwrap();
4982 cx.assert_editor_state(
4983 r#"let foo = 2;
4984«letˇ» foo = 2;
4985let «fooˇ» = 2;
4986let foo = 2;
4987let foo = «2ˇ»;"#,
4988 );
4989}
4990
4991#[gpui::test]
4992async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4993 init_test(cx, |_| {});
4994
4995 let mut cx =
4996 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
4997
4998 cx.assert_editor_state(indoc! {"
4999 ˇbbb
5000 ccc
5001
5002 bbb
5003 ccc
5004 "});
5005 cx.dispatch_action(SelectPrevious::default());
5006 cx.assert_editor_state(indoc! {"
5007 «bbbˇ»
5008 ccc
5009
5010 bbb
5011 ccc
5012 "});
5013 cx.dispatch_action(SelectPrevious::default());
5014 cx.assert_editor_state(indoc! {"
5015 «bbbˇ»
5016 ccc
5017
5018 «bbbˇ»
5019 ccc
5020 "});
5021}
5022
5023#[gpui::test]
5024async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5025 init_test(cx, |_| {});
5026
5027 let mut cx = EditorTestContext::new(cx).await;
5028 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5029
5030 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5031 .unwrap();
5032 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5033
5034 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5035 .unwrap();
5036 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5037
5038 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5039 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5040
5041 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5042 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5043
5044 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5045 .unwrap();
5046 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5047
5048 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5049 .unwrap();
5050 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5051
5052 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5053 .unwrap();
5054 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5055}
5056
5057#[gpui::test]
5058async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5059 init_test(cx, |_| {});
5060
5061 let mut cx = EditorTestContext::new(cx).await;
5062 cx.set_state(
5063 r#"let foo = 2;
5064lˇet foo = 2;
5065let fooˇ = 2;
5066let foo = 2;
5067let foo = ˇ2;"#,
5068 );
5069
5070 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5071 .unwrap();
5072 cx.assert_editor_state(
5073 r#"let foo = 2;
5074«letˇ» foo = 2;
5075let «fooˇ» = 2;
5076let foo = 2;
5077let foo = «2ˇ»;"#,
5078 );
5079
5080 // noop for multiple selections with different contents
5081 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5082 .unwrap();
5083 cx.assert_editor_state(
5084 r#"let foo = 2;
5085«letˇ» foo = 2;
5086let «fooˇ» = 2;
5087let foo = 2;
5088let foo = «2ˇ»;"#,
5089 );
5090}
5091
5092#[gpui::test]
5093async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5094 init_test(cx, |_| {});
5095
5096 let mut cx = EditorTestContext::new(cx).await;
5097 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5098
5099 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5100 .unwrap();
5101 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5102
5103 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5104 .unwrap();
5105 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5106
5107 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5108 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5109
5110 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5111 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5112
5113 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5114 .unwrap();
5115 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5116
5117 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5118 .unwrap();
5119 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5120}
5121
5122#[gpui::test]
5123async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5124 init_test(cx, |_| {});
5125
5126 let language = Arc::new(Language::new(
5127 LanguageConfig::default(),
5128 Some(tree_sitter_rust::LANGUAGE.into()),
5129 ));
5130
5131 let text = r#"
5132 use mod1::mod2::{mod3, mod4};
5133
5134 fn fn_1(param1: bool, param2: &str) {
5135 let var1 = "text";
5136 }
5137 "#
5138 .unindent();
5139
5140 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5141 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5142 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5143
5144 editor
5145 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5146 .await;
5147
5148 editor.update(cx, |view, cx| {
5149 view.change_selections(None, cx, |s| {
5150 s.select_display_ranges([
5151 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5152 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5153 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5154 ]);
5155 });
5156 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5157 });
5158 editor.update(cx, |editor, cx| {
5159 assert_text_with_selections(
5160 editor,
5161 indoc! {r#"
5162 use mod1::mod2::{mod3, «mod4ˇ»};
5163
5164 fn fn_1«ˇ(param1: bool, param2: &str)» {
5165 let var1 = "«textˇ»";
5166 }
5167 "#},
5168 cx,
5169 );
5170 });
5171
5172 editor.update(cx, |view, cx| {
5173 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5174 });
5175 editor.update(cx, |editor, cx| {
5176 assert_text_with_selections(
5177 editor,
5178 indoc! {r#"
5179 use mod1::mod2::«{mod3, mod4}ˇ»;
5180
5181 «ˇfn fn_1(param1: bool, param2: &str) {
5182 let var1 = "text";
5183 }»
5184 "#},
5185 cx,
5186 );
5187 });
5188
5189 editor.update(cx, |view, cx| {
5190 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5191 });
5192 assert_eq!(
5193 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5194 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5195 );
5196
5197 // Trying to expand the selected syntax node one more time has no effect.
5198 editor.update(cx, |view, cx| {
5199 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5200 });
5201 assert_eq!(
5202 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5203 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5204 );
5205
5206 editor.update(cx, |view, cx| {
5207 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5208 });
5209 editor.update(cx, |editor, cx| {
5210 assert_text_with_selections(
5211 editor,
5212 indoc! {r#"
5213 use mod1::mod2::«{mod3, mod4}ˇ»;
5214
5215 «ˇfn fn_1(param1: bool, param2: &str) {
5216 let var1 = "text";
5217 }»
5218 "#},
5219 cx,
5220 );
5221 });
5222
5223 editor.update(cx, |view, cx| {
5224 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5225 });
5226 editor.update(cx, |editor, cx| {
5227 assert_text_with_selections(
5228 editor,
5229 indoc! {r#"
5230 use mod1::mod2::{mod3, «mod4ˇ»};
5231
5232 fn fn_1«ˇ(param1: bool, param2: &str)» {
5233 let var1 = "«textˇ»";
5234 }
5235 "#},
5236 cx,
5237 );
5238 });
5239
5240 editor.update(cx, |view, cx| {
5241 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5242 });
5243 editor.update(cx, |editor, cx| {
5244 assert_text_with_selections(
5245 editor,
5246 indoc! {r#"
5247 use mod1::mod2::{mod3, mo«ˇ»d4};
5248
5249 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5250 let var1 = "te«ˇ»xt";
5251 }
5252 "#},
5253 cx,
5254 );
5255 });
5256
5257 // Trying to shrink the selected syntax node one more time has no effect.
5258 editor.update(cx, |view, cx| {
5259 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5260 });
5261 editor.update(cx, |editor, cx| {
5262 assert_text_with_selections(
5263 editor,
5264 indoc! {r#"
5265 use mod1::mod2::{mod3, mo«ˇ»d4};
5266
5267 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5268 let var1 = "te«ˇ»xt";
5269 }
5270 "#},
5271 cx,
5272 );
5273 });
5274
5275 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5276 // a fold.
5277 editor.update(cx, |view, cx| {
5278 view.fold_ranges(
5279 vec![
5280 (
5281 Point::new(0, 21)..Point::new(0, 24),
5282 FoldPlaceholder::test(),
5283 ),
5284 (
5285 Point::new(3, 20)..Point::new(3, 22),
5286 FoldPlaceholder::test(),
5287 ),
5288 ],
5289 true,
5290 cx,
5291 );
5292 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5293 });
5294 editor.update(cx, |editor, cx| {
5295 assert_text_with_selections(
5296 editor,
5297 indoc! {r#"
5298 use mod1::mod2::«{mod3, mod4}ˇ»;
5299
5300 fn fn_1«ˇ(param1: bool, param2: &str)» {
5301 «let var1 = "text";ˇ»
5302 }
5303 "#},
5304 cx,
5305 );
5306 });
5307}
5308
5309#[gpui::test]
5310async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5311 init_test(cx, |_| {});
5312
5313 let language = Arc::new(
5314 Language::new(
5315 LanguageConfig {
5316 brackets: BracketPairConfig {
5317 pairs: vec![
5318 BracketPair {
5319 start: "{".to_string(),
5320 end: "}".to_string(),
5321 close: false,
5322 surround: false,
5323 newline: true,
5324 },
5325 BracketPair {
5326 start: "(".to_string(),
5327 end: ")".to_string(),
5328 close: false,
5329 surround: false,
5330 newline: true,
5331 },
5332 ],
5333 ..Default::default()
5334 },
5335 ..Default::default()
5336 },
5337 Some(tree_sitter_rust::LANGUAGE.into()),
5338 )
5339 .with_indents_query(
5340 r#"
5341 (_ "(" ")" @end) @indent
5342 (_ "{" "}" @end) @indent
5343 "#,
5344 )
5345 .unwrap(),
5346 );
5347
5348 let text = "fn a() {}";
5349
5350 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5351 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5352 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5353 editor
5354 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5355 .await;
5356
5357 editor.update(cx, |editor, cx| {
5358 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5359 editor.newline(&Newline, cx);
5360 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5361 assert_eq!(
5362 editor.selections.ranges(cx),
5363 &[
5364 Point::new(1, 4)..Point::new(1, 4),
5365 Point::new(3, 4)..Point::new(3, 4),
5366 Point::new(5, 0)..Point::new(5, 0)
5367 ]
5368 );
5369 });
5370}
5371
5372#[gpui::test]
5373async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5374 init_test(cx, |_| {});
5375
5376 let mut cx = EditorTestContext::new(cx).await;
5377
5378 let language = Arc::new(Language::new(
5379 LanguageConfig {
5380 brackets: BracketPairConfig {
5381 pairs: vec![
5382 BracketPair {
5383 start: "{".to_string(),
5384 end: "}".to_string(),
5385 close: true,
5386 surround: true,
5387 newline: true,
5388 },
5389 BracketPair {
5390 start: "(".to_string(),
5391 end: ")".to_string(),
5392 close: true,
5393 surround: true,
5394 newline: true,
5395 },
5396 BracketPair {
5397 start: "/*".to_string(),
5398 end: " */".to_string(),
5399 close: true,
5400 surround: true,
5401 newline: true,
5402 },
5403 BracketPair {
5404 start: "[".to_string(),
5405 end: "]".to_string(),
5406 close: false,
5407 surround: false,
5408 newline: true,
5409 },
5410 BracketPair {
5411 start: "\"".to_string(),
5412 end: "\"".to_string(),
5413 close: true,
5414 surround: true,
5415 newline: false,
5416 },
5417 BracketPair {
5418 start: "<".to_string(),
5419 end: ">".to_string(),
5420 close: false,
5421 surround: true,
5422 newline: true,
5423 },
5424 ],
5425 ..Default::default()
5426 },
5427 autoclose_before: "})]".to_string(),
5428 ..Default::default()
5429 },
5430 Some(tree_sitter_rust::LANGUAGE.into()),
5431 ));
5432
5433 cx.language_registry().add(language.clone());
5434 cx.update_buffer(|buffer, cx| {
5435 buffer.set_language(Some(language), cx);
5436 });
5437
5438 cx.set_state(
5439 &r#"
5440 🏀ˇ
5441 εˇ
5442 ❤️ˇ
5443 "#
5444 .unindent(),
5445 );
5446
5447 // autoclose multiple nested brackets at multiple cursors
5448 cx.update_editor(|view, cx| {
5449 view.handle_input("{", cx);
5450 view.handle_input("{", cx);
5451 view.handle_input("{", cx);
5452 });
5453 cx.assert_editor_state(
5454 &"
5455 🏀{{{ˇ}}}
5456 ε{{{ˇ}}}
5457 ❤️{{{ˇ}}}
5458 "
5459 .unindent(),
5460 );
5461
5462 // insert a different closing bracket
5463 cx.update_editor(|view, cx| {
5464 view.handle_input(")", cx);
5465 });
5466 cx.assert_editor_state(
5467 &"
5468 🏀{{{)ˇ}}}
5469 ε{{{)ˇ}}}
5470 ❤️{{{)ˇ}}}
5471 "
5472 .unindent(),
5473 );
5474
5475 // skip over the auto-closed brackets when typing a closing bracket
5476 cx.update_editor(|view, cx| {
5477 view.move_right(&MoveRight, cx);
5478 view.handle_input("}", cx);
5479 view.handle_input("}", cx);
5480 view.handle_input("}", cx);
5481 });
5482 cx.assert_editor_state(
5483 &"
5484 🏀{{{)}}}}ˇ
5485 ε{{{)}}}}ˇ
5486 ❤️{{{)}}}}ˇ
5487 "
5488 .unindent(),
5489 );
5490
5491 // autoclose multi-character pairs
5492 cx.set_state(
5493 &"
5494 ˇ
5495 ˇ
5496 "
5497 .unindent(),
5498 );
5499 cx.update_editor(|view, cx| {
5500 view.handle_input("/", cx);
5501 view.handle_input("*", cx);
5502 });
5503 cx.assert_editor_state(
5504 &"
5505 /*ˇ */
5506 /*ˇ */
5507 "
5508 .unindent(),
5509 );
5510
5511 // one cursor autocloses a multi-character pair, one cursor
5512 // does not autoclose.
5513 cx.set_state(
5514 &"
5515 /ˇ
5516 ˇ
5517 "
5518 .unindent(),
5519 );
5520 cx.update_editor(|view, cx| view.handle_input("*", cx));
5521 cx.assert_editor_state(
5522 &"
5523 /*ˇ */
5524 *ˇ
5525 "
5526 .unindent(),
5527 );
5528
5529 // Don't autoclose if the next character isn't whitespace and isn't
5530 // listed in the language's "autoclose_before" section.
5531 cx.set_state("ˇa b");
5532 cx.update_editor(|view, cx| view.handle_input("{", cx));
5533 cx.assert_editor_state("{ˇa b");
5534
5535 // Don't autoclose if `close` is false for the bracket pair
5536 cx.set_state("ˇ");
5537 cx.update_editor(|view, cx| view.handle_input("[", cx));
5538 cx.assert_editor_state("[ˇ");
5539
5540 // Surround with brackets if text is selected
5541 cx.set_state("«aˇ» b");
5542 cx.update_editor(|view, cx| view.handle_input("{", cx));
5543 cx.assert_editor_state("{«aˇ»} b");
5544
5545 // Autclose pair where the start and end characters are the same
5546 cx.set_state("aˇ");
5547 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5548 cx.assert_editor_state("a\"ˇ\"");
5549 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5550 cx.assert_editor_state("a\"\"ˇ");
5551
5552 // Don't autoclose pair if autoclose is disabled
5553 cx.set_state("ˇ");
5554 cx.update_editor(|view, cx| view.handle_input("<", cx));
5555 cx.assert_editor_state("<ˇ");
5556
5557 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5558 cx.set_state("«aˇ» b");
5559 cx.update_editor(|view, cx| view.handle_input("<", cx));
5560 cx.assert_editor_state("<«aˇ»> b");
5561}
5562
5563#[gpui::test]
5564async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5565 init_test(cx, |settings| {
5566 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5567 });
5568
5569 let mut cx = EditorTestContext::new(cx).await;
5570
5571 let language = Arc::new(Language::new(
5572 LanguageConfig {
5573 brackets: BracketPairConfig {
5574 pairs: vec![
5575 BracketPair {
5576 start: "{".to_string(),
5577 end: "}".to_string(),
5578 close: true,
5579 surround: true,
5580 newline: true,
5581 },
5582 BracketPair {
5583 start: "(".to_string(),
5584 end: ")".to_string(),
5585 close: true,
5586 surround: true,
5587 newline: true,
5588 },
5589 BracketPair {
5590 start: "[".to_string(),
5591 end: "]".to_string(),
5592 close: false,
5593 surround: false,
5594 newline: true,
5595 },
5596 ],
5597 ..Default::default()
5598 },
5599 autoclose_before: "})]".to_string(),
5600 ..Default::default()
5601 },
5602 Some(tree_sitter_rust::LANGUAGE.into()),
5603 ));
5604
5605 cx.language_registry().add(language.clone());
5606 cx.update_buffer(|buffer, cx| {
5607 buffer.set_language(Some(language), cx);
5608 });
5609
5610 cx.set_state(
5611 &"
5612 ˇ
5613 ˇ
5614 ˇ
5615 "
5616 .unindent(),
5617 );
5618
5619 // ensure only matching closing brackets are skipped over
5620 cx.update_editor(|view, cx| {
5621 view.handle_input("}", cx);
5622 view.move_left(&MoveLeft, cx);
5623 view.handle_input(")", cx);
5624 view.move_left(&MoveLeft, cx);
5625 });
5626 cx.assert_editor_state(
5627 &"
5628 ˇ)}
5629 ˇ)}
5630 ˇ)}
5631 "
5632 .unindent(),
5633 );
5634
5635 // skip-over closing brackets at multiple cursors
5636 cx.update_editor(|view, cx| {
5637 view.handle_input(")", cx);
5638 view.handle_input("}", cx);
5639 });
5640 cx.assert_editor_state(
5641 &"
5642 )}ˇ
5643 )}ˇ
5644 )}ˇ
5645 "
5646 .unindent(),
5647 );
5648
5649 // ignore non-close brackets
5650 cx.update_editor(|view, cx| {
5651 view.handle_input("]", cx);
5652 view.move_left(&MoveLeft, cx);
5653 view.handle_input("]", cx);
5654 });
5655 cx.assert_editor_state(
5656 &"
5657 )}]ˇ]
5658 )}]ˇ]
5659 )}]ˇ]
5660 "
5661 .unindent(),
5662 );
5663}
5664
5665#[gpui::test]
5666async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5667 init_test(cx, |_| {});
5668
5669 let mut cx = EditorTestContext::new(cx).await;
5670
5671 let html_language = Arc::new(
5672 Language::new(
5673 LanguageConfig {
5674 name: "HTML".into(),
5675 brackets: BracketPairConfig {
5676 pairs: vec![
5677 BracketPair {
5678 start: "<".into(),
5679 end: ">".into(),
5680 close: true,
5681 ..Default::default()
5682 },
5683 BracketPair {
5684 start: "{".into(),
5685 end: "}".into(),
5686 close: true,
5687 ..Default::default()
5688 },
5689 BracketPair {
5690 start: "(".into(),
5691 end: ")".into(),
5692 close: true,
5693 ..Default::default()
5694 },
5695 ],
5696 ..Default::default()
5697 },
5698 autoclose_before: "})]>".into(),
5699 ..Default::default()
5700 },
5701 Some(tree_sitter_html::language()),
5702 )
5703 .with_injection_query(
5704 r#"
5705 (script_element
5706 (raw_text) @content
5707 (#set! "language" "javascript"))
5708 "#,
5709 )
5710 .unwrap(),
5711 );
5712
5713 let javascript_language = Arc::new(Language::new(
5714 LanguageConfig {
5715 name: "JavaScript".into(),
5716 brackets: BracketPairConfig {
5717 pairs: vec![
5718 BracketPair {
5719 start: "/*".into(),
5720 end: " */".into(),
5721 close: true,
5722 ..Default::default()
5723 },
5724 BracketPair {
5725 start: "{".into(),
5726 end: "}".into(),
5727 close: true,
5728 ..Default::default()
5729 },
5730 BracketPair {
5731 start: "(".into(),
5732 end: ")".into(),
5733 close: true,
5734 ..Default::default()
5735 },
5736 ],
5737 ..Default::default()
5738 },
5739 autoclose_before: "})]>".into(),
5740 ..Default::default()
5741 },
5742 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5743 ));
5744
5745 cx.language_registry().add(html_language.clone());
5746 cx.language_registry().add(javascript_language.clone());
5747
5748 cx.update_buffer(|buffer, cx| {
5749 buffer.set_language(Some(html_language), cx);
5750 });
5751
5752 cx.set_state(
5753 &r#"
5754 <body>ˇ
5755 <script>
5756 var x = 1;ˇ
5757 </script>
5758 </body>ˇ
5759 "#
5760 .unindent(),
5761 );
5762
5763 // Precondition: different languages are active at different locations.
5764 cx.update_editor(|editor, cx| {
5765 let snapshot = editor.snapshot(cx);
5766 let cursors = editor.selections.ranges::<usize>(cx);
5767 let languages = cursors
5768 .iter()
5769 .map(|c| snapshot.language_at(c.start).unwrap().name())
5770 .collect::<Vec<_>>();
5771 assert_eq!(
5772 languages,
5773 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5774 );
5775 });
5776
5777 // Angle brackets autoclose in HTML, but not JavaScript.
5778 cx.update_editor(|editor, cx| {
5779 editor.handle_input("<", cx);
5780 editor.handle_input("a", cx);
5781 });
5782 cx.assert_editor_state(
5783 &r#"
5784 <body><aˇ>
5785 <script>
5786 var x = 1;<aˇ
5787 </script>
5788 </body><aˇ>
5789 "#
5790 .unindent(),
5791 );
5792
5793 // Curly braces and parens autoclose in both HTML and JavaScript.
5794 cx.update_editor(|editor, cx| {
5795 editor.handle_input(" b=", cx);
5796 editor.handle_input("{", cx);
5797 editor.handle_input("c", cx);
5798 editor.handle_input("(", cx);
5799 });
5800 cx.assert_editor_state(
5801 &r#"
5802 <body><a b={c(ˇ)}>
5803 <script>
5804 var x = 1;<a b={c(ˇ)}
5805 </script>
5806 </body><a b={c(ˇ)}>
5807 "#
5808 .unindent(),
5809 );
5810
5811 // Brackets that were already autoclosed are skipped.
5812 cx.update_editor(|editor, cx| {
5813 editor.handle_input(")", cx);
5814 editor.handle_input("d", cx);
5815 editor.handle_input("}", cx);
5816 });
5817 cx.assert_editor_state(
5818 &r#"
5819 <body><a b={c()d}ˇ>
5820 <script>
5821 var x = 1;<a b={c()d}ˇ
5822 </script>
5823 </body><a b={c()d}ˇ>
5824 "#
5825 .unindent(),
5826 );
5827 cx.update_editor(|editor, cx| {
5828 editor.handle_input(">", cx);
5829 });
5830 cx.assert_editor_state(
5831 &r#"
5832 <body><a b={c()d}>ˇ
5833 <script>
5834 var x = 1;<a b={c()d}>ˇ
5835 </script>
5836 </body><a b={c()d}>ˇ
5837 "#
5838 .unindent(),
5839 );
5840
5841 // Reset
5842 cx.set_state(
5843 &r#"
5844 <body>ˇ
5845 <script>
5846 var x = 1;ˇ
5847 </script>
5848 </body>ˇ
5849 "#
5850 .unindent(),
5851 );
5852
5853 cx.update_editor(|editor, cx| {
5854 editor.handle_input("<", cx);
5855 });
5856 cx.assert_editor_state(
5857 &r#"
5858 <body><ˇ>
5859 <script>
5860 var x = 1;<ˇ
5861 </script>
5862 </body><ˇ>
5863 "#
5864 .unindent(),
5865 );
5866
5867 // When backspacing, the closing angle brackets are removed.
5868 cx.update_editor(|editor, cx| {
5869 editor.backspace(&Backspace, cx);
5870 });
5871 cx.assert_editor_state(
5872 &r#"
5873 <body>ˇ
5874 <script>
5875 var x = 1;ˇ
5876 </script>
5877 </body>ˇ
5878 "#
5879 .unindent(),
5880 );
5881
5882 // Block comments autoclose in JavaScript, but not HTML.
5883 cx.update_editor(|editor, cx| {
5884 editor.handle_input("/", cx);
5885 editor.handle_input("*", cx);
5886 });
5887 cx.assert_editor_state(
5888 &r#"
5889 <body>/*ˇ
5890 <script>
5891 var x = 1;/*ˇ */
5892 </script>
5893 </body>/*ˇ
5894 "#
5895 .unindent(),
5896 );
5897}
5898
5899#[gpui::test]
5900async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5901 init_test(cx, |_| {});
5902
5903 let mut cx = EditorTestContext::new(cx).await;
5904
5905 let rust_language = Arc::new(
5906 Language::new(
5907 LanguageConfig {
5908 name: "Rust".into(),
5909 brackets: serde_json::from_value(json!([
5910 { "start": "{", "end": "}", "close": true, "newline": true },
5911 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5912 ]))
5913 .unwrap(),
5914 autoclose_before: "})]>".into(),
5915 ..Default::default()
5916 },
5917 Some(tree_sitter_rust::LANGUAGE.into()),
5918 )
5919 .with_override_query("(string_literal) @string")
5920 .unwrap(),
5921 );
5922
5923 cx.language_registry().add(rust_language.clone());
5924 cx.update_buffer(|buffer, cx| {
5925 buffer.set_language(Some(rust_language), cx);
5926 });
5927
5928 cx.set_state(
5929 &r#"
5930 let x = ˇ
5931 "#
5932 .unindent(),
5933 );
5934
5935 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5936 cx.update_editor(|editor, cx| {
5937 editor.handle_input("\"", cx);
5938 });
5939 cx.assert_editor_state(
5940 &r#"
5941 let x = "ˇ"
5942 "#
5943 .unindent(),
5944 );
5945
5946 // Inserting another quotation mark. The cursor moves across the existing
5947 // automatically-inserted quotation mark.
5948 cx.update_editor(|editor, cx| {
5949 editor.handle_input("\"", cx);
5950 });
5951 cx.assert_editor_state(
5952 &r#"
5953 let x = ""ˇ
5954 "#
5955 .unindent(),
5956 );
5957
5958 // Reset
5959 cx.set_state(
5960 &r#"
5961 let x = ˇ
5962 "#
5963 .unindent(),
5964 );
5965
5966 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5967 cx.update_editor(|editor, cx| {
5968 editor.handle_input("\"", cx);
5969 editor.handle_input(" ", cx);
5970 editor.move_left(&Default::default(), cx);
5971 editor.handle_input("\\", cx);
5972 editor.handle_input("\"", cx);
5973 });
5974 cx.assert_editor_state(
5975 &r#"
5976 let x = "\"ˇ "
5977 "#
5978 .unindent(),
5979 );
5980
5981 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5982 // mark. Nothing is inserted.
5983 cx.update_editor(|editor, cx| {
5984 editor.move_right(&Default::default(), cx);
5985 editor.handle_input("\"", cx);
5986 });
5987 cx.assert_editor_state(
5988 &r#"
5989 let x = "\" "ˇ
5990 "#
5991 .unindent(),
5992 );
5993}
5994
5995#[gpui::test]
5996async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5997 init_test(cx, |_| {});
5998
5999 let language = Arc::new(Language::new(
6000 LanguageConfig {
6001 brackets: BracketPairConfig {
6002 pairs: vec![
6003 BracketPair {
6004 start: "{".to_string(),
6005 end: "}".to_string(),
6006 close: true,
6007 surround: true,
6008 newline: true,
6009 },
6010 BracketPair {
6011 start: "/* ".to_string(),
6012 end: "*/".to_string(),
6013 close: true,
6014 surround: true,
6015 ..Default::default()
6016 },
6017 ],
6018 ..Default::default()
6019 },
6020 ..Default::default()
6021 },
6022 Some(tree_sitter_rust::LANGUAGE.into()),
6023 ));
6024
6025 let text = r#"
6026 a
6027 b
6028 c
6029 "#
6030 .unindent();
6031
6032 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6033 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6034 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6035 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6036 .await;
6037
6038 view.update(cx, |view, cx| {
6039 view.change_selections(None, cx, |s| {
6040 s.select_display_ranges([
6041 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6042 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6043 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6044 ])
6045 });
6046
6047 view.handle_input("{", cx);
6048 view.handle_input("{", cx);
6049 view.handle_input("{", cx);
6050 assert_eq!(
6051 view.text(cx),
6052 "
6053 {{{a}}}
6054 {{{b}}}
6055 {{{c}}}
6056 "
6057 .unindent()
6058 );
6059 assert_eq!(
6060 view.selections.display_ranges(cx),
6061 [
6062 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6063 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6064 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6065 ]
6066 );
6067
6068 view.undo(&Undo, cx);
6069 view.undo(&Undo, cx);
6070 view.undo(&Undo, cx);
6071 assert_eq!(
6072 view.text(cx),
6073 "
6074 a
6075 b
6076 c
6077 "
6078 .unindent()
6079 );
6080 assert_eq!(
6081 view.selections.display_ranges(cx),
6082 [
6083 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6084 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6085 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6086 ]
6087 );
6088
6089 // Ensure inserting the first character of a multi-byte bracket pair
6090 // doesn't surround the selections with the bracket.
6091 view.handle_input("/", cx);
6092 assert_eq!(
6093 view.text(cx),
6094 "
6095 /
6096 /
6097 /
6098 "
6099 .unindent()
6100 );
6101 assert_eq!(
6102 view.selections.display_ranges(cx),
6103 [
6104 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6105 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6106 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6107 ]
6108 );
6109
6110 view.undo(&Undo, cx);
6111 assert_eq!(
6112 view.text(cx),
6113 "
6114 a
6115 b
6116 c
6117 "
6118 .unindent()
6119 );
6120 assert_eq!(
6121 view.selections.display_ranges(cx),
6122 [
6123 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6125 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6126 ]
6127 );
6128
6129 // Ensure inserting the last character of a multi-byte bracket pair
6130 // doesn't surround the selections with the bracket.
6131 view.handle_input("*", cx);
6132 assert_eq!(
6133 view.text(cx),
6134 "
6135 *
6136 *
6137 *
6138 "
6139 .unindent()
6140 );
6141 assert_eq!(
6142 view.selections.display_ranges(cx),
6143 [
6144 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6145 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6146 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6147 ]
6148 );
6149 });
6150}
6151
6152#[gpui::test]
6153async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6154 init_test(cx, |_| {});
6155
6156 let language = Arc::new(Language::new(
6157 LanguageConfig {
6158 brackets: BracketPairConfig {
6159 pairs: vec![BracketPair {
6160 start: "{".to_string(),
6161 end: "}".to_string(),
6162 close: true,
6163 surround: true,
6164 newline: true,
6165 }],
6166 ..Default::default()
6167 },
6168 autoclose_before: "}".to_string(),
6169 ..Default::default()
6170 },
6171 Some(tree_sitter_rust::LANGUAGE.into()),
6172 ));
6173
6174 let text = r#"
6175 a
6176 b
6177 c
6178 "#
6179 .unindent();
6180
6181 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6182 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6183 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6184 editor
6185 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6186 .await;
6187
6188 editor.update(cx, |editor, cx| {
6189 editor.change_selections(None, cx, |s| {
6190 s.select_ranges([
6191 Point::new(0, 1)..Point::new(0, 1),
6192 Point::new(1, 1)..Point::new(1, 1),
6193 Point::new(2, 1)..Point::new(2, 1),
6194 ])
6195 });
6196
6197 editor.handle_input("{", cx);
6198 editor.handle_input("{", cx);
6199 editor.handle_input("_", cx);
6200 assert_eq!(
6201 editor.text(cx),
6202 "
6203 a{{_}}
6204 b{{_}}
6205 c{{_}}
6206 "
6207 .unindent()
6208 );
6209 assert_eq!(
6210 editor.selections.ranges::<Point>(cx),
6211 [
6212 Point::new(0, 4)..Point::new(0, 4),
6213 Point::new(1, 4)..Point::new(1, 4),
6214 Point::new(2, 4)..Point::new(2, 4)
6215 ]
6216 );
6217
6218 editor.backspace(&Default::default(), cx);
6219 editor.backspace(&Default::default(), cx);
6220 assert_eq!(
6221 editor.text(cx),
6222 "
6223 a{}
6224 b{}
6225 c{}
6226 "
6227 .unindent()
6228 );
6229 assert_eq!(
6230 editor.selections.ranges::<Point>(cx),
6231 [
6232 Point::new(0, 2)..Point::new(0, 2),
6233 Point::new(1, 2)..Point::new(1, 2),
6234 Point::new(2, 2)..Point::new(2, 2)
6235 ]
6236 );
6237
6238 editor.delete_to_previous_word_start(&Default::default(), cx);
6239 assert_eq!(
6240 editor.text(cx),
6241 "
6242 a
6243 b
6244 c
6245 "
6246 .unindent()
6247 );
6248 assert_eq!(
6249 editor.selections.ranges::<Point>(cx),
6250 [
6251 Point::new(0, 1)..Point::new(0, 1),
6252 Point::new(1, 1)..Point::new(1, 1),
6253 Point::new(2, 1)..Point::new(2, 1)
6254 ]
6255 );
6256 });
6257}
6258
6259#[gpui::test]
6260async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6261 init_test(cx, |settings| {
6262 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6263 });
6264
6265 let mut cx = EditorTestContext::new(cx).await;
6266
6267 let language = Arc::new(Language::new(
6268 LanguageConfig {
6269 brackets: BracketPairConfig {
6270 pairs: vec![
6271 BracketPair {
6272 start: "{".to_string(),
6273 end: "}".to_string(),
6274 close: true,
6275 surround: true,
6276 newline: true,
6277 },
6278 BracketPair {
6279 start: "(".to_string(),
6280 end: ")".to_string(),
6281 close: true,
6282 surround: true,
6283 newline: true,
6284 },
6285 BracketPair {
6286 start: "[".to_string(),
6287 end: "]".to_string(),
6288 close: false,
6289 surround: true,
6290 newline: true,
6291 },
6292 ],
6293 ..Default::default()
6294 },
6295 autoclose_before: "})]".to_string(),
6296 ..Default::default()
6297 },
6298 Some(tree_sitter_rust::LANGUAGE.into()),
6299 ));
6300
6301 cx.language_registry().add(language.clone());
6302 cx.update_buffer(|buffer, cx| {
6303 buffer.set_language(Some(language), cx);
6304 });
6305
6306 cx.set_state(
6307 &"
6308 {(ˇ)}
6309 [[ˇ]]
6310 {(ˇ)}
6311 "
6312 .unindent(),
6313 );
6314
6315 cx.update_editor(|view, cx| {
6316 view.backspace(&Default::default(), cx);
6317 view.backspace(&Default::default(), cx);
6318 });
6319
6320 cx.assert_editor_state(
6321 &"
6322 ˇ
6323 ˇ]]
6324 ˇ
6325 "
6326 .unindent(),
6327 );
6328
6329 cx.update_editor(|view, cx| {
6330 view.handle_input("{", cx);
6331 view.handle_input("{", cx);
6332 view.move_right(&MoveRight, cx);
6333 view.move_right(&MoveRight, cx);
6334 view.move_left(&MoveLeft, cx);
6335 view.move_left(&MoveLeft, cx);
6336 view.backspace(&Default::default(), cx);
6337 });
6338
6339 cx.assert_editor_state(
6340 &"
6341 {ˇ}
6342 {ˇ}]]
6343 {ˇ}
6344 "
6345 .unindent(),
6346 );
6347
6348 cx.update_editor(|view, cx| {
6349 view.backspace(&Default::default(), cx);
6350 });
6351
6352 cx.assert_editor_state(
6353 &"
6354 ˇ
6355 ˇ]]
6356 ˇ
6357 "
6358 .unindent(),
6359 );
6360}
6361
6362#[gpui::test]
6363async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6364 init_test(cx, |_| {});
6365
6366 let language = Arc::new(Language::new(
6367 LanguageConfig::default(),
6368 Some(tree_sitter_rust::LANGUAGE.into()),
6369 ));
6370
6371 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6372 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6373 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6374 editor
6375 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6376 .await;
6377
6378 editor.update(cx, |editor, cx| {
6379 editor.set_auto_replace_emoji_shortcode(true);
6380
6381 editor.handle_input("Hello ", cx);
6382 editor.handle_input(":wave", cx);
6383 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6384
6385 editor.handle_input(":", cx);
6386 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6387
6388 editor.handle_input(" :smile", cx);
6389 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6390
6391 editor.handle_input(":", cx);
6392 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6393
6394 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6395 editor.handle_input(":wave", cx);
6396 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6397
6398 editor.handle_input(":", cx);
6399 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6400
6401 editor.handle_input(":1", cx);
6402 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6403
6404 editor.handle_input(":", cx);
6405 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6406
6407 // Ensure shortcode does not get replaced when it is part of a word
6408 editor.handle_input(" Test:wave", cx);
6409 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6410
6411 editor.handle_input(":", cx);
6412 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6413
6414 editor.set_auto_replace_emoji_shortcode(false);
6415
6416 // Ensure shortcode does not get replaced when auto replace is off
6417 editor.handle_input(" :wave", cx);
6418 assert_eq!(
6419 editor.text(cx),
6420 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6421 );
6422
6423 editor.handle_input(":", cx);
6424 assert_eq!(
6425 editor.text(cx),
6426 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6427 );
6428 });
6429}
6430
6431#[gpui::test]
6432async fn test_snippets(cx: &mut gpui::TestAppContext) {
6433 init_test(cx, |_| {});
6434
6435 let (text, insertion_ranges) = marked_text_ranges(
6436 indoc! {"
6437 a.ˇ b
6438 a.ˇ b
6439 a.ˇ b
6440 "},
6441 false,
6442 );
6443
6444 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6445 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6446
6447 editor.update(cx, |editor, cx| {
6448 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6449
6450 editor
6451 .insert_snippet(&insertion_ranges, snippet, cx)
6452 .unwrap();
6453
6454 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6455 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6456 assert_eq!(editor.text(cx), expected_text);
6457 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6458 }
6459
6460 assert(
6461 editor,
6462 cx,
6463 indoc! {"
6464 a.f(«one», two, «three») b
6465 a.f(«one», two, «three») b
6466 a.f(«one», two, «three») b
6467 "},
6468 );
6469
6470 // Can't move earlier than the first tab stop
6471 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6472 assert(
6473 editor,
6474 cx,
6475 indoc! {"
6476 a.f(«one», two, «three») b
6477 a.f(«one», two, «three») b
6478 a.f(«one», two, «three») b
6479 "},
6480 );
6481
6482 assert!(editor.move_to_next_snippet_tabstop(cx));
6483 assert(
6484 editor,
6485 cx,
6486 indoc! {"
6487 a.f(one, «two», three) b
6488 a.f(one, «two», three) b
6489 a.f(one, «two», three) b
6490 "},
6491 );
6492
6493 editor.move_to_prev_snippet_tabstop(cx);
6494 assert(
6495 editor,
6496 cx,
6497 indoc! {"
6498 a.f(«one», two, «three») b
6499 a.f(«one», two, «three») b
6500 a.f(«one», two, «three») b
6501 "},
6502 );
6503
6504 assert!(editor.move_to_next_snippet_tabstop(cx));
6505 assert(
6506 editor,
6507 cx,
6508 indoc! {"
6509 a.f(one, «two», three) b
6510 a.f(one, «two», three) b
6511 a.f(one, «two», three) b
6512 "},
6513 );
6514 assert!(editor.move_to_next_snippet_tabstop(cx));
6515 assert(
6516 editor,
6517 cx,
6518 indoc! {"
6519 a.f(one, two, three)ˇ b
6520 a.f(one, two, three)ˇ b
6521 a.f(one, two, three)ˇ b
6522 "},
6523 );
6524
6525 // As soon as the last tab stop is reached, snippet state is gone
6526 editor.move_to_prev_snippet_tabstop(cx);
6527 assert(
6528 editor,
6529 cx,
6530 indoc! {"
6531 a.f(one, two, three)ˇ b
6532 a.f(one, two, three)ˇ b
6533 a.f(one, two, three)ˇ b
6534 "},
6535 );
6536 });
6537}
6538
6539#[gpui::test]
6540async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6541 init_test(cx, |_| {});
6542
6543 let fs = FakeFs::new(cx.executor());
6544 fs.insert_file("/file.rs", Default::default()).await;
6545
6546 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6547
6548 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6549 language_registry.add(rust_lang());
6550 let mut fake_servers = language_registry.register_fake_lsp(
6551 "Rust",
6552 FakeLspAdapter {
6553 capabilities: lsp::ServerCapabilities {
6554 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6555 ..Default::default()
6556 },
6557 ..Default::default()
6558 },
6559 );
6560
6561 let buffer = project
6562 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6563 .await
6564 .unwrap();
6565
6566 cx.executor().start_waiting();
6567 let fake_server = fake_servers.next().await.unwrap();
6568
6569 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6570 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6571 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6572 assert!(cx.read(|cx| editor.is_dirty(cx)));
6573
6574 let save = editor
6575 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6576 .unwrap();
6577 fake_server
6578 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6579 assert_eq!(
6580 params.text_document.uri,
6581 lsp::Url::from_file_path("/file.rs").unwrap()
6582 );
6583 assert_eq!(params.options.tab_size, 4);
6584 Ok(Some(vec![lsp::TextEdit::new(
6585 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6586 ", ".to_string(),
6587 )]))
6588 })
6589 .next()
6590 .await;
6591 cx.executor().start_waiting();
6592 save.await;
6593
6594 assert_eq!(
6595 editor.update(cx, |editor, cx| editor.text(cx)),
6596 "one, two\nthree\n"
6597 );
6598 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6599
6600 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6601 assert!(cx.read(|cx| editor.is_dirty(cx)));
6602
6603 // Ensure we can still save even if formatting hangs.
6604 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6605 assert_eq!(
6606 params.text_document.uri,
6607 lsp::Url::from_file_path("/file.rs").unwrap()
6608 );
6609 futures::future::pending::<()>().await;
6610 unreachable!()
6611 });
6612 let save = editor
6613 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6614 .unwrap();
6615 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6616 cx.executor().start_waiting();
6617 save.await;
6618 assert_eq!(
6619 editor.update(cx, |editor, cx| editor.text(cx)),
6620 "one\ntwo\nthree\n"
6621 );
6622 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6623
6624 // For non-dirty buffer, no formatting request should be sent
6625 let save = editor
6626 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6627 .unwrap();
6628 let _pending_format_request = fake_server
6629 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6630 panic!("Should not be invoked on non-dirty buffer");
6631 })
6632 .next();
6633 cx.executor().start_waiting();
6634 save.await;
6635
6636 // Set rust language override and assert overridden tabsize is sent to language server
6637 update_test_language_settings(cx, |settings| {
6638 settings.languages.insert(
6639 "Rust".into(),
6640 LanguageSettingsContent {
6641 tab_size: NonZeroU32::new(8),
6642 ..Default::default()
6643 },
6644 );
6645 });
6646
6647 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6648 assert!(cx.read(|cx| editor.is_dirty(cx)));
6649 let save = editor
6650 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6651 .unwrap();
6652 fake_server
6653 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6654 assert_eq!(
6655 params.text_document.uri,
6656 lsp::Url::from_file_path("/file.rs").unwrap()
6657 );
6658 assert_eq!(params.options.tab_size, 8);
6659 Ok(Some(vec![]))
6660 })
6661 .next()
6662 .await;
6663 cx.executor().start_waiting();
6664 save.await;
6665}
6666
6667#[gpui::test]
6668async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6669 init_test(cx, |_| {});
6670
6671 let cols = 4;
6672 let rows = 10;
6673 let sample_text_1 = sample_text(rows, cols, 'a');
6674 assert_eq!(
6675 sample_text_1,
6676 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6677 );
6678 let sample_text_2 = sample_text(rows, cols, 'l');
6679 assert_eq!(
6680 sample_text_2,
6681 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6682 );
6683 let sample_text_3 = sample_text(rows, cols, 'v');
6684 assert_eq!(
6685 sample_text_3,
6686 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6687 );
6688
6689 let fs = FakeFs::new(cx.executor());
6690 fs.insert_tree(
6691 "/a",
6692 json!({
6693 "main.rs": sample_text_1,
6694 "other.rs": sample_text_2,
6695 "lib.rs": sample_text_3,
6696 }),
6697 )
6698 .await;
6699
6700 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6701 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6702 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6703
6704 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6705 language_registry.add(rust_lang());
6706 let mut fake_servers = language_registry.register_fake_lsp(
6707 "Rust",
6708 FakeLspAdapter {
6709 capabilities: lsp::ServerCapabilities {
6710 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6711 ..Default::default()
6712 },
6713 ..Default::default()
6714 },
6715 );
6716
6717 let worktree = project.update(cx, |project, cx| {
6718 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6719 assert_eq!(worktrees.len(), 1);
6720 worktrees.pop().unwrap()
6721 });
6722 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6723
6724 let buffer_1 = project
6725 .update(cx, |project, cx| {
6726 project.open_buffer((worktree_id, "main.rs"), cx)
6727 })
6728 .await
6729 .unwrap();
6730 let buffer_2 = project
6731 .update(cx, |project, cx| {
6732 project.open_buffer((worktree_id, "other.rs"), cx)
6733 })
6734 .await
6735 .unwrap();
6736 let buffer_3 = project
6737 .update(cx, |project, cx| {
6738 project.open_buffer((worktree_id, "lib.rs"), cx)
6739 })
6740 .await
6741 .unwrap();
6742
6743 let multi_buffer = cx.new_model(|cx| {
6744 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6745 multi_buffer.push_excerpts(
6746 buffer_1.clone(),
6747 [
6748 ExcerptRange {
6749 context: Point::new(0, 0)..Point::new(3, 0),
6750 primary: None,
6751 },
6752 ExcerptRange {
6753 context: Point::new(5, 0)..Point::new(7, 0),
6754 primary: None,
6755 },
6756 ExcerptRange {
6757 context: Point::new(9, 0)..Point::new(10, 4),
6758 primary: None,
6759 },
6760 ],
6761 cx,
6762 );
6763 multi_buffer.push_excerpts(
6764 buffer_2.clone(),
6765 [
6766 ExcerptRange {
6767 context: Point::new(0, 0)..Point::new(3, 0),
6768 primary: None,
6769 },
6770 ExcerptRange {
6771 context: Point::new(5, 0)..Point::new(7, 0),
6772 primary: None,
6773 },
6774 ExcerptRange {
6775 context: Point::new(9, 0)..Point::new(10, 4),
6776 primary: None,
6777 },
6778 ],
6779 cx,
6780 );
6781 multi_buffer.push_excerpts(
6782 buffer_3.clone(),
6783 [
6784 ExcerptRange {
6785 context: Point::new(0, 0)..Point::new(3, 0),
6786 primary: None,
6787 },
6788 ExcerptRange {
6789 context: Point::new(5, 0)..Point::new(7, 0),
6790 primary: None,
6791 },
6792 ExcerptRange {
6793 context: Point::new(9, 0)..Point::new(10, 4),
6794 primary: None,
6795 },
6796 ],
6797 cx,
6798 );
6799 multi_buffer
6800 });
6801 let multi_buffer_editor = cx.new_view(|cx| {
6802 Editor::new(
6803 EditorMode::Full,
6804 multi_buffer,
6805 Some(project.clone()),
6806 true,
6807 cx,
6808 )
6809 });
6810
6811 multi_buffer_editor.update(cx, |editor, cx| {
6812 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6813 editor.insert("|one|two|three|", cx);
6814 });
6815 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6816 multi_buffer_editor.update(cx, |editor, cx| {
6817 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6818 s.select_ranges(Some(60..70))
6819 });
6820 editor.insert("|four|five|six|", cx);
6821 });
6822 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6823
6824 // First two buffers should be edited, but not the third one.
6825 assert_eq!(
6826 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6827 "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}",
6828 );
6829 buffer_1.update(cx, |buffer, _| {
6830 assert!(buffer.is_dirty());
6831 assert_eq!(
6832 buffer.text(),
6833 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6834 )
6835 });
6836 buffer_2.update(cx, |buffer, _| {
6837 assert!(buffer.is_dirty());
6838 assert_eq!(
6839 buffer.text(),
6840 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6841 )
6842 });
6843 buffer_3.update(cx, |buffer, _| {
6844 assert!(!buffer.is_dirty());
6845 assert_eq!(buffer.text(), sample_text_3,)
6846 });
6847
6848 cx.executor().start_waiting();
6849 let save = multi_buffer_editor
6850 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6851 .unwrap();
6852
6853 let fake_server = fake_servers.next().await.unwrap();
6854 fake_server
6855 .server
6856 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6857 Ok(Some(vec![lsp::TextEdit::new(
6858 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6859 format!("[{} formatted]", params.text_document.uri),
6860 )]))
6861 })
6862 .detach();
6863 save.await;
6864
6865 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6866 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6867 assert_eq!(
6868 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6869 "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}",
6870 );
6871 buffer_1.update(cx, |buffer, _| {
6872 assert!(!buffer.is_dirty());
6873 assert_eq!(
6874 buffer.text(),
6875 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6876 )
6877 });
6878 buffer_2.update(cx, |buffer, _| {
6879 assert!(!buffer.is_dirty());
6880 assert_eq!(
6881 buffer.text(),
6882 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6883 )
6884 });
6885 buffer_3.update(cx, |buffer, _| {
6886 assert!(!buffer.is_dirty());
6887 assert_eq!(buffer.text(), sample_text_3,)
6888 });
6889}
6890
6891#[gpui::test]
6892async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6893 init_test(cx, |_| {});
6894
6895 let fs = FakeFs::new(cx.executor());
6896 fs.insert_file("/file.rs", Default::default()).await;
6897
6898 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6899
6900 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6901 language_registry.add(rust_lang());
6902 let mut fake_servers = language_registry.register_fake_lsp(
6903 "Rust",
6904 FakeLspAdapter {
6905 capabilities: lsp::ServerCapabilities {
6906 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6907 ..Default::default()
6908 },
6909 ..Default::default()
6910 },
6911 );
6912
6913 let buffer = project
6914 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6915 .await
6916 .unwrap();
6917
6918 cx.executor().start_waiting();
6919 let fake_server = fake_servers.next().await.unwrap();
6920
6921 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6922 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6923 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6924 assert!(cx.read(|cx| editor.is_dirty(cx)));
6925
6926 let save = editor
6927 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6928 .unwrap();
6929 fake_server
6930 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6931 assert_eq!(
6932 params.text_document.uri,
6933 lsp::Url::from_file_path("/file.rs").unwrap()
6934 );
6935 assert_eq!(params.options.tab_size, 4);
6936 Ok(Some(vec![lsp::TextEdit::new(
6937 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6938 ", ".to_string(),
6939 )]))
6940 })
6941 .next()
6942 .await;
6943 cx.executor().start_waiting();
6944 save.await;
6945 assert_eq!(
6946 editor.update(cx, |editor, cx| editor.text(cx)),
6947 "one, two\nthree\n"
6948 );
6949 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6950
6951 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6952 assert!(cx.read(|cx| editor.is_dirty(cx)));
6953
6954 // Ensure we can still save even if formatting hangs.
6955 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6956 move |params, _| async move {
6957 assert_eq!(
6958 params.text_document.uri,
6959 lsp::Url::from_file_path("/file.rs").unwrap()
6960 );
6961 futures::future::pending::<()>().await;
6962 unreachable!()
6963 },
6964 );
6965 let save = editor
6966 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6967 .unwrap();
6968 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6969 cx.executor().start_waiting();
6970 save.await;
6971 assert_eq!(
6972 editor.update(cx, |editor, cx| editor.text(cx)),
6973 "one\ntwo\nthree\n"
6974 );
6975 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6976
6977 // For non-dirty buffer, no formatting request should be sent
6978 let save = editor
6979 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6980 .unwrap();
6981 let _pending_format_request = fake_server
6982 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6983 panic!("Should not be invoked on non-dirty buffer");
6984 })
6985 .next();
6986 cx.executor().start_waiting();
6987 save.await;
6988
6989 // Set Rust language override and assert overridden tabsize is sent to language server
6990 update_test_language_settings(cx, |settings| {
6991 settings.languages.insert(
6992 "Rust".into(),
6993 LanguageSettingsContent {
6994 tab_size: NonZeroU32::new(8),
6995 ..Default::default()
6996 },
6997 );
6998 });
6999
7000 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7001 assert!(cx.read(|cx| editor.is_dirty(cx)));
7002 let save = editor
7003 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7004 .unwrap();
7005 fake_server
7006 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7007 assert_eq!(
7008 params.text_document.uri,
7009 lsp::Url::from_file_path("/file.rs").unwrap()
7010 );
7011 assert_eq!(params.options.tab_size, 8);
7012 Ok(Some(vec![]))
7013 })
7014 .next()
7015 .await;
7016 cx.executor().start_waiting();
7017 save.await;
7018}
7019
7020#[gpui::test]
7021async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7022 init_test(cx, |settings| {
7023 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7024 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7025 ))
7026 });
7027
7028 let fs = FakeFs::new(cx.executor());
7029 fs.insert_file("/file.rs", Default::default()).await;
7030
7031 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7032
7033 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7034 language_registry.add(Arc::new(Language::new(
7035 LanguageConfig {
7036 name: "Rust".into(),
7037 matcher: LanguageMatcher {
7038 path_suffixes: vec!["rs".to_string()],
7039 ..Default::default()
7040 },
7041 ..LanguageConfig::default()
7042 },
7043 Some(tree_sitter_rust::LANGUAGE.into()),
7044 )));
7045 update_test_language_settings(cx, |settings| {
7046 // Enable Prettier formatting for the same buffer, and ensure
7047 // LSP is called instead of Prettier.
7048 settings.defaults.prettier = Some(PrettierSettings {
7049 allowed: true,
7050 ..PrettierSettings::default()
7051 });
7052 });
7053 let mut fake_servers = language_registry.register_fake_lsp(
7054 "Rust",
7055 FakeLspAdapter {
7056 capabilities: lsp::ServerCapabilities {
7057 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7058 ..Default::default()
7059 },
7060 ..Default::default()
7061 },
7062 );
7063
7064 let buffer = project
7065 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7066 .await
7067 .unwrap();
7068
7069 cx.executor().start_waiting();
7070 let fake_server = fake_servers.next().await.unwrap();
7071
7072 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7073 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7074 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7075
7076 let format = editor
7077 .update(cx, |editor, cx| {
7078 editor.perform_format(
7079 project.clone(),
7080 FormatTrigger::Manual,
7081 FormatTarget::Buffer,
7082 cx,
7083 )
7084 })
7085 .unwrap();
7086 fake_server
7087 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7088 assert_eq!(
7089 params.text_document.uri,
7090 lsp::Url::from_file_path("/file.rs").unwrap()
7091 );
7092 assert_eq!(params.options.tab_size, 4);
7093 Ok(Some(vec![lsp::TextEdit::new(
7094 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7095 ", ".to_string(),
7096 )]))
7097 })
7098 .next()
7099 .await;
7100 cx.executor().start_waiting();
7101 format.await;
7102 assert_eq!(
7103 editor.update(cx, |editor, cx| editor.text(cx)),
7104 "one, two\nthree\n"
7105 );
7106
7107 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7108 // Ensure we don't lock if formatting hangs.
7109 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7110 assert_eq!(
7111 params.text_document.uri,
7112 lsp::Url::from_file_path("/file.rs").unwrap()
7113 );
7114 futures::future::pending::<()>().await;
7115 unreachable!()
7116 });
7117 let format = editor
7118 .update(cx, |editor, cx| {
7119 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7120 })
7121 .unwrap();
7122 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7123 cx.executor().start_waiting();
7124 format.await;
7125 assert_eq!(
7126 editor.update(cx, |editor, cx| editor.text(cx)),
7127 "one\ntwo\nthree\n"
7128 );
7129}
7130
7131#[gpui::test]
7132async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7133 init_test(cx, |_| {});
7134
7135 let mut cx = EditorLspTestContext::new_rust(
7136 lsp::ServerCapabilities {
7137 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7138 ..Default::default()
7139 },
7140 cx,
7141 )
7142 .await;
7143
7144 cx.set_state(indoc! {"
7145 one.twoˇ
7146 "});
7147
7148 // The format request takes a long time. When it completes, it inserts
7149 // a newline and an indent before the `.`
7150 cx.lsp
7151 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7152 let executor = cx.background_executor().clone();
7153 async move {
7154 executor.timer(Duration::from_millis(100)).await;
7155 Ok(Some(vec![lsp::TextEdit {
7156 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7157 new_text: "\n ".into(),
7158 }]))
7159 }
7160 });
7161
7162 // Submit a format request.
7163 let format_1 = cx
7164 .update_editor(|editor, cx| editor.format(&Format, cx))
7165 .unwrap();
7166 cx.executor().run_until_parked();
7167
7168 // Submit a second format request.
7169 let format_2 = cx
7170 .update_editor(|editor, cx| editor.format(&Format, cx))
7171 .unwrap();
7172 cx.executor().run_until_parked();
7173
7174 // Wait for both format requests to complete
7175 cx.executor().advance_clock(Duration::from_millis(200));
7176 cx.executor().start_waiting();
7177 format_1.await.unwrap();
7178 cx.executor().start_waiting();
7179 format_2.await.unwrap();
7180
7181 // The formatting edits only happens once.
7182 cx.assert_editor_state(indoc! {"
7183 one
7184 .twoˇ
7185 "});
7186}
7187
7188#[gpui::test]
7189async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7190 init_test(cx, |settings| {
7191 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7192 });
7193
7194 let mut cx = EditorLspTestContext::new_rust(
7195 lsp::ServerCapabilities {
7196 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7197 ..Default::default()
7198 },
7199 cx,
7200 )
7201 .await;
7202
7203 // Set up a buffer white some trailing whitespace and no trailing newline.
7204 cx.set_state(
7205 &[
7206 "one ", //
7207 "twoˇ", //
7208 "three ", //
7209 "four", //
7210 ]
7211 .join("\n"),
7212 );
7213
7214 // Submit a format request.
7215 let format = cx
7216 .update_editor(|editor, cx| editor.format(&Format, cx))
7217 .unwrap();
7218
7219 // Record which buffer changes have been sent to the language server
7220 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7221 cx.lsp
7222 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7223 let buffer_changes = buffer_changes.clone();
7224 move |params, _| {
7225 buffer_changes.lock().extend(
7226 params
7227 .content_changes
7228 .into_iter()
7229 .map(|e| (e.range.unwrap(), e.text)),
7230 );
7231 }
7232 });
7233
7234 // Handle formatting requests to the language server.
7235 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7236 let buffer_changes = buffer_changes.clone();
7237 move |_, _| {
7238 // When formatting is requested, trailing whitespace has already been stripped,
7239 // and the trailing newline has already been added.
7240 assert_eq!(
7241 &buffer_changes.lock()[1..],
7242 &[
7243 (
7244 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7245 "".into()
7246 ),
7247 (
7248 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7249 "".into()
7250 ),
7251 (
7252 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7253 "\n".into()
7254 ),
7255 ]
7256 );
7257
7258 // Insert blank lines between each line of the buffer.
7259 async move {
7260 Ok(Some(vec![
7261 lsp::TextEdit {
7262 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7263 new_text: "\n".into(),
7264 },
7265 lsp::TextEdit {
7266 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7267 new_text: "\n".into(),
7268 },
7269 ]))
7270 }
7271 }
7272 });
7273
7274 // After formatting the buffer, the trailing whitespace is stripped,
7275 // a newline is appended, and the edits provided by the language server
7276 // have been applied.
7277 format.await.unwrap();
7278 cx.assert_editor_state(
7279 &[
7280 "one", //
7281 "", //
7282 "twoˇ", //
7283 "", //
7284 "three", //
7285 "four", //
7286 "", //
7287 ]
7288 .join("\n"),
7289 );
7290
7291 // Undoing the formatting undoes the trailing whitespace removal, the
7292 // trailing newline, and the LSP edits.
7293 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7294 cx.assert_editor_state(
7295 &[
7296 "one ", //
7297 "twoˇ", //
7298 "three ", //
7299 "four", //
7300 ]
7301 .join("\n"),
7302 );
7303}
7304
7305#[gpui::test]
7306async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7307 cx: &mut gpui::TestAppContext,
7308) {
7309 init_test(cx, |_| {});
7310
7311 cx.update(|cx| {
7312 cx.update_global::<SettingsStore, _>(|settings, cx| {
7313 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7314 settings.auto_signature_help = Some(true);
7315 });
7316 });
7317 });
7318
7319 let mut cx = EditorLspTestContext::new_rust(
7320 lsp::ServerCapabilities {
7321 signature_help_provider: Some(lsp::SignatureHelpOptions {
7322 ..Default::default()
7323 }),
7324 ..Default::default()
7325 },
7326 cx,
7327 )
7328 .await;
7329
7330 let language = Language::new(
7331 LanguageConfig {
7332 name: "Rust".into(),
7333 brackets: BracketPairConfig {
7334 pairs: vec![
7335 BracketPair {
7336 start: "{".to_string(),
7337 end: "}".to_string(),
7338 close: true,
7339 surround: true,
7340 newline: true,
7341 },
7342 BracketPair {
7343 start: "(".to_string(),
7344 end: ")".to_string(),
7345 close: true,
7346 surround: true,
7347 newline: true,
7348 },
7349 BracketPair {
7350 start: "/*".to_string(),
7351 end: " */".to_string(),
7352 close: true,
7353 surround: true,
7354 newline: true,
7355 },
7356 BracketPair {
7357 start: "[".to_string(),
7358 end: "]".to_string(),
7359 close: false,
7360 surround: false,
7361 newline: true,
7362 },
7363 BracketPair {
7364 start: "\"".to_string(),
7365 end: "\"".to_string(),
7366 close: true,
7367 surround: true,
7368 newline: false,
7369 },
7370 BracketPair {
7371 start: "<".to_string(),
7372 end: ">".to_string(),
7373 close: false,
7374 surround: true,
7375 newline: true,
7376 },
7377 ],
7378 ..Default::default()
7379 },
7380 autoclose_before: "})]".to_string(),
7381 ..Default::default()
7382 },
7383 Some(tree_sitter_rust::LANGUAGE.into()),
7384 );
7385 let language = Arc::new(language);
7386
7387 cx.language_registry().add(language.clone());
7388 cx.update_buffer(|buffer, cx| {
7389 buffer.set_language(Some(language), cx);
7390 });
7391
7392 cx.set_state(
7393 &r#"
7394 fn main() {
7395 sampleˇ
7396 }
7397 "#
7398 .unindent(),
7399 );
7400
7401 cx.update_editor(|view, cx| {
7402 view.handle_input("(", cx);
7403 });
7404 cx.assert_editor_state(
7405 &"
7406 fn main() {
7407 sample(ˇ)
7408 }
7409 "
7410 .unindent(),
7411 );
7412
7413 let mocked_response = lsp::SignatureHelp {
7414 signatures: vec![lsp::SignatureInformation {
7415 label: "fn sample(param1: u8, param2: u8)".to_string(),
7416 documentation: None,
7417 parameters: Some(vec![
7418 lsp::ParameterInformation {
7419 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7420 documentation: None,
7421 },
7422 lsp::ParameterInformation {
7423 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7424 documentation: None,
7425 },
7426 ]),
7427 active_parameter: None,
7428 }],
7429 active_signature: Some(0),
7430 active_parameter: Some(0),
7431 };
7432 handle_signature_help_request(&mut cx, mocked_response).await;
7433
7434 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7435 .await;
7436
7437 cx.editor(|editor, _| {
7438 let signature_help_state = editor.signature_help_state.popover().cloned();
7439 assert!(signature_help_state.is_some());
7440 let ParsedMarkdown {
7441 text, highlights, ..
7442 } = signature_help_state.unwrap().parsed_content;
7443 assert_eq!(text, "param1: u8, param2: u8");
7444 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7445 });
7446}
7447
7448#[gpui::test]
7449async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7450 init_test(cx, |_| {});
7451
7452 cx.update(|cx| {
7453 cx.update_global::<SettingsStore, _>(|settings, cx| {
7454 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7455 settings.auto_signature_help = Some(false);
7456 settings.show_signature_help_after_edits = Some(false);
7457 });
7458 });
7459 });
7460
7461 let mut cx = EditorLspTestContext::new_rust(
7462 lsp::ServerCapabilities {
7463 signature_help_provider: Some(lsp::SignatureHelpOptions {
7464 ..Default::default()
7465 }),
7466 ..Default::default()
7467 },
7468 cx,
7469 )
7470 .await;
7471
7472 let language = Language::new(
7473 LanguageConfig {
7474 name: "Rust".into(),
7475 brackets: BracketPairConfig {
7476 pairs: vec![
7477 BracketPair {
7478 start: "{".to_string(),
7479 end: "}".to_string(),
7480 close: true,
7481 surround: true,
7482 newline: true,
7483 },
7484 BracketPair {
7485 start: "(".to_string(),
7486 end: ")".to_string(),
7487 close: true,
7488 surround: true,
7489 newline: true,
7490 },
7491 BracketPair {
7492 start: "/*".to_string(),
7493 end: " */".to_string(),
7494 close: true,
7495 surround: true,
7496 newline: true,
7497 },
7498 BracketPair {
7499 start: "[".to_string(),
7500 end: "]".to_string(),
7501 close: false,
7502 surround: false,
7503 newline: true,
7504 },
7505 BracketPair {
7506 start: "\"".to_string(),
7507 end: "\"".to_string(),
7508 close: true,
7509 surround: true,
7510 newline: false,
7511 },
7512 BracketPair {
7513 start: "<".to_string(),
7514 end: ">".to_string(),
7515 close: false,
7516 surround: true,
7517 newline: true,
7518 },
7519 ],
7520 ..Default::default()
7521 },
7522 autoclose_before: "})]".to_string(),
7523 ..Default::default()
7524 },
7525 Some(tree_sitter_rust::LANGUAGE.into()),
7526 );
7527 let language = Arc::new(language);
7528
7529 cx.language_registry().add(language.clone());
7530 cx.update_buffer(|buffer, cx| {
7531 buffer.set_language(Some(language), cx);
7532 });
7533
7534 // Ensure that signature_help is not called when no signature help is enabled.
7535 cx.set_state(
7536 &r#"
7537 fn main() {
7538 sampleˇ
7539 }
7540 "#
7541 .unindent(),
7542 );
7543 cx.update_editor(|view, cx| {
7544 view.handle_input("(", cx);
7545 });
7546 cx.assert_editor_state(
7547 &"
7548 fn main() {
7549 sample(ˇ)
7550 }
7551 "
7552 .unindent(),
7553 );
7554 cx.editor(|editor, _| {
7555 assert!(editor.signature_help_state.task().is_none());
7556 });
7557
7558 let mocked_response = lsp::SignatureHelp {
7559 signatures: vec![lsp::SignatureInformation {
7560 label: "fn sample(param1: u8, param2: u8)".to_string(),
7561 documentation: None,
7562 parameters: Some(vec![
7563 lsp::ParameterInformation {
7564 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7565 documentation: None,
7566 },
7567 lsp::ParameterInformation {
7568 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7569 documentation: None,
7570 },
7571 ]),
7572 active_parameter: None,
7573 }],
7574 active_signature: Some(0),
7575 active_parameter: Some(0),
7576 };
7577
7578 // Ensure that signature_help is called when enabled afte edits
7579 cx.update(|cx| {
7580 cx.update_global::<SettingsStore, _>(|settings, cx| {
7581 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7582 settings.auto_signature_help = Some(false);
7583 settings.show_signature_help_after_edits = Some(true);
7584 });
7585 });
7586 });
7587 cx.set_state(
7588 &r#"
7589 fn main() {
7590 sampleˇ
7591 }
7592 "#
7593 .unindent(),
7594 );
7595 cx.update_editor(|view, cx| {
7596 view.handle_input("(", cx);
7597 });
7598 cx.assert_editor_state(
7599 &"
7600 fn main() {
7601 sample(ˇ)
7602 }
7603 "
7604 .unindent(),
7605 );
7606 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7607 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7608 .await;
7609 cx.update_editor(|editor, _| {
7610 let signature_help_state = editor.signature_help_state.popover().cloned();
7611 assert!(signature_help_state.is_some());
7612 let ParsedMarkdown {
7613 text, highlights, ..
7614 } = signature_help_state.unwrap().parsed_content;
7615 assert_eq!(text, "param1: u8, param2: u8");
7616 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7617 editor.signature_help_state = SignatureHelpState::default();
7618 });
7619
7620 // Ensure that signature_help is called when auto signature help override is enabled
7621 cx.update(|cx| {
7622 cx.update_global::<SettingsStore, _>(|settings, cx| {
7623 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7624 settings.auto_signature_help = Some(true);
7625 settings.show_signature_help_after_edits = Some(false);
7626 });
7627 });
7628 });
7629 cx.set_state(
7630 &r#"
7631 fn main() {
7632 sampleˇ
7633 }
7634 "#
7635 .unindent(),
7636 );
7637 cx.update_editor(|view, cx| {
7638 view.handle_input("(", cx);
7639 });
7640 cx.assert_editor_state(
7641 &"
7642 fn main() {
7643 sample(ˇ)
7644 }
7645 "
7646 .unindent(),
7647 );
7648 handle_signature_help_request(&mut cx, mocked_response).await;
7649 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7650 .await;
7651 cx.editor(|editor, _| {
7652 let signature_help_state = editor.signature_help_state.popover().cloned();
7653 assert!(signature_help_state.is_some());
7654 let ParsedMarkdown {
7655 text, highlights, ..
7656 } = signature_help_state.unwrap().parsed_content;
7657 assert_eq!(text, "param1: u8, param2: u8");
7658 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7659 });
7660}
7661
7662#[gpui::test]
7663async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7664 init_test(cx, |_| {});
7665 cx.update(|cx| {
7666 cx.update_global::<SettingsStore, _>(|settings, cx| {
7667 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7668 settings.auto_signature_help = Some(true);
7669 });
7670 });
7671 });
7672
7673 let mut cx = EditorLspTestContext::new_rust(
7674 lsp::ServerCapabilities {
7675 signature_help_provider: Some(lsp::SignatureHelpOptions {
7676 ..Default::default()
7677 }),
7678 ..Default::default()
7679 },
7680 cx,
7681 )
7682 .await;
7683
7684 // A test that directly calls `show_signature_help`
7685 cx.update_editor(|editor, cx| {
7686 editor.show_signature_help(&ShowSignatureHelp, cx);
7687 });
7688
7689 let mocked_response = lsp::SignatureHelp {
7690 signatures: vec![lsp::SignatureInformation {
7691 label: "fn sample(param1: u8, param2: u8)".to_string(),
7692 documentation: None,
7693 parameters: Some(vec![
7694 lsp::ParameterInformation {
7695 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7696 documentation: None,
7697 },
7698 lsp::ParameterInformation {
7699 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7700 documentation: None,
7701 },
7702 ]),
7703 active_parameter: None,
7704 }],
7705 active_signature: Some(0),
7706 active_parameter: Some(0),
7707 };
7708 handle_signature_help_request(&mut cx, mocked_response).await;
7709
7710 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7711 .await;
7712
7713 cx.editor(|editor, _| {
7714 let signature_help_state = editor.signature_help_state.popover().cloned();
7715 assert!(signature_help_state.is_some());
7716 let ParsedMarkdown {
7717 text, highlights, ..
7718 } = signature_help_state.unwrap().parsed_content;
7719 assert_eq!(text, "param1: u8, param2: u8");
7720 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7721 });
7722
7723 // When exiting outside from inside the brackets, `signature_help` is closed.
7724 cx.set_state(indoc! {"
7725 fn main() {
7726 sample(ˇ);
7727 }
7728
7729 fn sample(param1: u8, param2: u8) {}
7730 "});
7731
7732 cx.update_editor(|editor, cx| {
7733 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7734 });
7735
7736 let mocked_response = lsp::SignatureHelp {
7737 signatures: Vec::new(),
7738 active_signature: None,
7739 active_parameter: None,
7740 };
7741 handle_signature_help_request(&mut cx, mocked_response).await;
7742
7743 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7744 .await;
7745
7746 cx.editor(|editor, _| {
7747 assert!(!editor.signature_help_state.is_shown());
7748 });
7749
7750 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7751 cx.set_state(indoc! {"
7752 fn main() {
7753 sample(ˇ);
7754 }
7755
7756 fn sample(param1: u8, param2: u8) {}
7757 "});
7758
7759 let mocked_response = lsp::SignatureHelp {
7760 signatures: vec![lsp::SignatureInformation {
7761 label: "fn sample(param1: u8, param2: u8)".to_string(),
7762 documentation: None,
7763 parameters: Some(vec![
7764 lsp::ParameterInformation {
7765 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7766 documentation: None,
7767 },
7768 lsp::ParameterInformation {
7769 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7770 documentation: None,
7771 },
7772 ]),
7773 active_parameter: None,
7774 }],
7775 active_signature: Some(0),
7776 active_parameter: Some(0),
7777 };
7778 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7779 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7780 .await;
7781 cx.editor(|editor, _| {
7782 assert!(editor.signature_help_state.is_shown());
7783 });
7784
7785 // Restore the popover with more parameter input
7786 cx.set_state(indoc! {"
7787 fn main() {
7788 sample(param1, param2ˇ);
7789 }
7790
7791 fn sample(param1: u8, param2: u8) {}
7792 "});
7793
7794 let mocked_response = lsp::SignatureHelp {
7795 signatures: vec![lsp::SignatureInformation {
7796 label: "fn sample(param1: u8, param2: u8)".to_string(),
7797 documentation: None,
7798 parameters: Some(vec![
7799 lsp::ParameterInformation {
7800 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7801 documentation: None,
7802 },
7803 lsp::ParameterInformation {
7804 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7805 documentation: None,
7806 },
7807 ]),
7808 active_parameter: None,
7809 }],
7810 active_signature: Some(0),
7811 active_parameter: Some(1),
7812 };
7813 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7814 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7815 .await;
7816
7817 // When selecting a range, the popover is gone.
7818 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7819 cx.update_editor(|editor, cx| {
7820 editor.change_selections(None, cx, |s| {
7821 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7822 })
7823 });
7824 cx.assert_editor_state(indoc! {"
7825 fn main() {
7826 sample(param1, «ˇparam2»);
7827 }
7828
7829 fn sample(param1: u8, param2: u8) {}
7830 "});
7831 cx.editor(|editor, _| {
7832 assert!(!editor.signature_help_state.is_shown());
7833 });
7834
7835 // When unselecting again, the popover is back if within the brackets.
7836 cx.update_editor(|editor, cx| {
7837 editor.change_selections(None, cx, |s| {
7838 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7839 })
7840 });
7841 cx.assert_editor_state(indoc! {"
7842 fn main() {
7843 sample(param1, ˇparam2);
7844 }
7845
7846 fn sample(param1: u8, param2: u8) {}
7847 "});
7848 handle_signature_help_request(&mut cx, mocked_response).await;
7849 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7850 .await;
7851 cx.editor(|editor, _| {
7852 assert!(editor.signature_help_state.is_shown());
7853 });
7854
7855 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7856 cx.update_editor(|editor, cx| {
7857 editor.change_selections(None, cx, |s| {
7858 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7859 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7860 })
7861 });
7862 cx.assert_editor_state(indoc! {"
7863 fn main() {
7864 sample(param1, ˇparam2);
7865 }
7866
7867 fn sample(param1: u8, param2: u8) {}
7868 "});
7869
7870 let mocked_response = lsp::SignatureHelp {
7871 signatures: vec![lsp::SignatureInformation {
7872 label: "fn sample(param1: u8, param2: u8)".to_string(),
7873 documentation: None,
7874 parameters: Some(vec![
7875 lsp::ParameterInformation {
7876 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7877 documentation: None,
7878 },
7879 lsp::ParameterInformation {
7880 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7881 documentation: None,
7882 },
7883 ]),
7884 active_parameter: None,
7885 }],
7886 active_signature: Some(0),
7887 active_parameter: Some(1),
7888 };
7889 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7890 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7891 .await;
7892 cx.update_editor(|editor, cx| {
7893 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7894 });
7895 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7896 .await;
7897 cx.update_editor(|editor, cx| {
7898 editor.change_selections(None, cx, |s| {
7899 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7900 })
7901 });
7902 cx.assert_editor_state(indoc! {"
7903 fn main() {
7904 sample(param1, «ˇparam2»);
7905 }
7906
7907 fn sample(param1: u8, param2: u8) {}
7908 "});
7909 cx.update_editor(|editor, cx| {
7910 editor.change_selections(None, cx, |s| {
7911 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7912 })
7913 });
7914 cx.assert_editor_state(indoc! {"
7915 fn main() {
7916 sample(param1, ˇparam2);
7917 }
7918
7919 fn sample(param1: u8, param2: u8) {}
7920 "});
7921 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7922 .await;
7923}
7924
7925#[gpui::test]
7926async fn test_completion(cx: &mut gpui::TestAppContext) {
7927 init_test(cx, |_| {});
7928
7929 let mut cx = EditorLspTestContext::new_rust(
7930 lsp::ServerCapabilities {
7931 completion_provider: Some(lsp::CompletionOptions {
7932 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7933 resolve_provider: Some(true),
7934 ..Default::default()
7935 }),
7936 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7937 ..Default::default()
7938 },
7939 cx,
7940 )
7941 .await;
7942 let counter = Arc::new(AtomicUsize::new(0));
7943
7944 cx.set_state(indoc! {"
7945 oneˇ
7946 two
7947 three
7948 "});
7949 cx.simulate_keystroke(".");
7950 handle_completion_request(
7951 &mut cx,
7952 indoc! {"
7953 one.|<>
7954 two
7955 three
7956 "},
7957 vec!["first_completion", "second_completion"],
7958 counter.clone(),
7959 )
7960 .await;
7961 cx.condition(|editor, _| editor.context_menu_visible())
7962 .await;
7963 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7964
7965 let _handler = handle_signature_help_request(
7966 &mut cx,
7967 lsp::SignatureHelp {
7968 signatures: vec![lsp::SignatureInformation {
7969 label: "test signature".to_string(),
7970 documentation: None,
7971 parameters: Some(vec![lsp::ParameterInformation {
7972 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7973 documentation: None,
7974 }]),
7975 active_parameter: None,
7976 }],
7977 active_signature: None,
7978 active_parameter: None,
7979 },
7980 );
7981 cx.update_editor(|editor, cx| {
7982 assert!(
7983 !editor.signature_help_state.is_shown(),
7984 "No signature help was called for"
7985 );
7986 editor.show_signature_help(&ShowSignatureHelp, cx);
7987 });
7988 cx.run_until_parked();
7989 cx.update_editor(|editor, _| {
7990 assert!(
7991 !editor.signature_help_state.is_shown(),
7992 "No signature help should be shown when completions menu is open"
7993 );
7994 });
7995
7996 let apply_additional_edits = cx.update_editor(|editor, cx| {
7997 editor.context_menu_next(&Default::default(), cx);
7998 editor
7999 .confirm_completion(&ConfirmCompletion::default(), cx)
8000 .unwrap()
8001 });
8002 cx.assert_editor_state(indoc! {"
8003 one.second_completionˇ
8004 two
8005 three
8006 "});
8007
8008 handle_resolve_completion_request(
8009 &mut cx,
8010 Some(vec![
8011 (
8012 //This overlaps with the primary completion edit which is
8013 //misbehavior from the LSP spec, test that we filter it out
8014 indoc! {"
8015 one.second_ˇcompletion
8016 two
8017 threeˇ
8018 "},
8019 "overlapping additional edit",
8020 ),
8021 (
8022 indoc! {"
8023 one.second_completion
8024 two
8025 threeˇ
8026 "},
8027 "\nadditional edit",
8028 ),
8029 ]),
8030 )
8031 .await;
8032 apply_additional_edits.await.unwrap();
8033 cx.assert_editor_state(indoc! {"
8034 one.second_completionˇ
8035 two
8036 three
8037 additional edit
8038 "});
8039
8040 cx.set_state(indoc! {"
8041 one.second_completion
8042 twoˇ
8043 threeˇ
8044 additional edit
8045 "});
8046 cx.simulate_keystroke(" ");
8047 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8048 cx.simulate_keystroke("s");
8049 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8050
8051 cx.assert_editor_state(indoc! {"
8052 one.second_completion
8053 two sˇ
8054 three sˇ
8055 additional edit
8056 "});
8057 handle_completion_request(
8058 &mut cx,
8059 indoc! {"
8060 one.second_completion
8061 two s
8062 three <s|>
8063 additional edit
8064 "},
8065 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8066 counter.clone(),
8067 )
8068 .await;
8069 cx.condition(|editor, _| editor.context_menu_visible())
8070 .await;
8071 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8072
8073 cx.simulate_keystroke("i");
8074
8075 handle_completion_request(
8076 &mut cx,
8077 indoc! {"
8078 one.second_completion
8079 two si
8080 three <si|>
8081 additional edit
8082 "},
8083 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8084 counter.clone(),
8085 )
8086 .await;
8087 cx.condition(|editor, _| editor.context_menu_visible())
8088 .await;
8089 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8090
8091 let apply_additional_edits = cx.update_editor(|editor, cx| {
8092 editor
8093 .confirm_completion(&ConfirmCompletion::default(), cx)
8094 .unwrap()
8095 });
8096 cx.assert_editor_state(indoc! {"
8097 one.second_completion
8098 two sixth_completionˇ
8099 three sixth_completionˇ
8100 additional edit
8101 "});
8102
8103 handle_resolve_completion_request(&mut cx, None).await;
8104 apply_additional_edits.await.unwrap();
8105
8106 cx.update(|cx| {
8107 cx.update_global::<SettingsStore, _>(|settings, cx| {
8108 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8109 settings.show_completions_on_input = Some(false);
8110 });
8111 })
8112 });
8113 cx.set_state("editorˇ");
8114 cx.simulate_keystroke(".");
8115 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8116 cx.simulate_keystroke("c");
8117 cx.simulate_keystroke("l");
8118 cx.simulate_keystroke("o");
8119 cx.assert_editor_state("editor.cloˇ");
8120 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8121 cx.update_editor(|editor, cx| {
8122 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8123 });
8124 handle_completion_request(
8125 &mut cx,
8126 "editor.<clo|>",
8127 vec!["close", "clobber"],
8128 counter.clone(),
8129 )
8130 .await;
8131 cx.condition(|editor, _| editor.context_menu_visible())
8132 .await;
8133 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8134
8135 let apply_additional_edits = cx.update_editor(|editor, cx| {
8136 editor
8137 .confirm_completion(&ConfirmCompletion::default(), cx)
8138 .unwrap()
8139 });
8140 cx.assert_editor_state("editor.closeˇ");
8141 handle_resolve_completion_request(&mut cx, None).await;
8142 apply_additional_edits.await.unwrap();
8143}
8144
8145#[gpui::test]
8146async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8147 init_test(cx, |_| {});
8148 let mut cx = EditorLspTestContext::new_rust(
8149 lsp::ServerCapabilities {
8150 completion_provider: Some(lsp::CompletionOptions {
8151 trigger_characters: Some(vec![".".to_string()]),
8152 ..Default::default()
8153 }),
8154 ..Default::default()
8155 },
8156 cx,
8157 )
8158 .await;
8159 cx.lsp
8160 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8161 Ok(Some(lsp::CompletionResponse::Array(vec![
8162 lsp::CompletionItem {
8163 label: "first".into(),
8164 ..Default::default()
8165 },
8166 lsp::CompletionItem {
8167 label: "last".into(),
8168 ..Default::default()
8169 },
8170 ])))
8171 });
8172 cx.set_state("variableˇ");
8173 cx.simulate_keystroke(".");
8174 cx.executor().run_until_parked();
8175
8176 cx.update_editor(|editor, _| {
8177 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8178 assert_eq!(
8179 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8180 &["first", "last"]
8181 );
8182 } else {
8183 panic!("expected completion menu to be open");
8184 }
8185 });
8186
8187 cx.update_editor(|editor, cx| {
8188 editor.move_page_down(&MovePageDown::default(), cx);
8189 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8190 assert!(
8191 menu.selected_item == 1,
8192 "expected PageDown to select the last item from the context menu"
8193 );
8194 } else {
8195 panic!("expected completion menu to stay open after PageDown");
8196 }
8197 });
8198
8199 cx.update_editor(|editor, cx| {
8200 editor.move_page_up(&MovePageUp::default(), cx);
8201 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8202 assert!(
8203 menu.selected_item == 0,
8204 "expected PageUp to select the first item from the context menu"
8205 );
8206 } else {
8207 panic!("expected completion menu to stay open after PageUp");
8208 }
8209 });
8210}
8211
8212#[gpui::test]
8213async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8214 init_test(cx, |_| {});
8215
8216 let mut cx = EditorLspTestContext::new_rust(
8217 lsp::ServerCapabilities {
8218 completion_provider: Some(lsp::CompletionOptions {
8219 trigger_characters: Some(vec![".".to_string()]),
8220 resolve_provider: Some(true),
8221 ..Default::default()
8222 }),
8223 ..Default::default()
8224 },
8225 cx,
8226 )
8227 .await;
8228
8229 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8230 cx.simulate_keystroke(".");
8231 let completion_item = lsp::CompletionItem {
8232 label: "Some".into(),
8233 kind: Some(lsp::CompletionItemKind::SNIPPET),
8234 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8235 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8236 kind: lsp::MarkupKind::Markdown,
8237 value: "```rust\nSome(2)\n```".to_string(),
8238 })),
8239 deprecated: Some(false),
8240 sort_text: Some("Some".to_string()),
8241 filter_text: Some("Some".to_string()),
8242 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8243 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8244 range: lsp::Range {
8245 start: lsp::Position {
8246 line: 0,
8247 character: 22,
8248 },
8249 end: lsp::Position {
8250 line: 0,
8251 character: 22,
8252 },
8253 },
8254 new_text: "Some(2)".to_string(),
8255 })),
8256 additional_text_edits: Some(vec![lsp::TextEdit {
8257 range: lsp::Range {
8258 start: lsp::Position {
8259 line: 0,
8260 character: 20,
8261 },
8262 end: lsp::Position {
8263 line: 0,
8264 character: 22,
8265 },
8266 },
8267 new_text: "".to_string(),
8268 }]),
8269 ..Default::default()
8270 };
8271
8272 let closure_completion_item = completion_item.clone();
8273 let counter = Arc::new(AtomicUsize::new(0));
8274 let counter_clone = counter.clone();
8275 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8276 let task_completion_item = closure_completion_item.clone();
8277 counter_clone.fetch_add(1, atomic::Ordering::Release);
8278 async move {
8279 Ok(Some(lsp::CompletionResponse::Array(vec![
8280 task_completion_item,
8281 ])))
8282 }
8283 });
8284
8285 cx.condition(|editor, _| editor.context_menu_visible())
8286 .await;
8287 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8288 assert!(request.next().await.is_some());
8289 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8290
8291 cx.simulate_keystroke("S");
8292 cx.simulate_keystroke("o");
8293 cx.simulate_keystroke("m");
8294 cx.condition(|editor, _| editor.context_menu_visible())
8295 .await;
8296 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8297 assert!(request.next().await.is_some());
8298 assert!(request.next().await.is_some());
8299 assert!(request.next().await.is_some());
8300 request.close();
8301 assert!(request.next().await.is_none());
8302 assert_eq!(
8303 counter.load(atomic::Ordering::Acquire),
8304 4,
8305 "With the completions menu open, only one LSP request should happen per input"
8306 );
8307}
8308
8309#[gpui::test]
8310async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8311 init_test(cx, |_| {});
8312 let mut cx = EditorTestContext::new(cx).await;
8313 let language = Arc::new(Language::new(
8314 LanguageConfig {
8315 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8316 ..Default::default()
8317 },
8318 Some(tree_sitter_rust::LANGUAGE.into()),
8319 ));
8320 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8321
8322 // If multiple selections intersect a line, the line is only toggled once.
8323 cx.set_state(indoc! {"
8324 fn a() {
8325 «//b();
8326 ˇ»// «c();
8327 //ˇ» d();
8328 }
8329 "});
8330
8331 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8332
8333 cx.assert_editor_state(indoc! {"
8334 fn a() {
8335 «b();
8336 c();
8337 ˇ» d();
8338 }
8339 "});
8340
8341 // The comment prefix is inserted at the same column for every line in a
8342 // selection.
8343 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8344
8345 cx.assert_editor_state(indoc! {"
8346 fn a() {
8347 // «b();
8348 // c();
8349 ˇ»// d();
8350 }
8351 "});
8352
8353 // If a selection ends at the beginning of a line, that line is not toggled.
8354 cx.set_selections_state(indoc! {"
8355 fn a() {
8356 // b();
8357 «// c();
8358 ˇ» // d();
8359 }
8360 "});
8361
8362 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8363
8364 cx.assert_editor_state(indoc! {"
8365 fn a() {
8366 // b();
8367 «c();
8368 ˇ» // d();
8369 }
8370 "});
8371
8372 // If a selection span a single line and is empty, the line is toggled.
8373 cx.set_state(indoc! {"
8374 fn a() {
8375 a();
8376 b();
8377 ˇ
8378 }
8379 "});
8380
8381 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8382
8383 cx.assert_editor_state(indoc! {"
8384 fn a() {
8385 a();
8386 b();
8387 //•ˇ
8388 }
8389 "});
8390
8391 // If a selection span multiple lines, empty lines are not toggled.
8392 cx.set_state(indoc! {"
8393 fn a() {
8394 «a();
8395
8396 c();ˇ»
8397 }
8398 "});
8399
8400 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8401
8402 cx.assert_editor_state(indoc! {"
8403 fn a() {
8404 // «a();
8405
8406 // c();ˇ»
8407 }
8408 "});
8409
8410 // If a selection includes multiple comment prefixes, all lines are uncommented.
8411 cx.set_state(indoc! {"
8412 fn a() {
8413 «// a();
8414 /// b();
8415 //! c();ˇ»
8416 }
8417 "});
8418
8419 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8420
8421 cx.assert_editor_state(indoc! {"
8422 fn a() {
8423 «a();
8424 b();
8425 c();ˇ»
8426 }
8427 "});
8428}
8429
8430#[gpui::test]
8431async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8432 init_test(cx, |_| {});
8433
8434 let language = Arc::new(Language::new(
8435 LanguageConfig {
8436 line_comments: vec!["// ".into()],
8437 ..Default::default()
8438 },
8439 Some(tree_sitter_rust::LANGUAGE.into()),
8440 ));
8441
8442 let mut cx = EditorTestContext::new(cx).await;
8443
8444 cx.language_registry().add(language.clone());
8445 cx.update_buffer(|buffer, cx| {
8446 buffer.set_language(Some(language), cx);
8447 });
8448
8449 let toggle_comments = &ToggleComments {
8450 advance_downwards: true,
8451 };
8452
8453 // Single cursor on one line -> advance
8454 // Cursor moves horizontally 3 characters as well on non-blank line
8455 cx.set_state(indoc!(
8456 "fn a() {
8457 ˇdog();
8458 cat();
8459 }"
8460 ));
8461 cx.update_editor(|editor, cx| {
8462 editor.toggle_comments(toggle_comments, cx);
8463 });
8464 cx.assert_editor_state(indoc!(
8465 "fn a() {
8466 // dog();
8467 catˇ();
8468 }"
8469 ));
8470
8471 // Single selection on one line -> don't advance
8472 cx.set_state(indoc!(
8473 "fn a() {
8474 «dog()ˇ»;
8475 cat();
8476 }"
8477 ));
8478 cx.update_editor(|editor, cx| {
8479 editor.toggle_comments(toggle_comments, cx);
8480 });
8481 cx.assert_editor_state(indoc!(
8482 "fn a() {
8483 // «dog()ˇ»;
8484 cat();
8485 }"
8486 ));
8487
8488 // Multiple cursors on one line -> advance
8489 cx.set_state(indoc!(
8490 "fn a() {
8491 ˇdˇog();
8492 cat();
8493 }"
8494 ));
8495 cx.update_editor(|editor, cx| {
8496 editor.toggle_comments(toggle_comments, cx);
8497 });
8498 cx.assert_editor_state(indoc!(
8499 "fn a() {
8500 // dog();
8501 catˇ(ˇ);
8502 }"
8503 ));
8504
8505 // Multiple cursors on one line, with selection -> don't advance
8506 cx.set_state(indoc!(
8507 "fn a() {
8508 ˇdˇog«()ˇ»;
8509 cat();
8510 }"
8511 ));
8512 cx.update_editor(|editor, cx| {
8513 editor.toggle_comments(toggle_comments, cx);
8514 });
8515 cx.assert_editor_state(indoc!(
8516 "fn a() {
8517 // ˇdˇog«()ˇ»;
8518 cat();
8519 }"
8520 ));
8521
8522 // Single cursor on one line -> advance
8523 // Cursor moves to column 0 on blank line
8524 cx.set_state(indoc!(
8525 "fn a() {
8526 ˇdog();
8527
8528 cat();
8529 }"
8530 ));
8531 cx.update_editor(|editor, cx| {
8532 editor.toggle_comments(toggle_comments, cx);
8533 });
8534 cx.assert_editor_state(indoc!(
8535 "fn a() {
8536 // dog();
8537 ˇ
8538 cat();
8539 }"
8540 ));
8541
8542 // Single cursor on one line -> advance
8543 // Cursor starts and ends at column 0
8544 cx.set_state(indoc!(
8545 "fn a() {
8546 ˇ dog();
8547 cat();
8548 }"
8549 ));
8550 cx.update_editor(|editor, cx| {
8551 editor.toggle_comments(toggle_comments, cx);
8552 });
8553 cx.assert_editor_state(indoc!(
8554 "fn a() {
8555 // dog();
8556 ˇ cat();
8557 }"
8558 ));
8559}
8560
8561#[gpui::test]
8562async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8563 init_test(cx, |_| {});
8564
8565 let mut cx = EditorTestContext::new(cx).await;
8566
8567 let html_language = Arc::new(
8568 Language::new(
8569 LanguageConfig {
8570 name: "HTML".into(),
8571 block_comment: Some(("<!-- ".into(), " -->".into())),
8572 ..Default::default()
8573 },
8574 Some(tree_sitter_html::language()),
8575 )
8576 .with_injection_query(
8577 r#"
8578 (script_element
8579 (raw_text) @content
8580 (#set! "language" "javascript"))
8581 "#,
8582 )
8583 .unwrap(),
8584 );
8585
8586 let javascript_language = Arc::new(Language::new(
8587 LanguageConfig {
8588 name: "JavaScript".into(),
8589 line_comments: vec!["// ".into()],
8590 ..Default::default()
8591 },
8592 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8593 ));
8594
8595 cx.language_registry().add(html_language.clone());
8596 cx.language_registry().add(javascript_language.clone());
8597 cx.update_buffer(|buffer, cx| {
8598 buffer.set_language(Some(html_language), cx);
8599 });
8600
8601 // Toggle comments for empty selections
8602 cx.set_state(
8603 &r#"
8604 <p>A</p>ˇ
8605 <p>B</p>ˇ
8606 <p>C</p>ˇ
8607 "#
8608 .unindent(),
8609 );
8610 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8611 cx.assert_editor_state(
8612 &r#"
8613 <!-- <p>A</p>ˇ -->
8614 <!-- <p>B</p>ˇ -->
8615 <!-- <p>C</p>ˇ -->
8616 "#
8617 .unindent(),
8618 );
8619 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8620 cx.assert_editor_state(
8621 &r#"
8622 <p>A</p>ˇ
8623 <p>B</p>ˇ
8624 <p>C</p>ˇ
8625 "#
8626 .unindent(),
8627 );
8628
8629 // Toggle comments for mixture of empty and non-empty selections, where
8630 // multiple selections occupy a given line.
8631 cx.set_state(
8632 &r#"
8633 <p>A«</p>
8634 <p>ˇ»B</p>ˇ
8635 <p>C«</p>
8636 <p>ˇ»D</p>ˇ
8637 "#
8638 .unindent(),
8639 );
8640
8641 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8642 cx.assert_editor_state(
8643 &r#"
8644 <!-- <p>A«</p>
8645 <p>ˇ»B</p>ˇ -->
8646 <!-- <p>C«</p>
8647 <p>ˇ»D</p>ˇ -->
8648 "#
8649 .unindent(),
8650 );
8651 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8652 cx.assert_editor_state(
8653 &r#"
8654 <p>A«</p>
8655 <p>ˇ»B</p>ˇ
8656 <p>C«</p>
8657 <p>ˇ»D</p>ˇ
8658 "#
8659 .unindent(),
8660 );
8661
8662 // Toggle comments when different languages are active for different
8663 // selections.
8664 cx.set_state(
8665 &r#"
8666 ˇ<script>
8667 ˇvar x = new Y();
8668 ˇ</script>
8669 "#
8670 .unindent(),
8671 );
8672 cx.executor().run_until_parked();
8673 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8674 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8675 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8676 cx.assert_editor_state(
8677 &r#"
8678 <!-- ˇ<script> -->
8679 // ˇvar x = new Y();
8680 // ˇ</script>
8681 "#
8682 .unindent(),
8683 );
8684}
8685
8686#[gpui::test]
8687fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8688 init_test(cx, |_| {});
8689
8690 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8691 let multibuffer = cx.new_model(|cx| {
8692 let mut multibuffer = MultiBuffer::new(ReadWrite);
8693 multibuffer.push_excerpts(
8694 buffer.clone(),
8695 [
8696 ExcerptRange {
8697 context: Point::new(0, 0)..Point::new(0, 4),
8698 primary: None,
8699 },
8700 ExcerptRange {
8701 context: Point::new(1, 0)..Point::new(1, 4),
8702 primary: None,
8703 },
8704 ],
8705 cx,
8706 );
8707 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8708 multibuffer
8709 });
8710
8711 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8712 view.update(cx, |view, cx| {
8713 assert_eq!(view.text(cx), "aaaa\nbbbb");
8714 view.change_selections(None, cx, |s| {
8715 s.select_ranges([
8716 Point::new(0, 0)..Point::new(0, 0),
8717 Point::new(1, 0)..Point::new(1, 0),
8718 ])
8719 });
8720
8721 view.handle_input("X", cx);
8722 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8723 assert_eq!(
8724 view.selections.ranges(cx),
8725 [
8726 Point::new(0, 1)..Point::new(0, 1),
8727 Point::new(1, 1)..Point::new(1, 1),
8728 ]
8729 );
8730
8731 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8732 view.change_selections(None, cx, |s| {
8733 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8734 });
8735 view.backspace(&Default::default(), cx);
8736 assert_eq!(view.text(cx), "Xa\nbbb");
8737 assert_eq!(
8738 view.selections.ranges(cx),
8739 [Point::new(1, 0)..Point::new(1, 0)]
8740 );
8741
8742 view.change_selections(None, cx, |s| {
8743 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8744 });
8745 view.backspace(&Default::default(), cx);
8746 assert_eq!(view.text(cx), "X\nbb");
8747 assert_eq!(
8748 view.selections.ranges(cx),
8749 [Point::new(0, 1)..Point::new(0, 1)]
8750 );
8751 });
8752}
8753
8754#[gpui::test]
8755fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8756 init_test(cx, |_| {});
8757
8758 let markers = vec![('[', ']').into(), ('(', ')').into()];
8759 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8760 indoc! {"
8761 [aaaa
8762 (bbbb]
8763 cccc)",
8764 },
8765 markers.clone(),
8766 );
8767 let excerpt_ranges = markers.into_iter().map(|marker| {
8768 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8769 ExcerptRange {
8770 context,
8771 primary: None,
8772 }
8773 });
8774 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8775 let multibuffer = cx.new_model(|cx| {
8776 let mut multibuffer = MultiBuffer::new(ReadWrite);
8777 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8778 multibuffer
8779 });
8780
8781 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8782 view.update(cx, |view, cx| {
8783 let (expected_text, selection_ranges) = marked_text_ranges(
8784 indoc! {"
8785 aaaa
8786 bˇbbb
8787 bˇbbˇb
8788 cccc"
8789 },
8790 true,
8791 );
8792 assert_eq!(view.text(cx), expected_text);
8793 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8794
8795 view.handle_input("X", cx);
8796
8797 let (expected_text, expected_selections) = marked_text_ranges(
8798 indoc! {"
8799 aaaa
8800 bXˇbbXb
8801 bXˇbbXˇb
8802 cccc"
8803 },
8804 false,
8805 );
8806 assert_eq!(view.text(cx), expected_text);
8807 assert_eq!(view.selections.ranges(cx), expected_selections);
8808
8809 view.newline(&Newline, cx);
8810 let (expected_text, expected_selections) = marked_text_ranges(
8811 indoc! {"
8812 aaaa
8813 bX
8814 ˇbbX
8815 b
8816 bX
8817 ˇbbX
8818 ˇb
8819 cccc"
8820 },
8821 false,
8822 );
8823 assert_eq!(view.text(cx), expected_text);
8824 assert_eq!(view.selections.ranges(cx), expected_selections);
8825 });
8826}
8827
8828#[gpui::test]
8829fn test_refresh_selections(cx: &mut TestAppContext) {
8830 init_test(cx, |_| {});
8831
8832 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8833 let mut excerpt1_id = None;
8834 let multibuffer = cx.new_model(|cx| {
8835 let mut multibuffer = MultiBuffer::new(ReadWrite);
8836 excerpt1_id = multibuffer
8837 .push_excerpts(
8838 buffer.clone(),
8839 [
8840 ExcerptRange {
8841 context: Point::new(0, 0)..Point::new(1, 4),
8842 primary: None,
8843 },
8844 ExcerptRange {
8845 context: Point::new(1, 0)..Point::new(2, 4),
8846 primary: None,
8847 },
8848 ],
8849 cx,
8850 )
8851 .into_iter()
8852 .next();
8853 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8854 multibuffer
8855 });
8856
8857 let editor = cx.add_window(|cx| {
8858 let mut editor = build_editor(multibuffer.clone(), cx);
8859 let snapshot = editor.snapshot(cx);
8860 editor.change_selections(None, cx, |s| {
8861 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8862 });
8863 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8864 assert_eq!(
8865 editor.selections.ranges(cx),
8866 [
8867 Point::new(1, 3)..Point::new(1, 3),
8868 Point::new(2, 1)..Point::new(2, 1),
8869 ]
8870 );
8871 editor
8872 });
8873
8874 // Refreshing selections is a no-op when excerpts haven't changed.
8875 _ = editor.update(cx, |editor, cx| {
8876 editor.change_selections(None, cx, |s| s.refresh());
8877 assert_eq!(
8878 editor.selections.ranges(cx),
8879 [
8880 Point::new(1, 3)..Point::new(1, 3),
8881 Point::new(2, 1)..Point::new(2, 1),
8882 ]
8883 );
8884 });
8885
8886 multibuffer.update(cx, |multibuffer, cx| {
8887 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8888 });
8889 _ = editor.update(cx, |editor, cx| {
8890 // Removing an excerpt causes the first selection to become degenerate.
8891 assert_eq!(
8892 editor.selections.ranges(cx),
8893 [
8894 Point::new(0, 0)..Point::new(0, 0),
8895 Point::new(0, 1)..Point::new(0, 1)
8896 ]
8897 );
8898
8899 // Refreshing selections will relocate the first selection to the original buffer
8900 // location.
8901 editor.change_selections(None, cx, |s| s.refresh());
8902 assert_eq!(
8903 editor.selections.ranges(cx),
8904 [
8905 Point::new(0, 1)..Point::new(0, 1),
8906 Point::new(0, 3)..Point::new(0, 3)
8907 ]
8908 );
8909 assert!(editor.selections.pending_anchor().is_some());
8910 });
8911}
8912
8913#[gpui::test]
8914fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8915 init_test(cx, |_| {});
8916
8917 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8918 let mut excerpt1_id = None;
8919 let multibuffer = cx.new_model(|cx| {
8920 let mut multibuffer = MultiBuffer::new(ReadWrite);
8921 excerpt1_id = multibuffer
8922 .push_excerpts(
8923 buffer.clone(),
8924 [
8925 ExcerptRange {
8926 context: Point::new(0, 0)..Point::new(1, 4),
8927 primary: None,
8928 },
8929 ExcerptRange {
8930 context: Point::new(1, 0)..Point::new(2, 4),
8931 primary: None,
8932 },
8933 ],
8934 cx,
8935 )
8936 .into_iter()
8937 .next();
8938 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8939 multibuffer
8940 });
8941
8942 let editor = cx.add_window(|cx| {
8943 let mut editor = build_editor(multibuffer.clone(), cx);
8944 let snapshot = editor.snapshot(cx);
8945 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8946 assert_eq!(
8947 editor.selections.ranges(cx),
8948 [Point::new(1, 3)..Point::new(1, 3)]
8949 );
8950 editor
8951 });
8952
8953 multibuffer.update(cx, |multibuffer, cx| {
8954 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8955 });
8956 _ = editor.update(cx, |editor, cx| {
8957 assert_eq!(
8958 editor.selections.ranges(cx),
8959 [Point::new(0, 0)..Point::new(0, 0)]
8960 );
8961
8962 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8963 editor.change_selections(None, cx, |s| s.refresh());
8964 assert_eq!(
8965 editor.selections.ranges(cx),
8966 [Point::new(0, 3)..Point::new(0, 3)]
8967 );
8968 assert!(editor.selections.pending_anchor().is_some());
8969 });
8970}
8971
8972#[gpui::test]
8973async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8974 init_test(cx, |_| {});
8975
8976 let language = Arc::new(
8977 Language::new(
8978 LanguageConfig {
8979 brackets: BracketPairConfig {
8980 pairs: vec![
8981 BracketPair {
8982 start: "{".to_string(),
8983 end: "}".to_string(),
8984 close: true,
8985 surround: true,
8986 newline: true,
8987 },
8988 BracketPair {
8989 start: "/* ".to_string(),
8990 end: " */".to_string(),
8991 close: true,
8992 surround: true,
8993 newline: true,
8994 },
8995 ],
8996 ..Default::default()
8997 },
8998 ..Default::default()
8999 },
9000 Some(tree_sitter_rust::LANGUAGE.into()),
9001 )
9002 .with_indents_query("")
9003 .unwrap(),
9004 );
9005
9006 let text = concat!(
9007 "{ }\n", //
9008 " x\n", //
9009 " /* */\n", //
9010 "x\n", //
9011 "{{} }\n", //
9012 );
9013
9014 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9015 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9016 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9017 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9018 .await;
9019
9020 view.update(cx, |view, cx| {
9021 view.change_selections(None, cx, |s| {
9022 s.select_display_ranges([
9023 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9024 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9025 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9026 ])
9027 });
9028 view.newline(&Newline, cx);
9029
9030 assert_eq!(
9031 view.buffer().read(cx).read(cx).text(),
9032 concat!(
9033 "{ \n", // Suppress rustfmt
9034 "\n", //
9035 "}\n", //
9036 " x\n", //
9037 " /* \n", //
9038 " \n", //
9039 " */\n", //
9040 "x\n", //
9041 "{{} \n", //
9042 "}\n", //
9043 )
9044 );
9045 });
9046}
9047
9048#[gpui::test]
9049fn test_highlighted_ranges(cx: &mut TestAppContext) {
9050 init_test(cx, |_| {});
9051
9052 let editor = cx.add_window(|cx| {
9053 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9054 build_editor(buffer.clone(), cx)
9055 });
9056
9057 _ = editor.update(cx, |editor, cx| {
9058 struct Type1;
9059 struct Type2;
9060
9061 let buffer = editor.buffer.read(cx).snapshot(cx);
9062
9063 let anchor_range =
9064 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9065
9066 editor.highlight_background::<Type1>(
9067 &[
9068 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9069 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9070 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9071 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9072 ],
9073 |_| Hsla::red(),
9074 cx,
9075 );
9076 editor.highlight_background::<Type2>(
9077 &[
9078 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9079 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9080 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9081 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9082 ],
9083 |_| Hsla::green(),
9084 cx,
9085 );
9086
9087 let snapshot = editor.snapshot(cx);
9088 let mut highlighted_ranges = editor.background_highlights_in_range(
9089 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9090 &snapshot,
9091 cx.theme().colors(),
9092 );
9093 // Enforce a consistent ordering based on color without relying on the ordering of the
9094 // highlight's `TypeId` which is non-executor.
9095 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9096 assert_eq!(
9097 highlighted_ranges,
9098 &[
9099 (
9100 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9101 Hsla::red(),
9102 ),
9103 (
9104 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9105 Hsla::red(),
9106 ),
9107 (
9108 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9109 Hsla::green(),
9110 ),
9111 (
9112 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9113 Hsla::green(),
9114 ),
9115 ]
9116 );
9117 assert_eq!(
9118 editor.background_highlights_in_range(
9119 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9120 &snapshot,
9121 cx.theme().colors(),
9122 ),
9123 &[(
9124 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9125 Hsla::red(),
9126 )]
9127 );
9128 });
9129}
9130
9131#[gpui::test]
9132async fn test_following(cx: &mut gpui::TestAppContext) {
9133 init_test(cx, |_| {});
9134
9135 let fs = FakeFs::new(cx.executor());
9136 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9137
9138 let buffer = project.update(cx, |project, cx| {
9139 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9140 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9141 });
9142 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9143 let follower = cx.update(|cx| {
9144 cx.open_window(
9145 WindowOptions {
9146 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9147 gpui::Point::new(px(0.), px(0.)),
9148 gpui::Point::new(px(10.), px(80.)),
9149 ))),
9150 ..Default::default()
9151 },
9152 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9153 )
9154 .unwrap()
9155 });
9156
9157 let is_still_following = Rc::new(RefCell::new(true));
9158 let follower_edit_event_count = Rc::new(RefCell::new(0));
9159 let pending_update = Rc::new(RefCell::new(None));
9160 _ = follower.update(cx, {
9161 let update = pending_update.clone();
9162 let is_still_following = is_still_following.clone();
9163 let follower_edit_event_count = follower_edit_event_count.clone();
9164 |_, cx| {
9165 cx.subscribe(
9166 &leader.root_view(cx).unwrap(),
9167 move |_, leader, event, cx| {
9168 leader
9169 .read(cx)
9170 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9171 },
9172 )
9173 .detach();
9174
9175 cx.subscribe(
9176 &follower.root_view(cx).unwrap(),
9177 move |_, _, event: &EditorEvent, _cx| {
9178 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9179 *is_still_following.borrow_mut() = false;
9180 }
9181
9182 if let EditorEvent::BufferEdited = event {
9183 *follower_edit_event_count.borrow_mut() += 1;
9184 }
9185 },
9186 )
9187 .detach();
9188 }
9189 });
9190
9191 // Update the selections only
9192 _ = leader.update(cx, |leader, cx| {
9193 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9194 });
9195 follower
9196 .update(cx, |follower, cx| {
9197 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9198 })
9199 .unwrap()
9200 .await
9201 .unwrap();
9202 _ = follower.update(cx, |follower, cx| {
9203 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9204 });
9205 assert!(*is_still_following.borrow());
9206 assert_eq!(*follower_edit_event_count.borrow(), 0);
9207
9208 // Update the scroll position only
9209 _ = leader.update(cx, |leader, cx| {
9210 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9211 });
9212 follower
9213 .update(cx, |follower, cx| {
9214 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9215 })
9216 .unwrap()
9217 .await
9218 .unwrap();
9219 assert_eq!(
9220 follower
9221 .update(cx, |follower, cx| follower.scroll_position(cx))
9222 .unwrap(),
9223 gpui::Point::new(1.5, 3.5)
9224 );
9225 assert!(*is_still_following.borrow());
9226 assert_eq!(*follower_edit_event_count.borrow(), 0);
9227
9228 // Update the selections and scroll position. The follower's scroll position is updated
9229 // via autoscroll, not via the leader's exact scroll position.
9230 _ = leader.update(cx, |leader, cx| {
9231 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9232 leader.request_autoscroll(Autoscroll::newest(), cx);
9233 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9234 });
9235 follower
9236 .update(cx, |follower, cx| {
9237 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9238 })
9239 .unwrap()
9240 .await
9241 .unwrap();
9242 _ = follower.update(cx, |follower, cx| {
9243 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9244 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9245 });
9246 assert!(*is_still_following.borrow());
9247
9248 // Creating a pending selection that precedes another selection
9249 _ = leader.update(cx, |leader, cx| {
9250 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9251 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9252 });
9253 follower
9254 .update(cx, |follower, cx| {
9255 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9256 })
9257 .unwrap()
9258 .await
9259 .unwrap();
9260 _ = follower.update(cx, |follower, cx| {
9261 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9262 });
9263 assert!(*is_still_following.borrow());
9264
9265 // Extend the pending selection so that it surrounds another selection
9266 _ = leader.update(cx, |leader, cx| {
9267 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9268 });
9269 follower
9270 .update(cx, |follower, cx| {
9271 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9272 })
9273 .unwrap()
9274 .await
9275 .unwrap();
9276 _ = follower.update(cx, |follower, cx| {
9277 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9278 });
9279
9280 // Scrolling locally breaks the follow
9281 _ = follower.update(cx, |follower, cx| {
9282 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9283 follower.set_scroll_anchor(
9284 ScrollAnchor {
9285 anchor: top_anchor,
9286 offset: gpui::Point::new(0.0, 0.5),
9287 },
9288 cx,
9289 );
9290 });
9291 assert!(!(*is_still_following.borrow()));
9292}
9293
9294#[gpui::test]
9295async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9296 init_test(cx, |_| {});
9297
9298 let fs = FakeFs::new(cx.executor());
9299 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9300 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9301 let pane = workspace
9302 .update(cx, |workspace, _| workspace.active_pane().clone())
9303 .unwrap();
9304
9305 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9306
9307 let leader = pane.update(cx, |_, cx| {
9308 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9309 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9310 });
9311
9312 // Start following the editor when it has no excerpts.
9313 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9314 let follower_1 = cx
9315 .update_window(*workspace.deref(), |_, cx| {
9316 Editor::from_state_proto(
9317 workspace.root_view(cx).unwrap(),
9318 ViewId {
9319 creator: Default::default(),
9320 id: 0,
9321 },
9322 &mut state_message,
9323 cx,
9324 )
9325 })
9326 .unwrap()
9327 .unwrap()
9328 .await
9329 .unwrap();
9330
9331 let update_message = Rc::new(RefCell::new(None));
9332 follower_1.update(cx, {
9333 let update = update_message.clone();
9334 |_, cx| {
9335 cx.subscribe(&leader, move |_, leader, event, cx| {
9336 leader
9337 .read(cx)
9338 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9339 })
9340 .detach();
9341 }
9342 });
9343
9344 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9345 (
9346 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9347 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9348 )
9349 });
9350
9351 // Insert some excerpts.
9352 leader.update(cx, |leader, cx| {
9353 leader.buffer.update(cx, |multibuffer, cx| {
9354 let excerpt_ids = multibuffer.push_excerpts(
9355 buffer_1.clone(),
9356 [
9357 ExcerptRange {
9358 context: 1..6,
9359 primary: None,
9360 },
9361 ExcerptRange {
9362 context: 12..15,
9363 primary: None,
9364 },
9365 ExcerptRange {
9366 context: 0..3,
9367 primary: None,
9368 },
9369 ],
9370 cx,
9371 );
9372 multibuffer.insert_excerpts_after(
9373 excerpt_ids[0],
9374 buffer_2.clone(),
9375 [
9376 ExcerptRange {
9377 context: 8..12,
9378 primary: None,
9379 },
9380 ExcerptRange {
9381 context: 0..6,
9382 primary: None,
9383 },
9384 ],
9385 cx,
9386 );
9387 });
9388 });
9389
9390 // Apply the update of adding the excerpts.
9391 follower_1
9392 .update(cx, |follower, cx| {
9393 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9394 })
9395 .await
9396 .unwrap();
9397 assert_eq!(
9398 follower_1.update(cx, |editor, cx| editor.text(cx)),
9399 leader.update(cx, |editor, cx| editor.text(cx))
9400 );
9401 update_message.borrow_mut().take();
9402
9403 // Start following separately after it already has excerpts.
9404 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9405 let follower_2 = cx
9406 .update_window(*workspace.deref(), |_, cx| {
9407 Editor::from_state_proto(
9408 workspace.root_view(cx).unwrap().clone(),
9409 ViewId {
9410 creator: Default::default(),
9411 id: 0,
9412 },
9413 &mut state_message,
9414 cx,
9415 )
9416 })
9417 .unwrap()
9418 .unwrap()
9419 .await
9420 .unwrap();
9421 assert_eq!(
9422 follower_2.update(cx, |editor, cx| editor.text(cx)),
9423 leader.update(cx, |editor, cx| editor.text(cx))
9424 );
9425
9426 // Remove some excerpts.
9427 leader.update(cx, |leader, cx| {
9428 leader.buffer.update(cx, |multibuffer, cx| {
9429 let excerpt_ids = multibuffer.excerpt_ids();
9430 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9431 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9432 });
9433 });
9434
9435 // Apply the update of removing the excerpts.
9436 follower_1
9437 .update(cx, |follower, cx| {
9438 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9439 })
9440 .await
9441 .unwrap();
9442 follower_2
9443 .update(cx, |follower, cx| {
9444 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9445 })
9446 .await
9447 .unwrap();
9448 update_message.borrow_mut().take();
9449 assert_eq!(
9450 follower_1.update(cx, |editor, cx| editor.text(cx)),
9451 leader.update(cx, |editor, cx| editor.text(cx))
9452 );
9453}
9454
9455#[gpui::test]
9456async fn go_to_prev_overlapping_diagnostic(
9457 executor: BackgroundExecutor,
9458 cx: &mut gpui::TestAppContext,
9459) {
9460 init_test(cx, |_| {});
9461
9462 let mut cx = EditorTestContext::new(cx).await;
9463 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9464
9465 cx.set_state(indoc! {"
9466 ˇfn func(abc def: i32) -> u32 {
9467 }
9468 "});
9469
9470 cx.update(|cx| {
9471 project.update(cx, |project, cx| {
9472 project
9473 .update_diagnostics(
9474 LanguageServerId(0),
9475 lsp::PublishDiagnosticsParams {
9476 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9477 version: None,
9478 diagnostics: vec![
9479 lsp::Diagnostic {
9480 range: lsp::Range::new(
9481 lsp::Position::new(0, 11),
9482 lsp::Position::new(0, 12),
9483 ),
9484 severity: Some(lsp::DiagnosticSeverity::ERROR),
9485 ..Default::default()
9486 },
9487 lsp::Diagnostic {
9488 range: lsp::Range::new(
9489 lsp::Position::new(0, 12),
9490 lsp::Position::new(0, 15),
9491 ),
9492 severity: Some(lsp::DiagnosticSeverity::ERROR),
9493 ..Default::default()
9494 },
9495 lsp::Diagnostic {
9496 range: lsp::Range::new(
9497 lsp::Position::new(0, 25),
9498 lsp::Position::new(0, 28),
9499 ),
9500 severity: Some(lsp::DiagnosticSeverity::ERROR),
9501 ..Default::default()
9502 },
9503 ],
9504 },
9505 &[],
9506 cx,
9507 )
9508 .unwrap()
9509 });
9510 });
9511
9512 executor.run_until_parked();
9513
9514 cx.update_editor(|editor, cx| {
9515 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9516 });
9517
9518 cx.assert_editor_state(indoc! {"
9519 fn func(abc def: i32) -> ˇu32 {
9520 }
9521 "});
9522
9523 cx.update_editor(|editor, cx| {
9524 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9525 });
9526
9527 cx.assert_editor_state(indoc! {"
9528 fn func(abc ˇdef: i32) -> u32 {
9529 }
9530 "});
9531
9532 cx.update_editor(|editor, cx| {
9533 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9534 });
9535
9536 cx.assert_editor_state(indoc! {"
9537 fn func(abcˇ def: i32) -> u32 {
9538 }
9539 "});
9540
9541 cx.update_editor(|editor, cx| {
9542 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9543 });
9544
9545 cx.assert_editor_state(indoc! {"
9546 fn func(abc def: i32) -> ˇu32 {
9547 }
9548 "});
9549}
9550
9551#[gpui::test]
9552async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9553 init_test(cx, |_| {});
9554
9555 let mut cx = EditorTestContext::new(cx).await;
9556
9557 cx.set_state(indoc! {"
9558 fn func(abˇc def: i32) -> u32 {
9559 }
9560 "});
9561 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9562
9563 cx.update(|cx| {
9564 project.update(cx, |project, cx| {
9565 project.update_diagnostics(
9566 LanguageServerId(0),
9567 lsp::PublishDiagnosticsParams {
9568 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9569 version: None,
9570 diagnostics: vec![lsp::Diagnostic {
9571 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9572 severity: Some(lsp::DiagnosticSeverity::ERROR),
9573 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9574 ..Default::default()
9575 }],
9576 },
9577 &[],
9578 cx,
9579 )
9580 })
9581 }).unwrap();
9582 cx.run_until_parked();
9583 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9584 cx.run_until_parked();
9585 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9586}
9587
9588#[gpui::test]
9589async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9590 init_test(cx, |_| {});
9591
9592 let mut cx = EditorTestContext::new(cx).await;
9593
9594 let diff_base = r#"
9595 use some::mod;
9596
9597 const A: u32 = 42;
9598
9599 fn main() {
9600 println!("hello");
9601
9602 println!("world");
9603 }
9604 "#
9605 .unindent();
9606
9607 // Edits are modified, removed, modified, added
9608 cx.set_state(
9609 &r#"
9610 use some::modified;
9611
9612 ˇ
9613 fn main() {
9614 println!("hello there");
9615
9616 println!("around the");
9617 println!("world");
9618 }
9619 "#
9620 .unindent(),
9621 );
9622
9623 cx.set_diff_base(Some(&diff_base));
9624 executor.run_until_parked();
9625
9626 cx.update_editor(|editor, cx| {
9627 //Wrap around the bottom of the buffer
9628 for _ in 0..3 {
9629 editor.go_to_next_hunk(&GoToHunk, cx);
9630 }
9631 });
9632
9633 cx.assert_editor_state(
9634 &r#"
9635 ˇuse some::modified;
9636
9637
9638 fn main() {
9639 println!("hello there");
9640
9641 println!("around the");
9642 println!("world");
9643 }
9644 "#
9645 .unindent(),
9646 );
9647
9648 cx.update_editor(|editor, cx| {
9649 //Wrap around the top of the buffer
9650 for _ in 0..2 {
9651 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9652 }
9653 });
9654
9655 cx.assert_editor_state(
9656 &r#"
9657 use some::modified;
9658
9659
9660 fn main() {
9661 ˇ println!("hello there");
9662
9663 println!("around the");
9664 println!("world");
9665 }
9666 "#
9667 .unindent(),
9668 );
9669
9670 cx.update_editor(|editor, cx| {
9671 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9672 });
9673
9674 cx.assert_editor_state(
9675 &r#"
9676 use some::modified;
9677
9678 ˇ
9679 fn main() {
9680 println!("hello there");
9681
9682 println!("around the");
9683 println!("world");
9684 }
9685 "#
9686 .unindent(),
9687 );
9688
9689 cx.update_editor(|editor, cx| {
9690 for _ in 0..3 {
9691 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9692 }
9693 });
9694
9695 cx.assert_editor_state(
9696 &r#"
9697 use some::modified;
9698
9699
9700 fn main() {
9701 ˇ println!("hello there");
9702
9703 println!("around the");
9704 println!("world");
9705 }
9706 "#
9707 .unindent(),
9708 );
9709
9710 cx.update_editor(|editor, cx| {
9711 editor.fold(&Fold, cx);
9712
9713 //Make sure that the fold only gets one hunk
9714 for _ in 0..4 {
9715 editor.go_to_next_hunk(&GoToHunk, cx);
9716 }
9717 });
9718
9719 cx.assert_editor_state(
9720 &r#"
9721 ˇuse some::modified;
9722
9723
9724 fn main() {
9725 println!("hello there");
9726
9727 println!("around the");
9728 println!("world");
9729 }
9730 "#
9731 .unindent(),
9732 );
9733}
9734
9735#[test]
9736fn test_split_words() {
9737 fn split(text: &str) -> Vec<&str> {
9738 split_words(text).collect()
9739 }
9740
9741 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9742 assert_eq!(split("hello_world"), &["hello_", "world"]);
9743 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9744 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9745 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9746 assert_eq!(split("helloworld"), &["helloworld"]);
9747
9748 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9749}
9750
9751#[gpui::test]
9752async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9753 init_test(cx, |_| {});
9754
9755 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9756 let mut assert = |before, after| {
9757 let _state_context = cx.set_state(before);
9758 cx.update_editor(|editor, cx| {
9759 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9760 });
9761 cx.assert_editor_state(after);
9762 };
9763
9764 // Outside bracket jumps to outside of matching bracket
9765 assert("console.logˇ(var);", "console.log(var)ˇ;");
9766 assert("console.log(var)ˇ;", "console.logˇ(var);");
9767
9768 // Inside bracket jumps to inside of matching bracket
9769 assert("console.log(ˇvar);", "console.log(varˇ);");
9770 assert("console.log(varˇ);", "console.log(ˇvar);");
9771
9772 // When outside a bracket and inside, favor jumping to the inside bracket
9773 assert(
9774 "console.log('foo', [1, 2, 3]ˇ);",
9775 "console.log(ˇ'foo', [1, 2, 3]);",
9776 );
9777 assert(
9778 "console.log(ˇ'foo', [1, 2, 3]);",
9779 "console.log('foo', [1, 2, 3]ˇ);",
9780 );
9781
9782 // Bias forward if two options are equally likely
9783 assert(
9784 "let result = curried_fun()ˇ();",
9785 "let result = curried_fun()()ˇ;",
9786 );
9787
9788 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9789 assert(
9790 indoc! {"
9791 function test() {
9792 console.log('test')ˇ
9793 }"},
9794 indoc! {"
9795 function test() {
9796 console.logˇ('test')
9797 }"},
9798 );
9799}
9800
9801#[gpui::test]
9802async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9803 init_test(cx, |_| {});
9804
9805 let fs = FakeFs::new(cx.executor());
9806 fs.insert_tree(
9807 "/a",
9808 json!({
9809 "main.rs": "fn main() { let a = 5; }",
9810 "other.rs": "// Test file",
9811 }),
9812 )
9813 .await;
9814 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9815
9816 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9817 language_registry.add(Arc::new(Language::new(
9818 LanguageConfig {
9819 name: "Rust".into(),
9820 matcher: LanguageMatcher {
9821 path_suffixes: vec!["rs".to_string()],
9822 ..Default::default()
9823 },
9824 brackets: BracketPairConfig {
9825 pairs: vec![BracketPair {
9826 start: "{".to_string(),
9827 end: "}".to_string(),
9828 close: true,
9829 surround: true,
9830 newline: true,
9831 }],
9832 disabled_scopes_by_bracket_ix: Vec::new(),
9833 },
9834 ..Default::default()
9835 },
9836 Some(tree_sitter_rust::LANGUAGE.into()),
9837 )));
9838 let mut fake_servers = language_registry.register_fake_lsp(
9839 "Rust",
9840 FakeLspAdapter {
9841 capabilities: lsp::ServerCapabilities {
9842 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9843 first_trigger_character: "{".to_string(),
9844 more_trigger_character: None,
9845 }),
9846 ..Default::default()
9847 },
9848 ..Default::default()
9849 },
9850 );
9851
9852 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9853
9854 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9855
9856 let worktree_id = workspace
9857 .update(cx, |workspace, cx| {
9858 workspace.project().update(cx, |project, cx| {
9859 project.worktrees(cx).next().unwrap().read(cx).id()
9860 })
9861 })
9862 .unwrap();
9863
9864 let buffer = project
9865 .update(cx, |project, cx| {
9866 project.open_local_buffer("/a/main.rs", cx)
9867 })
9868 .await
9869 .unwrap();
9870 cx.executor().run_until_parked();
9871 cx.executor().start_waiting();
9872 let fake_server = fake_servers.next().await.unwrap();
9873 let editor_handle = workspace
9874 .update(cx, |workspace, cx| {
9875 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9876 })
9877 .unwrap()
9878 .await
9879 .unwrap()
9880 .downcast::<Editor>()
9881 .unwrap();
9882
9883 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9884 assert_eq!(
9885 params.text_document_position.text_document.uri,
9886 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9887 );
9888 assert_eq!(
9889 params.text_document_position.position,
9890 lsp::Position::new(0, 21),
9891 );
9892
9893 Ok(Some(vec![lsp::TextEdit {
9894 new_text: "]".to_string(),
9895 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9896 }]))
9897 });
9898
9899 editor_handle.update(cx, |editor, cx| {
9900 editor.focus(cx);
9901 editor.change_selections(None, cx, |s| {
9902 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9903 });
9904 editor.handle_input("{", cx);
9905 });
9906
9907 cx.executor().run_until_parked();
9908
9909 buffer.update(cx, |buffer, _| {
9910 assert_eq!(
9911 buffer.text(),
9912 "fn main() { let a = {5}; }",
9913 "No extra braces from on type formatting should appear in the buffer"
9914 )
9915 });
9916}
9917
9918#[gpui::test]
9919async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9920 init_test(cx, |_| {});
9921
9922 let fs = FakeFs::new(cx.executor());
9923 fs.insert_tree(
9924 "/a",
9925 json!({
9926 "main.rs": "fn main() { let a = 5; }",
9927 "other.rs": "// Test file",
9928 }),
9929 )
9930 .await;
9931
9932 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9933
9934 let server_restarts = Arc::new(AtomicUsize::new(0));
9935 let closure_restarts = Arc::clone(&server_restarts);
9936 let language_server_name = "test language server";
9937 let language_name: LanguageName = "Rust".into();
9938
9939 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9940 language_registry.add(Arc::new(Language::new(
9941 LanguageConfig {
9942 name: language_name.clone(),
9943 matcher: LanguageMatcher {
9944 path_suffixes: vec!["rs".to_string()],
9945 ..Default::default()
9946 },
9947 ..Default::default()
9948 },
9949 Some(tree_sitter_rust::LANGUAGE.into()),
9950 )));
9951 let mut fake_servers = language_registry.register_fake_lsp(
9952 "Rust",
9953 FakeLspAdapter {
9954 name: language_server_name,
9955 initialization_options: Some(json!({
9956 "testOptionValue": true
9957 })),
9958 initializer: Some(Box::new(move |fake_server| {
9959 let task_restarts = Arc::clone(&closure_restarts);
9960 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9961 task_restarts.fetch_add(1, atomic::Ordering::Release);
9962 futures::future::ready(Ok(()))
9963 });
9964 })),
9965 ..Default::default()
9966 },
9967 );
9968
9969 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9970 let _buffer = project
9971 .update(cx, |project, cx| {
9972 project.open_local_buffer("/a/main.rs", cx)
9973 })
9974 .await
9975 .unwrap();
9976 let _fake_server = fake_servers.next().await.unwrap();
9977 update_test_language_settings(cx, |language_settings| {
9978 language_settings.languages.insert(
9979 language_name.clone(),
9980 LanguageSettingsContent {
9981 tab_size: NonZeroU32::new(8),
9982 ..Default::default()
9983 },
9984 );
9985 });
9986 cx.executor().run_until_parked();
9987 assert_eq!(
9988 server_restarts.load(atomic::Ordering::Acquire),
9989 0,
9990 "Should not restart LSP server on an unrelated change"
9991 );
9992
9993 update_test_project_settings(cx, |project_settings| {
9994 project_settings.lsp.insert(
9995 "Some other server name".into(),
9996 LspSettings {
9997 binary: None,
9998 settings: None,
9999 initialization_options: Some(json!({
10000 "some other init value": false
10001 })),
10002 },
10003 );
10004 });
10005 cx.executor().run_until_parked();
10006 assert_eq!(
10007 server_restarts.load(atomic::Ordering::Acquire),
10008 0,
10009 "Should not restart LSP server on an unrelated LSP settings change"
10010 );
10011
10012 update_test_project_settings(cx, |project_settings| {
10013 project_settings.lsp.insert(
10014 language_server_name.into(),
10015 LspSettings {
10016 binary: None,
10017 settings: None,
10018 initialization_options: Some(json!({
10019 "anotherInitValue": false
10020 })),
10021 },
10022 );
10023 });
10024 cx.executor().run_until_parked();
10025 assert_eq!(
10026 server_restarts.load(atomic::Ordering::Acquire),
10027 1,
10028 "Should restart LSP server on a related LSP settings change"
10029 );
10030
10031 update_test_project_settings(cx, |project_settings| {
10032 project_settings.lsp.insert(
10033 language_server_name.into(),
10034 LspSettings {
10035 binary: None,
10036 settings: None,
10037 initialization_options: Some(json!({
10038 "anotherInitValue": false
10039 })),
10040 },
10041 );
10042 });
10043 cx.executor().run_until_parked();
10044 assert_eq!(
10045 server_restarts.load(atomic::Ordering::Acquire),
10046 1,
10047 "Should not restart LSP server on a related LSP settings change that is the same"
10048 );
10049
10050 update_test_project_settings(cx, |project_settings| {
10051 project_settings.lsp.insert(
10052 language_server_name.into(),
10053 LspSettings {
10054 binary: None,
10055 settings: None,
10056 initialization_options: None,
10057 },
10058 );
10059 });
10060 cx.executor().run_until_parked();
10061 assert_eq!(
10062 server_restarts.load(atomic::Ordering::Acquire),
10063 2,
10064 "Should restart LSP server on another related LSP settings change"
10065 );
10066}
10067
10068#[gpui::test]
10069async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10070 init_test(cx, |_| {});
10071
10072 let mut cx = EditorLspTestContext::new_rust(
10073 lsp::ServerCapabilities {
10074 completion_provider: Some(lsp::CompletionOptions {
10075 trigger_characters: Some(vec![".".to_string()]),
10076 resolve_provider: Some(true),
10077 ..Default::default()
10078 }),
10079 ..Default::default()
10080 },
10081 cx,
10082 )
10083 .await;
10084
10085 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10086 cx.simulate_keystroke(".");
10087 let completion_item = lsp::CompletionItem {
10088 label: "some".into(),
10089 kind: Some(lsp::CompletionItemKind::SNIPPET),
10090 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10091 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10092 kind: lsp::MarkupKind::Markdown,
10093 value: "```rust\nSome(2)\n```".to_string(),
10094 })),
10095 deprecated: Some(false),
10096 sort_text: Some("fffffff2".to_string()),
10097 filter_text: Some("some".to_string()),
10098 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10099 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10100 range: lsp::Range {
10101 start: lsp::Position {
10102 line: 0,
10103 character: 22,
10104 },
10105 end: lsp::Position {
10106 line: 0,
10107 character: 22,
10108 },
10109 },
10110 new_text: "Some(2)".to_string(),
10111 })),
10112 additional_text_edits: Some(vec![lsp::TextEdit {
10113 range: lsp::Range {
10114 start: lsp::Position {
10115 line: 0,
10116 character: 20,
10117 },
10118 end: lsp::Position {
10119 line: 0,
10120 character: 22,
10121 },
10122 },
10123 new_text: "".to_string(),
10124 }]),
10125 ..Default::default()
10126 };
10127
10128 let closure_completion_item = completion_item.clone();
10129 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10130 let task_completion_item = closure_completion_item.clone();
10131 async move {
10132 Ok(Some(lsp::CompletionResponse::Array(vec![
10133 task_completion_item,
10134 ])))
10135 }
10136 });
10137
10138 request.next().await;
10139
10140 cx.condition(|editor, _| editor.context_menu_visible())
10141 .await;
10142 let apply_additional_edits = cx.update_editor(|editor, cx| {
10143 editor
10144 .confirm_completion(&ConfirmCompletion::default(), cx)
10145 .unwrap()
10146 });
10147 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10148
10149 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10150 let task_completion_item = completion_item.clone();
10151 async move { Ok(task_completion_item) }
10152 })
10153 .next()
10154 .await
10155 .unwrap();
10156 apply_additional_edits.await.unwrap();
10157 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10158}
10159
10160#[gpui::test]
10161async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10162 init_test(cx, |_| {});
10163
10164 let mut cx = EditorLspTestContext::new(
10165 Language::new(
10166 LanguageConfig {
10167 matcher: LanguageMatcher {
10168 path_suffixes: vec!["jsx".into()],
10169 ..Default::default()
10170 },
10171 overrides: [(
10172 "element".into(),
10173 LanguageConfigOverride {
10174 word_characters: Override::Set(['-'].into_iter().collect()),
10175 ..Default::default()
10176 },
10177 )]
10178 .into_iter()
10179 .collect(),
10180 ..Default::default()
10181 },
10182 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10183 )
10184 .with_override_query("(jsx_self_closing_element) @element")
10185 .unwrap(),
10186 lsp::ServerCapabilities {
10187 completion_provider: Some(lsp::CompletionOptions {
10188 trigger_characters: Some(vec![":".to_string()]),
10189 ..Default::default()
10190 }),
10191 ..Default::default()
10192 },
10193 cx,
10194 )
10195 .await;
10196
10197 cx.lsp
10198 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10199 Ok(Some(lsp::CompletionResponse::Array(vec![
10200 lsp::CompletionItem {
10201 label: "bg-blue".into(),
10202 ..Default::default()
10203 },
10204 lsp::CompletionItem {
10205 label: "bg-red".into(),
10206 ..Default::default()
10207 },
10208 lsp::CompletionItem {
10209 label: "bg-yellow".into(),
10210 ..Default::default()
10211 },
10212 ])))
10213 });
10214
10215 cx.set_state(r#"<p class="bgˇ" />"#);
10216
10217 // Trigger completion when typing a dash, because the dash is an extra
10218 // word character in the 'element' scope, which contains the cursor.
10219 cx.simulate_keystroke("-");
10220 cx.executor().run_until_parked();
10221 cx.update_editor(|editor, _| {
10222 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10223 assert_eq!(
10224 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10225 &["bg-red", "bg-blue", "bg-yellow"]
10226 );
10227 } else {
10228 panic!("expected completion menu to be open");
10229 }
10230 });
10231
10232 cx.simulate_keystroke("l");
10233 cx.executor().run_until_parked();
10234 cx.update_editor(|editor, _| {
10235 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10236 assert_eq!(
10237 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10238 &["bg-blue", "bg-yellow"]
10239 );
10240 } else {
10241 panic!("expected completion menu to be open");
10242 }
10243 });
10244
10245 // When filtering completions, consider the character after the '-' to
10246 // be the start of a subword.
10247 cx.set_state(r#"<p class="yelˇ" />"#);
10248 cx.simulate_keystroke("l");
10249 cx.executor().run_until_parked();
10250 cx.update_editor(|editor, _| {
10251 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10252 assert_eq!(
10253 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10254 &["bg-yellow"]
10255 );
10256 } else {
10257 panic!("expected completion menu to be open");
10258 }
10259 });
10260}
10261
10262#[gpui::test]
10263async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10264 init_test(cx, |settings| {
10265 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10266 FormatterList(vec![Formatter::Prettier].into()),
10267 ))
10268 });
10269
10270 let fs = FakeFs::new(cx.executor());
10271 fs.insert_file("/file.ts", Default::default()).await;
10272
10273 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10274 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10275
10276 language_registry.add(Arc::new(Language::new(
10277 LanguageConfig {
10278 name: "TypeScript".into(),
10279 matcher: LanguageMatcher {
10280 path_suffixes: vec!["ts".to_string()],
10281 ..Default::default()
10282 },
10283 ..Default::default()
10284 },
10285 Some(tree_sitter_rust::LANGUAGE.into()),
10286 )));
10287 update_test_language_settings(cx, |settings| {
10288 settings.defaults.prettier = Some(PrettierSettings {
10289 allowed: true,
10290 ..PrettierSettings::default()
10291 });
10292 });
10293
10294 let test_plugin = "test_plugin";
10295 let _ = language_registry.register_fake_lsp(
10296 "TypeScript",
10297 FakeLspAdapter {
10298 prettier_plugins: vec![test_plugin],
10299 ..Default::default()
10300 },
10301 );
10302
10303 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10304 let buffer = project
10305 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10306 .await
10307 .unwrap();
10308
10309 let buffer_text = "one\ntwo\nthree\n";
10310 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10311 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10312 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10313
10314 editor
10315 .update(cx, |editor, cx| {
10316 editor.perform_format(
10317 project.clone(),
10318 FormatTrigger::Manual,
10319 FormatTarget::Buffer,
10320 cx,
10321 )
10322 })
10323 .unwrap()
10324 .await;
10325 assert_eq!(
10326 editor.update(cx, |editor, cx| editor.text(cx)),
10327 buffer_text.to_string() + prettier_format_suffix,
10328 "Test prettier formatting was not applied to the original buffer text",
10329 );
10330
10331 update_test_language_settings(cx, |settings| {
10332 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10333 });
10334 let format = editor.update(cx, |editor, cx| {
10335 editor.perform_format(
10336 project.clone(),
10337 FormatTrigger::Manual,
10338 FormatTarget::Buffer,
10339 cx,
10340 )
10341 });
10342 format.await.unwrap();
10343 assert_eq!(
10344 editor.update(cx, |editor, cx| editor.text(cx)),
10345 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10346 "Autoformatting (via test prettier) was not applied to the original buffer text",
10347 );
10348}
10349
10350#[gpui::test]
10351async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10352 init_test(cx, |_| {});
10353 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10354 let base_text = indoc! {r#"struct Row;
10355struct Row1;
10356struct Row2;
10357
10358struct Row4;
10359struct Row5;
10360struct Row6;
10361
10362struct Row8;
10363struct Row9;
10364struct Row10;"#};
10365
10366 // When addition hunks are not adjacent to carets, no hunk revert is performed
10367 assert_hunk_revert(
10368 indoc! {r#"struct Row;
10369 struct Row1;
10370 struct Row1.1;
10371 struct Row1.2;
10372 struct Row2;ˇ
10373
10374 struct Row4;
10375 struct Row5;
10376 struct Row6;
10377
10378 struct Row8;
10379 ˇstruct Row9;
10380 struct Row9.1;
10381 struct Row9.2;
10382 struct Row9.3;
10383 struct Row10;"#},
10384 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10385 indoc! {r#"struct Row;
10386 struct Row1;
10387 struct Row1.1;
10388 struct Row1.2;
10389 struct Row2;ˇ
10390
10391 struct Row4;
10392 struct Row5;
10393 struct Row6;
10394
10395 struct Row8;
10396 ˇstruct Row9;
10397 struct Row9.1;
10398 struct Row9.2;
10399 struct Row9.3;
10400 struct Row10;"#},
10401 base_text,
10402 &mut cx,
10403 );
10404 // Same for selections
10405 assert_hunk_revert(
10406 indoc! {r#"struct Row;
10407 struct Row1;
10408 struct Row2;
10409 struct Row2.1;
10410 struct Row2.2;
10411 «ˇ
10412 struct Row4;
10413 struct» Row5;
10414 «struct Row6;
10415 ˇ»
10416 struct Row9.1;
10417 struct Row9.2;
10418 struct Row9.3;
10419 struct Row8;
10420 struct Row9;
10421 struct Row10;"#},
10422 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10423 indoc! {r#"struct Row;
10424 struct Row1;
10425 struct Row2;
10426 struct Row2.1;
10427 struct Row2.2;
10428 «ˇ
10429 struct Row4;
10430 struct» Row5;
10431 «struct Row6;
10432 ˇ»
10433 struct Row9.1;
10434 struct Row9.2;
10435 struct Row9.3;
10436 struct Row8;
10437 struct Row9;
10438 struct Row10;"#},
10439 base_text,
10440 &mut cx,
10441 );
10442
10443 // When carets and selections intersect the addition hunks, those are reverted.
10444 // Adjacent carets got merged.
10445 assert_hunk_revert(
10446 indoc! {r#"struct Row;
10447 ˇ// something on the top
10448 struct Row1;
10449 struct Row2;
10450 struct Roˇw3.1;
10451 struct Row2.2;
10452 struct Row2.3;ˇ
10453
10454 struct Row4;
10455 struct ˇRow5.1;
10456 struct Row5.2;
10457 struct «Rowˇ»5.3;
10458 struct Row5;
10459 struct Row6;
10460 ˇ
10461 struct Row9.1;
10462 struct «Rowˇ»9.2;
10463 struct «ˇRow»9.3;
10464 struct Row8;
10465 struct Row9;
10466 «ˇ// something on bottom»
10467 struct Row10;"#},
10468 vec![
10469 DiffHunkStatus::Added,
10470 DiffHunkStatus::Added,
10471 DiffHunkStatus::Added,
10472 DiffHunkStatus::Added,
10473 DiffHunkStatus::Added,
10474 ],
10475 indoc! {r#"struct Row;
10476 ˇstruct Row1;
10477 struct Row2;
10478 ˇ
10479 struct Row4;
10480 ˇstruct Row5;
10481 struct Row6;
10482 ˇ
10483 ˇstruct Row8;
10484 struct Row9;
10485 ˇstruct Row10;"#},
10486 base_text,
10487 &mut cx,
10488 );
10489}
10490
10491#[gpui::test]
10492async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10493 init_test(cx, |_| {});
10494 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10495 let base_text = indoc! {r#"struct Row;
10496struct Row1;
10497struct Row2;
10498
10499struct Row4;
10500struct Row5;
10501struct Row6;
10502
10503struct Row8;
10504struct Row9;
10505struct Row10;"#};
10506
10507 // Modification hunks behave the same as the addition ones.
10508 assert_hunk_revert(
10509 indoc! {r#"struct Row;
10510 struct Row1;
10511 struct Row33;
10512 ˇ
10513 struct Row4;
10514 struct Row5;
10515 struct Row6;
10516 ˇ
10517 struct Row99;
10518 struct Row9;
10519 struct Row10;"#},
10520 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10521 indoc! {r#"struct Row;
10522 struct Row1;
10523 struct Row33;
10524 ˇ
10525 struct Row4;
10526 struct Row5;
10527 struct Row6;
10528 ˇ
10529 struct Row99;
10530 struct Row9;
10531 struct Row10;"#},
10532 base_text,
10533 &mut cx,
10534 );
10535 assert_hunk_revert(
10536 indoc! {r#"struct Row;
10537 struct Row1;
10538 struct Row33;
10539 «ˇ
10540 struct Row4;
10541 struct» Row5;
10542 «struct Row6;
10543 ˇ»
10544 struct Row99;
10545 struct Row9;
10546 struct Row10;"#},
10547 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10548 indoc! {r#"struct Row;
10549 struct Row1;
10550 struct Row33;
10551 «ˇ
10552 struct Row4;
10553 struct» Row5;
10554 «struct Row6;
10555 ˇ»
10556 struct Row99;
10557 struct Row9;
10558 struct Row10;"#},
10559 base_text,
10560 &mut cx,
10561 );
10562
10563 assert_hunk_revert(
10564 indoc! {r#"ˇstruct Row1.1;
10565 struct Row1;
10566 «ˇstr»uct Row22;
10567
10568 struct ˇRow44;
10569 struct Row5;
10570 struct «Rˇ»ow66;ˇ
10571
10572 «struˇ»ct Row88;
10573 struct Row9;
10574 struct Row1011;ˇ"#},
10575 vec![
10576 DiffHunkStatus::Modified,
10577 DiffHunkStatus::Modified,
10578 DiffHunkStatus::Modified,
10579 DiffHunkStatus::Modified,
10580 DiffHunkStatus::Modified,
10581 DiffHunkStatus::Modified,
10582 ],
10583 indoc! {r#"struct Row;
10584 ˇstruct Row1;
10585 struct Row2;
10586 ˇ
10587 struct Row4;
10588 ˇstruct Row5;
10589 struct Row6;
10590 ˇ
10591 struct Row8;
10592 ˇstruct Row9;
10593 struct Row10;ˇ"#},
10594 base_text,
10595 &mut cx,
10596 );
10597}
10598
10599#[gpui::test]
10600async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10601 init_test(cx, |_| {});
10602 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10603 let base_text = indoc! {r#"struct Row;
10604struct Row1;
10605struct Row2;
10606
10607struct Row4;
10608struct Row5;
10609struct Row6;
10610
10611struct Row8;
10612struct Row9;
10613struct Row10;"#};
10614
10615 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10616 assert_hunk_revert(
10617 indoc! {r#"struct Row;
10618 struct Row2;
10619
10620 ˇstruct Row4;
10621 struct Row5;
10622 struct Row6;
10623 ˇ
10624 struct Row8;
10625 struct Row10;"#},
10626 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10627 indoc! {r#"struct Row;
10628 struct Row2;
10629
10630 ˇstruct Row4;
10631 struct Row5;
10632 struct Row6;
10633 ˇ
10634 struct Row8;
10635 struct Row10;"#},
10636 base_text,
10637 &mut cx,
10638 );
10639 assert_hunk_revert(
10640 indoc! {r#"struct Row;
10641 struct Row2;
10642
10643 «ˇstruct Row4;
10644 struct» Row5;
10645 «struct Row6;
10646 ˇ»
10647 struct Row8;
10648 struct Row10;"#},
10649 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10650 indoc! {r#"struct Row;
10651 struct Row2;
10652
10653 «ˇstruct Row4;
10654 struct» Row5;
10655 «struct Row6;
10656 ˇ»
10657 struct Row8;
10658 struct Row10;"#},
10659 base_text,
10660 &mut cx,
10661 );
10662
10663 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10664 assert_hunk_revert(
10665 indoc! {r#"struct Row;
10666 ˇstruct Row2;
10667
10668 struct Row4;
10669 struct Row5;
10670 struct Row6;
10671
10672 struct Row8;ˇ
10673 struct Row10;"#},
10674 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10675 indoc! {r#"struct Row;
10676 struct Row1;
10677 ˇstruct Row2;
10678
10679 struct Row4;
10680 struct Row5;
10681 struct Row6;
10682
10683 struct Row8;ˇ
10684 struct Row9;
10685 struct Row10;"#},
10686 base_text,
10687 &mut cx,
10688 );
10689 assert_hunk_revert(
10690 indoc! {r#"struct Row;
10691 struct Row2«ˇ;
10692 struct Row4;
10693 struct» Row5;
10694 «struct Row6;
10695
10696 struct Row8;ˇ»
10697 struct Row10;"#},
10698 vec![
10699 DiffHunkStatus::Removed,
10700 DiffHunkStatus::Removed,
10701 DiffHunkStatus::Removed,
10702 ],
10703 indoc! {r#"struct Row;
10704 struct Row1;
10705 struct Row2«ˇ;
10706
10707 struct Row4;
10708 struct» Row5;
10709 «struct Row6;
10710
10711 struct Row8;ˇ»
10712 struct Row9;
10713 struct Row10;"#},
10714 base_text,
10715 &mut cx,
10716 );
10717}
10718
10719#[gpui::test]
10720async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10721 init_test(cx, |_| {});
10722
10723 let cols = 4;
10724 let rows = 10;
10725 let sample_text_1 = sample_text(rows, cols, 'a');
10726 assert_eq!(
10727 sample_text_1,
10728 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10729 );
10730 let sample_text_2 = sample_text(rows, cols, 'l');
10731 assert_eq!(
10732 sample_text_2,
10733 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10734 );
10735 let sample_text_3 = sample_text(rows, cols, 'v');
10736 assert_eq!(
10737 sample_text_3,
10738 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10739 );
10740
10741 fn diff_every_buffer_row(
10742 buffer: &Model<Buffer>,
10743 sample_text: String,
10744 cols: usize,
10745 cx: &mut gpui::TestAppContext,
10746 ) {
10747 // revert first character in each row, creating one large diff hunk per buffer
10748 let is_first_char = |offset: usize| offset % cols == 0;
10749 buffer.update(cx, |buffer, cx| {
10750 buffer.set_text(
10751 sample_text
10752 .chars()
10753 .enumerate()
10754 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10755 .collect::<String>(),
10756 cx,
10757 );
10758 buffer.set_diff_base(Some(sample_text), cx);
10759 });
10760 cx.executor().run_until_parked();
10761 }
10762
10763 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10764 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10765
10766 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10767 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10768
10769 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10770 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10771
10772 let multibuffer = cx.new_model(|cx| {
10773 let mut multibuffer = MultiBuffer::new(ReadWrite);
10774 multibuffer.push_excerpts(
10775 buffer_1.clone(),
10776 [
10777 ExcerptRange {
10778 context: Point::new(0, 0)..Point::new(3, 0),
10779 primary: None,
10780 },
10781 ExcerptRange {
10782 context: Point::new(5, 0)..Point::new(7, 0),
10783 primary: None,
10784 },
10785 ExcerptRange {
10786 context: Point::new(9, 0)..Point::new(10, 4),
10787 primary: None,
10788 },
10789 ],
10790 cx,
10791 );
10792 multibuffer.push_excerpts(
10793 buffer_2.clone(),
10794 [
10795 ExcerptRange {
10796 context: Point::new(0, 0)..Point::new(3, 0),
10797 primary: None,
10798 },
10799 ExcerptRange {
10800 context: Point::new(5, 0)..Point::new(7, 0),
10801 primary: None,
10802 },
10803 ExcerptRange {
10804 context: Point::new(9, 0)..Point::new(10, 4),
10805 primary: None,
10806 },
10807 ],
10808 cx,
10809 );
10810 multibuffer.push_excerpts(
10811 buffer_3.clone(),
10812 [
10813 ExcerptRange {
10814 context: Point::new(0, 0)..Point::new(3, 0),
10815 primary: None,
10816 },
10817 ExcerptRange {
10818 context: Point::new(5, 0)..Point::new(7, 0),
10819 primary: None,
10820 },
10821 ExcerptRange {
10822 context: Point::new(9, 0)..Point::new(10, 4),
10823 primary: None,
10824 },
10825 ],
10826 cx,
10827 );
10828 multibuffer
10829 });
10830
10831 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10832 editor.update(cx, |editor, cx| {
10833 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");
10834 editor.select_all(&SelectAll, cx);
10835 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10836 });
10837 cx.executor().run_until_parked();
10838 // When all ranges are selected, all buffer hunks are reverted.
10839 editor.update(cx, |editor, cx| {
10840 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");
10841 });
10842 buffer_1.update(cx, |buffer, _| {
10843 assert_eq!(buffer.text(), sample_text_1);
10844 });
10845 buffer_2.update(cx, |buffer, _| {
10846 assert_eq!(buffer.text(), sample_text_2);
10847 });
10848 buffer_3.update(cx, |buffer, _| {
10849 assert_eq!(buffer.text(), sample_text_3);
10850 });
10851
10852 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10853 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10854 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10855 editor.update(cx, |editor, cx| {
10856 editor.change_selections(None, cx, |s| {
10857 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10858 });
10859 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10860 });
10861 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10862 // but not affect buffer_2 and its related excerpts.
10863 editor.update(cx, |editor, cx| {
10864 assert_eq!(
10865 editor.text(cx),
10866 "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"
10867 );
10868 });
10869 buffer_1.update(cx, |buffer, _| {
10870 assert_eq!(buffer.text(), sample_text_1);
10871 });
10872 buffer_2.update(cx, |buffer, _| {
10873 assert_eq!(
10874 buffer.text(),
10875 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10876 );
10877 });
10878 buffer_3.update(cx, |buffer, _| {
10879 assert_eq!(
10880 buffer.text(),
10881 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10882 );
10883 });
10884}
10885
10886#[gpui::test]
10887async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10888 init_test(cx, |_| {});
10889
10890 let cols = 4;
10891 let rows = 10;
10892 let sample_text_1 = sample_text(rows, cols, 'a');
10893 assert_eq!(
10894 sample_text_1,
10895 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10896 );
10897 let sample_text_2 = sample_text(rows, cols, 'l');
10898 assert_eq!(
10899 sample_text_2,
10900 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10901 );
10902 let sample_text_3 = sample_text(rows, cols, 'v');
10903 assert_eq!(
10904 sample_text_3,
10905 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10906 );
10907
10908 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10909 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10910 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10911
10912 let multi_buffer = cx.new_model(|cx| {
10913 let mut multibuffer = MultiBuffer::new(ReadWrite);
10914 multibuffer.push_excerpts(
10915 buffer_1.clone(),
10916 [
10917 ExcerptRange {
10918 context: Point::new(0, 0)..Point::new(3, 0),
10919 primary: None,
10920 },
10921 ExcerptRange {
10922 context: Point::new(5, 0)..Point::new(7, 0),
10923 primary: None,
10924 },
10925 ExcerptRange {
10926 context: Point::new(9, 0)..Point::new(10, 4),
10927 primary: None,
10928 },
10929 ],
10930 cx,
10931 );
10932 multibuffer.push_excerpts(
10933 buffer_2.clone(),
10934 [
10935 ExcerptRange {
10936 context: Point::new(0, 0)..Point::new(3, 0),
10937 primary: None,
10938 },
10939 ExcerptRange {
10940 context: Point::new(5, 0)..Point::new(7, 0),
10941 primary: None,
10942 },
10943 ExcerptRange {
10944 context: Point::new(9, 0)..Point::new(10, 4),
10945 primary: None,
10946 },
10947 ],
10948 cx,
10949 );
10950 multibuffer.push_excerpts(
10951 buffer_3.clone(),
10952 [
10953 ExcerptRange {
10954 context: Point::new(0, 0)..Point::new(3, 0),
10955 primary: None,
10956 },
10957 ExcerptRange {
10958 context: Point::new(5, 0)..Point::new(7, 0),
10959 primary: None,
10960 },
10961 ExcerptRange {
10962 context: Point::new(9, 0)..Point::new(10, 4),
10963 primary: None,
10964 },
10965 ],
10966 cx,
10967 );
10968 multibuffer
10969 });
10970
10971 let fs = FakeFs::new(cx.executor());
10972 fs.insert_tree(
10973 "/a",
10974 json!({
10975 "main.rs": sample_text_1,
10976 "other.rs": sample_text_2,
10977 "lib.rs": sample_text_3,
10978 }),
10979 )
10980 .await;
10981 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10982 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10983 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10984 let multi_buffer_editor = cx.new_view(|cx| {
10985 Editor::new(
10986 EditorMode::Full,
10987 multi_buffer,
10988 Some(project.clone()),
10989 true,
10990 cx,
10991 )
10992 });
10993 let multibuffer_item_id = workspace
10994 .update(cx, |workspace, cx| {
10995 assert!(
10996 workspace.active_item(cx).is_none(),
10997 "active item should be None before the first item is added"
10998 );
10999 workspace.add_item_to_active_pane(
11000 Box::new(multi_buffer_editor.clone()),
11001 None,
11002 true,
11003 cx,
11004 );
11005 let active_item = workspace
11006 .active_item(cx)
11007 .expect("should have an active item after adding the multi buffer");
11008 assert!(
11009 !active_item.is_singleton(cx),
11010 "A multi buffer was expected to active after adding"
11011 );
11012 active_item.item_id()
11013 })
11014 .unwrap();
11015 cx.executor().run_until_parked();
11016
11017 multi_buffer_editor.update(cx, |editor, cx| {
11018 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11019 editor.open_excerpts(&OpenExcerpts, cx);
11020 });
11021 cx.executor().run_until_parked();
11022 let first_item_id = workspace
11023 .update(cx, |workspace, cx| {
11024 let active_item = workspace
11025 .active_item(cx)
11026 .expect("should have an active item after navigating into the 1st buffer");
11027 let first_item_id = active_item.item_id();
11028 assert_ne!(
11029 first_item_id, multibuffer_item_id,
11030 "Should navigate into the 1st buffer and activate it"
11031 );
11032 assert!(
11033 active_item.is_singleton(cx),
11034 "New active item should be a singleton buffer"
11035 );
11036 assert_eq!(
11037 active_item
11038 .act_as::<Editor>(cx)
11039 .expect("should have navigated into an editor for the 1st buffer")
11040 .read(cx)
11041 .text(cx),
11042 sample_text_1
11043 );
11044
11045 workspace
11046 .go_back(workspace.active_pane().downgrade(), cx)
11047 .detach_and_log_err(cx);
11048
11049 first_item_id
11050 })
11051 .unwrap();
11052 cx.executor().run_until_parked();
11053 workspace
11054 .update(cx, |workspace, cx| {
11055 let active_item = workspace
11056 .active_item(cx)
11057 .expect("should have an active item after navigating back");
11058 assert_eq!(
11059 active_item.item_id(),
11060 multibuffer_item_id,
11061 "Should navigate back to the multi buffer"
11062 );
11063 assert!(!active_item.is_singleton(cx));
11064 })
11065 .unwrap();
11066
11067 multi_buffer_editor.update(cx, |editor, cx| {
11068 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11069 s.select_ranges(Some(39..40))
11070 });
11071 editor.open_excerpts(&OpenExcerpts, cx);
11072 });
11073 cx.executor().run_until_parked();
11074 let second_item_id = workspace
11075 .update(cx, |workspace, cx| {
11076 let active_item = workspace
11077 .active_item(cx)
11078 .expect("should have an active item after navigating into the 2nd buffer");
11079 let second_item_id = active_item.item_id();
11080 assert_ne!(
11081 second_item_id, multibuffer_item_id,
11082 "Should navigate away from the multibuffer"
11083 );
11084 assert_ne!(
11085 second_item_id, first_item_id,
11086 "Should navigate into the 2nd buffer and activate it"
11087 );
11088 assert!(
11089 active_item.is_singleton(cx),
11090 "New active item should be a singleton buffer"
11091 );
11092 assert_eq!(
11093 active_item
11094 .act_as::<Editor>(cx)
11095 .expect("should have navigated into an editor")
11096 .read(cx)
11097 .text(cx),
11098 sample_text_2
11099 );
11100
11101 workspace
11102 .go_back(workspace.active_pane().downgrade(), cx)
11103 .detach_and_log_err(cx);
11104
11105 second_item_id
11106 })
11107 .unwrap();
11108 cx.executor().run_until_parked();
11109 workspace
11110 .update(cx, |workspace, cx| {
11111 let active_item = workspace
11112 .active_item(cx)
11113 .expect("should have an active item after navigating back from the 2nd buffer");
11114 assert_eq!(
11115 active_item.item_id(),
11116 multibuffer_item_id,
11117 "Should navigate back from the 2nd buffer to the multi buffer"
11118 );
11119 assert!(!active_item.is_singleton(cx));
11120 })
11121 .unwrap();
11122
11123 multi_buffer_editor.update(cx, |editor, cx| {
11124 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11125 s.select_ranges(Some(60..70))
11126 });
11127 editor.open_excerpts(&OpenExcerpts, cx);
11128 });
11129 cx.executor().run_until_parked();
11130 workspace
11131 .update(cx, |workspace, cx| {
11132 let active_item = workspace
11133 .active_item(cx)
11134 .expect("should have an active item after navigating into the 3rd buffer");
11135 let third_item_id = active_item.item_id();
11136 assert_ne!(
11137 third_item_id, multibuffer_item_id,
11138 "Should navigate into the 3rd buffer and activate it"
11139 );
11140 assert_ne!(third_item_id, first_item_id);
11141 assert_ne!(third_item_id, second_item_id);
11142 assert!(
11143 active_item.is_singleton(cx),
11144 "New active item should be a singleton buffer"
11145 );
11146 assert_eq!(
11147 active_item
11148 .act_as::<Editor>(cx)
11149 .expect("should have navigated into an editor")
11150 .read(cx)
11151 .text(cx),
11152 sample_text_3
11153 );
11154
11155 workspace
11156 .go_back(workspace.active_pane().downgrade(), cx)
11157 .detach_and_log_err(cx);
11158 })
11159 .unwrap();
11160 cx.executor().run_until_parked();
11161 workspace
11162 .update(cx, |workspace, cx| {
11163 let active_item = workspace
11164 .active_item(cx)
11165 .expect("should have an active item after navigating back from the 3rd buffer");
11166 assert_eq!(
11167 active_item.item_id(),
11168 multibuffer_item_id,
11169 "Should navigate back from the 3rd buffer to the multi buffer"
11170 );
11171 assert!(!active_item.is_singleton(cx));
11172 })
11173 .unwrap();
11174}
11175
11176#[gpui::test]
11177async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11178 init_test(cx, |_| {});
11179
11180 let mut cx = EditorTestContext::new(cx).await;
11181
11182 let diff_base = r#"
11183 use some::mod;
11184
11185 const A: u32 = 42;
11186
11187 fn main() {
11188 println!("hello");
11189
11190 println!("world");
11191 }
11192 "#
11193 .unindent();
11194
11195 cx.set_state(
11196 &r#"
11197 use some::modified;
11198
11199 ˇ
11200 fn main() {
11201 println!("hello there");
11202
11203 println!("around the");
11204 println!("world");
11205 }
11206 "#
11207 .unindent(),
11208 );
11209
11210 cx.set_diff_base(Some(&diff_base));
11211 executor.run_until_parked();
11212
11213 cx.update_editor(|editor, cx| {
11214 editor.go_to_next_hunk(&GoToHunk, cx);
11215 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11216 });
11217 executor.run_until_parked();
11218 cx.assert_diff_hunks(
11219 r#"
11220 use some::modified;
11221
11222
11223 fn main() {
11224 - println!("hello");
11225 + println!("hello there");
11226
11227 println!("around the");
11228 println!("world");
11229 }
11230 "#
11231 .unindent(),
11232 );
11233
11234 cx.update_editor(|editor, cx| {
11235 for _ in 0..3 {
11236 editor.go_to_next_hunk(&GoToHunk, cx);
11237 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11238 }
11239 });
11240 executor.run_until_parked();
11241 cx.assert_editor_state(
11242 &r#"
11243 use some::modified;
11244
11245 ˇ
11246 fn main() {
11247 println!("hello there");
11248
11249 println!("around the");
11250 println!("world");
11251 }
11252 "#
11253 .unindent(),
11254 );
11255
11256 cx.assert_diff_hunks(
11257 r#"
11258 - use some::mod;
11259 + use some::modified;
11260
11261 - const A: u32 = 42;
11262
11263 fn main() {
11264 - println!("hello");
11265 + println!("hello there");
11266
11267 + println!("around the");
11268 println!("world");
11269 }
11270 "#
11271 .unindent(),
11272 );
11273
11274 cx.update_editor(|editor, cx| {
11275 editor.cancel(&Cancel, cx);
11276 });
11277
11278 cx.assert_diff_hunks(
11279 r#"
11280 use some::modified;
11281
11282
11283 fn main() {
11284 println!("hello there");
11285
11286 println!("around the");
11287 println!("world");
11288 }
11289 "#
11290 .unindent(),
11291 );
11292}
11293
11294#[gpui::test]
11295async fn test_diff_base_change_with_expanded_diff_hunks(
11296 executor: BackgroundExecutor,
11297 cx: &mut gpui::TestAppContext,
11298) {
11299 init_test(cx, |_| {});
11300
11301 let mut cx = EditorTestContext::new(cx).await;
11302
11303 let diff_base = r#"
11304 use some::mod1;
11305 use some::mod2;
11306
11307 const A: u32 = 42;
11308 const B: u32 = 42;
11309 const C: u32 = 42;
11310
11311 fn main() {
11312 println!("hello");
11313
11314 println!("world");
11315 }
11316 "#
11317 .unindent();
11318
11319 cx.set_state(
11320 &r#"
11321 use some::mod2;
11322
11323 const A: u32 = 42;
11324 const C: u32 = 42;
11325
11326 fn main(ˇ) {
11327 //println!("hello");
11328
11329 println!("world");
11330 //
11331 //
11332 }
11333 "#
11334 .unindent(),
11335 );
11336
11337 cx.set_diff_base(Some(&diff_base));
11338 executor.run_until_parked();
11339
11340 cx.update_editor(|editor, cx| {
11341 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11342 });
11343 executor.run_until_parked();
11344 cx.assert_diff_hunks(
11345 r#"
11346 - use some::mod1;
11347 use some::mod2;
11348
11349 const A: u32 = 42;
11350 - const B: u32 = 42;
11351 const C: u32 = 42;
11352
11353 fn main() {
11354 - println!("hello");
11355 + //println!("hello");
11356
11357 println!("world");
11358 + //
11359 + //
11360 }
11361 "#
11362 .unindent(),
11363 );
11364
11365 cx.set_diff_base(Some("new diff base!"));
11366 executor.run_until_parked();
11367 cx.assert_diff_hunks(
11368 r#"
11369 use some::mod2;
11370
11371 const A: u32 = 42;
11372 const C: u32 = 42;
11373
11374 fn main() {
11375 //println!("hello");
11376
11377 println!("world");
11378 //
11379 //
11380 }
11381 "#
11382 .unindent(),
11383 );
11384
11385 cx.update_editor(|editor, cx| {
11386 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11387 });
11388 executor.run_until_parked();
11389 cx.assert_diff_hunks(
11390 r#"
11391 - new diff base!
11392 + use some::mod2;
11393 +
11394 + const A: u32 = 42;
11395 + const C: u32 = 42;
11396 +
11397 + fn main() {
11398 + //println!("hello");
11399 +
11400 + println!("world");
11401 + //
11402 + //
11403 + }
11404 "#
11405 .unindent(),
11406 );
11407}
11408
11409#[gpui::test]
11410async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11411 init_test(cx, |_| {});
11412
11413 let mut cx = EditorTestContext::new(cx).await;
11414
11415 let diff_base = r#"
11416 use some::mod1;
11417 use some::mod2;
11418
11419 const A: u32 = 42;
11420 const B: u32 = 42;
11421 const C: u32 = 42;
11422
11423 fn main() {
11424 println!("hello");
11425
11426 println!("world");
11427 }
11428
11429 fn another() {
11430 println!("another");
11431 }
11432
11433 fn another2() {
11434 println!("another2");
11435 }
11436 "#
11437 .unindent();
11438
11439 cx.set_state(
11440 &r#"
11441 «use some::mod2;
11442
11443 const A: u32 = 42;
11444 const C: u32 = 42;
11445
11446 fn main() {
11447 //println!("hello");
11448
11449 println!("world");
11450 //
11451 //ˇ»
11452 }
11453
11454 fn another() {
11455 println!("another");
11456 println!("another");
11457 }
11458
11459 println!("another2");
11460 }
11461 "#
11462 .unindent(),
11463 );
11464
11465 cx.set_diff_base(Some(&diff_base));
11466 executor.run_until_parked();
11467
11468 cx.update_editor(|editor, cx| {
11469 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11470 });
11471 executor.run_until_parked();
11472
11473 cx.assert_diff_hunks(
11474 r#"
11475 - use some::mod1;
11476 use some::mod2;
11477
11478 const A: u32 = 42;
11479 - const B: u32 = 42;
11480 const C: u32 = 42;
11481
11482 fn main() {
11483 - println!("hello");
11484 + //println!("hello");
11485
11486 println!("world");
11487 + //
11488 + //
11489 }
11490
11491 fn another() {
11492 println!("another");
11493 + println!("another");
11494 }
11495
11496 - fn another2() {
11497 println!("another2");
11498 }
11499 "#
11500 .unindent(),
11501 );
11502
11503 // Fold across some of the diff hunks. They should no longer appear expanded.
11504 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11505 cx.executor().run_until_parked();
11506
11507 // Hunks are not shown if their position is within a fold
11508 cx.assert_diff_hunks(
11509 r#"
11510 use some::mod2;
11511
11512 const A: u32 = 42;
11513 const C: u32 = 42;
11514
11515 fn main() {
11516 //println!("hello");
11517
11518 println!("world");
11519 //
11520 //
11521 }
11522
11523 fn another() {
11524 println!("another");
11525 + println!("another");
11526 }
11527
11528 - fn another2() {
11529 println!("another2");
11530 }
11531 "#
11532 .unindent(),
11533 );
11534
11535 cx.update_editor(|editor, cx| {
11536 editor.select_all(&SelectAll, cx);
11537 editor.unfold_lines(&UnfoldLines, cx);
11538 });
11539 cx.executor().run_until_parked();
11540
11541 // The deletions reappear when unfolding.
11542 cx.assert_diff_hunks(
11543 r#"
11544 - use some::mod1;
11545 use some::mod2;
11546
11547 const A: u32 = 42;
11548 - const B: u32 = 42;
11549 const C: u32 = 42;
11550
11551 fn main() {
11552 - println!("hello");
11553 + //println!("hello");
11554
11555 println!("world");
11556 + //
11557 + //
11558 }
11559
11560 fn another() {
11561 println!("another");
11562 + println!("another");
11563 }
11564
11565 - fn another2() {
11566 println!("another2");
11567 }
11568 "#
11569 .unindent(),
11570 );
11571}
11572
11573#[gpui::test]
11574async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11575 init_test(cx, |_| {});
11576
11577 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11578 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11579 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11580 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11581 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11582 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11583
11584 let buffer_1 = cx.new_model(|cx| {
11585 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11586 buffer.set_diff_base(Some(file_1_old.into()), cx);
11587 buffer
11588 });
11589 let buffer_2 = cx.new_model(|cx| {
11590 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11591 buffer.set_diff_base(Some(file_2_old.into()), cx);
11592 buffer
11593 });
11594 let buffer_3 = cx.new_model(|cx| {
11595 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11596 buffer.set_diff_base(Some(file_3_old.into()), cx);
11597 buffer
11598 });
11599
11600 let multi_buffer = cx.new_model(|cx| {
11601 let mut multibuffer = MultiBuffer::new(ReadWrite);
11602 multibuffer.push_excerpts(
11603 buffer_1.clone(),
11604 [
11605 ExcerptRange {
11606 context: Point::new(0, 0)..Point::new(3, 0),
11607 primary: None,
11608 },
11609 ExcerptRange {
11610 context: Point::new(5, 0)..Point::new(7, 0),
11611 primary: None,
11612 },
11613 ExcerptRange {
11614 context: Point::new(9, 0)..Point::new(10, 3),
11615 primary: None,
11616 },
11617 ],
11618 cx,
11619 );
11620 multibuffer.push_excerpts(
11621 buffer_2.clone(),
11622 [
11623 ExcerptRange {
11624 context: Point::new(0, 0)..Point::new(3, 0),
11625 primary: None,
11626 },
11627 ExcerptRange {
11628 context: Point::new(5, 0)..Point::new(7, 0),
11629 primary: None,
11630 },
11631 ExcerptRange {
11632 context: Point::new(9, 0)..Point::new(10, 3),
11633 primary: None,
11634 },
11635 ],
11636 cx,
11637 );
11638 multibuffer.push_excerpts(
11639 buffer_3.clone(),
11640 [
11641 ExcerptRange {
11642 context: Point::new(0, 0)..Point::new(3, 0),
11643 primary: None,
11644 },
11645 ExcerptRange {
11646 context: Point::new(5, 0)..Point::new(7, 0),
11647 primary: None,
11648 },
11649 ExcerptRange {
11650 context: Point::new(9, 0)..Point::new(10, 3),
11651 primary: None,
11652 },
11653 ],
11654 cx,
11655 );
11656 multibuffer
11657 });
11658
11659 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11660 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11661 cx.run_until_parked();
11662
11663 cx.assert_editor_state(
11664 &"
11665 ˇaaa
11666 ccc
11667 ddd
11668
11669 ggg
11670 hhh
11671
11672
11673 lll
11674 mmm
11675 NNN
11676
11677 qqq
11678 rrr
11679
11680 uuu
11681 111
11682 222
11683 333
11684
11685 666
11686 777
11687
11688 000
11689 !!!"
11690 .unindent(),
11691 );
11692
11693 cx.update_editor(|editor, cx| {
11694 editor.select_all(&SelectAll, cx);
11695 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11696 });
11697 cx.executor().run_until_parked();
11698
11699 cx.assert_diff_hunks(
11700 "
11701 aaa
11702 - bbb
11703 ccc
11704 ddd
11705
11706 ggg
11707 hhh
11708
11709
11710 lll
11711 mmm
11712 - nnn
11713 + NNN
11714
11715 qqq
11716 rrr
11717
11718 uuu
11719 111
11720 222
11721 333
11722
11723 + 666
11724 777
11725
11726 000
11727 !!!"
11728 .unindent(),
11729 );
11730}
11731
11732#[gpui::test]
11733async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
11734 init_test(cx, |_| {});
11735
11736 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
11737 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
11738
11739 let buffer = cx.new_model(|cx| {
11740 let mut buffer = Buffer::local(text.to_string(), cx);
11741 buffer.set_diff_base(Some(base.into()), cx);
11742 buffer
11743 });
11744
11745 let multi_buffer = cx.new_model(|cx| {
11746 let mut multibuffer = MultiBuffer::new(ReadWrite);
11747 multibuffer.push_excerpts(
11748 buffer.clone(),
11749 [
11750 ExcerptRange {
11751 context: Point::new(0, 0)..Point::new(2, 0),
11752 primary: None,
11753 },
11754 ExcerptRange {
11755 context: Point::new(5, 0)..Point::new(7, 0),
11756 primary: None,
11757 },
11758 ],
11759 cx,
11760 );
11761 multibuffer
11762 });
11763
11764 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11765 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11766 cx.run_until_parked();
11767
11768 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
11769 cx.executor().run_until_parked();
11770
11771 cx.assert_diff_hunks(
11772 "
11773 aaa
11774 - bbb
11775 + BBB
11776
11777 - ddd
11778 - eee
11779 + EEE
11780 fff
11781 "
11782 .unindent(),
11783 );
11784}
11785
11786#[gpui::test]
11787async fn test_edits_around_expanded_insertion_hunks(
11788 executor: BackgroundExecutor,
11789 cx: &mut gpui::TestAppContext,
11790) {
11791 init_test(cx, |_| {});
11792
11793 let mut cx = EditorTestContext::new(cx).await;
11794
11795 let diff_base = r#"
11796 use some::mod1;
11797 use some::mod2;
11798
11799 const A: u32 = 42;
11800
11801 fn main() {
11802 println!("hello");
11803
11804 println!("world");
11805 }
11806 "#
11807 .unindent();
11808 executor.run_until_parked();
11809 cx.set_state(
11810 &r#"
11811 use some::mod1;
11812 use some::mod2;
11813
11814 const A: u32 = 42;
11815 const B: u32 = 42;
11816 const C: u32 = 42;
11817 ˇ
11818
11819 fn main() {
11820 println!("hello");
11821
11822 println!("world");
11823 }
11824 "#
11825 .unindent(),
11826 );
11827
11828 cx.set_diff_base(Some(&diff_base));
11829 executor.run_until_parked();
11830
11831 cx.update_editor(|editor, cx| {
11832 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11833 });
11834 executor.run_until_parked();
11835
11836 cx.assert_diff_hunks(
11837 r#"
11838 use some::mod1;
11839 use some::mod2;
11840
11841 const A: u32 = 42;
11842 + const B: u32 = 42;
11843 + const C: u32 = 42;
11844 +
11845
11846 fn main() {
11847 println!("hello");
11848
11849 println!("world");
11850 }
11851 "#
11852 .unindent(),
11853 );
11854
11855 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11856 executor.run_until_parked();
11857
11858 cx.assert_diff_hunks(
11859 r#"
11860 use some::mod1;
11861 use some::mod2;
11862
11863 const A: u32 = 42;
11864 + const B: u32 = 42;
11865 + const C: u32 = 42;
11866 + const D: u32 = 42;
11867 +
11868
11869 fn main() {
11870 println!("hello");
11871
11872 println!("world");
11873 }
11874 "#
11875 .unindent(),
11876 );
11877
11878 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11879 executor.run_until_parked();
11880
11881 cx.assert_diff_hunks(
11882 r#"
11883 use some::mod1;
11884 use some::mod2;
11885
11886 const A: u32 = 42;
11887 + const B: u32 = 42;
11888 + const C: u32 = 42;
11889 + const D: u32 = 42;
11890 + const E: u32 = 42;
11891 +
11892
11893 fn main() {
11894 println!("hello");
11895
11896 println!("world");
11897 }
11898 "#
11899 .unindent(),
11900 );
11901
11902 cx.update_editor(|editor, cx| {
11903 editor.delete_line(&DeleteLine, cx);
11904 });
11905 executor.run_until_parked();
11906
11907 cx.assert_diff_hunks(
11908 r#"
11909 use some::mod1;
11910 use some::mod2;
11911
11912 const A: u32 = 42;
11913 + const B: u32 = 42;
11914 + const C: u32 = 42;
11915 + const D: u32 = 42;
11916 + const E: u32 = 42;
11917
11918 fn main() {
11919 println!("hello");
11920
11921 println!("world");
11922 }
11923 "#
11924 .unindent(),
11925 );
11926
11927 cx.update_editor(|editor, cx| {
11928 editor.move_up(&MoveUp, cx);
11929 editor.delete_line(&DeleteLine, cx);
11930 editor.move_up(&MoveUp, cx);
11931 editor.delete_line(&DeleteLine, cx);
11932 editor.move_up(&MoveUp, cx);
11933 editor.delete_line(&DeleteLine, cx);
11934 });
11935 executor.run_until_parked();
11936 cx.assert_editor_state(
11937 &r#"
11938 use some::mod1;
11939 use some::mod2;
11940
11941 const A: u32 = 42;
11942 const B: u32 = 42;
11943 ˇ
11944 fn main() {
11945 println!("hello");
11946
11947 println!("world");
11948 }
11949 "#
11950 .unindent(),
11951 );
11952
11953 cx.assert_diff_hunks(
11954 r#"
11955 use some::mod1;
11956 use some::mod2;
11957
11958 const A: u32 = 42;
11959 + const B: u32 = 42;
11960
11961 fn main() {
11962 println!("hello");
11963
11964 println!("world");
11965 }
11966 "#
11967 .unindent(),
11968 );
11969
11970 cx.update_editor(|editor, cx| {
11971 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11972 editor.delete_line(&DeleteLine, cx);
11973 });
11974 executor.run_until_parked();
11975 cx.assert_diff_hunks(
11976 r#"
11977 use some::mod1;
11978 - use some::mod2;
11979 -
11980 - const A: u32 = 42;
11981
11982 fn main() {
11983 println!("hello");
11984
11985 println!("world");
11986 }
11987 "#
11988 .unindent(),
11989 );
11990}
11991
11992#[gpui::test]
11993async fn test_edits_around_expanded_deletion_hunks(
11994 executor: BackgroundExecutor,
11995 cx: &mut gpui::TestAppContext,
11996) {
11997 init_test(cx, |_| {});
11998
11999 let mut cx = EditorTestContext::new(cx).await;
12000
12001 let diff_base = r#"
12002 use some::mod1;
12003 use some::mod2;
12004
12005 const A: u32 = 42;
12006 const B: u32 = 42;
12007 const C: u32 = 42;
12008
12009
12010 fn main() {
12011 println!("hello");
12012
12013 println!("world");
12014 }
12015 "#
12016 .unindent();
12017 executor.run_until_parked();
12018 cx.set_state(
12019 &r#"
12020 use some::mod1;
12021 use some::mod2;
12022
12023 ˇconst B: u32 = 42;
12024 const C: u32 = 42;
12025
12026
12027 fn main() {
12028 println!("hello");
12029
12030 println!("world");
12031 }
12032 "#
12033 .unindent(),
12034 );
12035
12036 cx.set_diff_base(Some(&diff_base));
12037 executor.run_until_parked();
12038
12039 cx.update_editor(|editor, cx| {
12040 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12041 });
12042 executor.run_until_parked();
12043
12044 cx.assert_diff_hunks(
12045 r#"
12046 use some::mod1;
12047 use some::mod2;
12048
12049 - const A: u32 = 42;
12050 const B: u32 = 42;
12051 const C: u32 = 42;
12052
12053
12054 fn main() {
12055 println!("hello");
12056
12057 println!("world");
12058 }
12059 "#
12060 .unindent(),
12061 );
12062
12063 cx.update_editor(|editor, cx| {
12064 editor.delete_line(&DeleteLine, cx);
12065 });
12066 executor.run_until_parked();
12067 cx.assert_editor_state(
12068 &r#"
12069 use some::mod1;
12070 use some::mod2;
12071
12072 ˇconst C: u32 = 42;
12073
12074
12075 fn main() {
12076 println!("hello");
12077
12078 println!("world");
12079 }
12080 "#
12081 .unindent(),
12082 );
12083 cx.assert_diff_hunks(
12084 r#"
12085 use some::mod1;
12086 use some::mod2;
12087
12088 - const A: u32 = 42;
12089 - const B: u32 = 42;
12090 const C: u32 = 42;
12091
12092
12093 fn main() {
12094 println!("hello");
12095
12096 println!("world");
12097 }
12098 "#
12099 .unindent(),
12100 );
12101
12102 cx.update_editor(|editor, cx| {
12103 editor.delete_line(&DeleteLine, cx);
12104 });
12105 executor.run_until_parked();
12106 cx.assert_editor_state(
12107 &r#"
12108 use some::mod1;
12109 use some::mod2;
12110
12111 ˇ
12112
12113 fn main() {
12114 println!("hello");
12115
12116 println!("world");
12117 }
12118 "#
12119 .unindent(),
12120 );
12121 cx.assert_diff_hunks(
12122 r#"
12123 use some::mod1;
12124 use some::mod2;
12125
12126 - const A: u32 = 42;
12127 - const B: u32 = 42;
12128 - const C: u32 = 42;
12129
12130
12131 fn main() {
12132 println!("hello");
12133
12134 println!("world");
12135 }
12136 "#
12137 .unindent(),
12138 );
12139
12140 cx.update_editor(|editor, cx| {
12141 editor.handle_input("replacement", cx);
12142 });
12143 executor.run_until_parked();
12144 cx.assert_editor_state(
12145 &r#"
12146 use some::mod1;
12147 use some::mod2;
12148
12149 replacementˇ
12150
12151 fn main() {
12152 println!("hello");
12153
12154 println!("world");
12155 }
12156 "#
12157 .unindent(),
12158 );
12159 cx.assert_diff_hunks(
12160 r#"
12161 use some::mod1;
12162 use some::mod2;
12163
12164 - const A: u32 = 42;
12165 - const B: u32 = 42;
12166 - const C: u32 = 42;
12167 -
12168 + replacement
12169
12170 fn main() {
12171 println!("hello");
12172
12173 println!("world");
12174 }
12175 "#
12176 .unindent(),
12177 );
12178}
12179
12180#[gpui::test]
12181async fn test_edit_after_expanded_modification_hunk(
12182 executor: BackgroundExecutor,
12183 cx: &mut gpui::TestAppContext,
12184) {
12185 init_test(cx, |_| {});
12186
12187 let mut cx = EditorTestContext::new(cx).await;
12188
12189 let diff_base = r#"
12190 use some::mod1;
12191 use some::mod2;
12192
12193 const A: u32 = 42;
12194 const B: u32 = 42;
12195 const C: u32 = 42;
12196 const D: u32 = 42;
12197
12198
12199 fn main() {
12200 println!("hello");
12201
12202 println!("world");
12203 }"#
12204 .unindent();
12205
12206 cx.set_state(
12207 &r#"
12208 use some::mod1;
12209 use some::mod2;
12210
12211 const A: u32 = 42;
12212 const B: u32 = 42;
12213 const C: u32 = 43ˇ
12214 const D: u32 = 42;
12215
12216
12217 fn main() {
12218 println!("hello");
12219
12220 println!("world");
12221 }"#
12222 .unindent(),
12223 );
12224
12225 cx.set_diff_base(Some(&diff_base));
12226 executor.run_until_parked();
12227 cx.update_editor(|editor, cx| {
12228 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12229 });
12230 executor.run_until_parked();
12231
12232 cx.assert_diff_hunks(
12233 r#"
12234 use some::mod1;
12235 use some::mod2;
12236
12237 const A: u32 = 42;
12238 const B: u32 = 42;
12239 - const C: u32 = 42;
12240 + const C: u32 = 43
12241 const D: u32 = 42;
12242
12243
12244 fn main() {
12245 println!("hello");
12246
12247 println!("world");
12248 }"#
12249 .unindent(),
12250 );
12251
12252 cx.update_editor(|editor, cx| {
12253 editor.handle_input("\nnew_line\n", cx);
12254 });
12255 executor.run_until_parked();
12256
12257 cx.assert_diff_hunks(
12258 r#"
12259 use some::mod1;
12260 use some::mod2;
12261
12262 const A: u32 = 42;
12263 const B: u32 = 42;
12264 - const C: u32 = 42;
12265 + const C: u32 = 43
12266 + new_line
12267 +
12268 const D: u32 = 42;
12269
12270
12271 fn main() {
12272 println!("hello");
12273
12274 println!("world");
12275 }"#
12276 .unindent(),
12277 );
12278}
12279
12280async fn setup_indent_guides_editor(
12281 text: &str,
12282 cx: &mut gpui::TestAppContext,
12283) -> (BufferId, EditorTestContext) {
12284 init_test(cx, |_| {});
12285
12286 let mut cx = EditorTestContext::new(cx).await;
12287
12288 let buffer_id = cx.update_editor(|editor, cx| {
12289 editor.set_text(text, cx);
12290 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12291
12292 buffer_ids[0]
12293 });
12294
12295 (buffer_id, cx)
12296}
12297
12298fn assert_indent_guides(
12299 range: Range<u32>,
12300 expected: Vec<IndentGuide>,
12301 active_indices: Option<Vec<usize>>,
12302 cx: &mut EditorTestContext,
12303) {
12304 let indent_guides = cx.update_editor(|editor, cx| {
12305 let snapshot = editor.snapshot(cx).display_snapshot;
12306 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12307 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12308 true,
12309 &snapshot,
12310 cx,
12311 );
12312
12313 indent_guides.sort_by(|a, b| {
12314 a.depth.cmp(&b.depth).then(
12315 a.start_row
12316 .cmp(&b.start_row)
12317 .then(a.end_row.cmp(&b.end_row)),
12318 )
12319 });
12320 indent_guides
12321 });
12322
12323 if let Some(expected) = active_indices {
12324 let active_indices = cx.update_editor(|editor, cx| {
12325 let snapshot = editor.snapshot(cx).display_snapshot;
12326 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12327 });
12328
12329 assert_eq!(
12330 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12331 expected,
12332 "Active indent guide indices do not match"
12333 );
12334 }
12335
12336 let expected: Vec<_> = expected
12337 .into_iter()
12338 .map(|guide| MultiBufferIndentGuide {
12339 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12340 buffer: guide,
12341 })
12342 .collect();
12343
12344 assert_eq!(indent_guides, expected, "Indent guides do not match");
12345}
12346
12347fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12348 IndentGuide {
12349 buffer_id,
12350 start_row,
12351 end_row,
12352 depth,
12353 tab_size: 4,
12354 settings: IndentGuideSettings {
12355 enabled: true,
12356 line_width: 1,
12357 active_line_width: 1,
12358 ..Default::default()
12359 },
12360 }
12361}
12362
12363#[gpui::test]
12364async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12365 let (buffer_id, mut cx) = setup_indent_guides_editor(
12366 &"
12367 fn main() {
12368 let a = 1;
12369 }"
12370 .unindent(),
12371 cx,
12372 )
12373 .await;
12374
12375 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12376}
12377
12378#[gpui::test]
12379async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12380 let (buffer_id, mut cx) = setup_indent_guides_editor(
12381 &"
12382 fn main() {
12383 let a = 1;
12384 let b = 2;
12385 }"
12386 .unindent(),
12387 cx,
12388 )
12389 .await;
12390
12391 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12392}
12393
12394#[gpui::test]
12395async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12396 let (buffer_id, mut cx) = setup_indent_guides_editor(
12397 &"
12398 fn main() {
12399 let a = 1;
12400 if a == 3 {
12401 let b = 2;
12402 } else {
12403 let c = 3;
12404 }
12405 }"
12406 .unindent(),
12407 cx,
12408 )
12409 .await;
12410
12411 assert_indent_guides(
12412 0..8,
12413 vec![
12414 indent_guide(buffer_id, 1, 6, 0),
12415 indent_guide(buffer_id, 3, 3, 1),
12416 indent_guide(buffer_id, 5, 5, 1),
12417 ],
12418 None,
12419 &mut cx,
12420 );
12421}
12422
12423#[gpui::test]
12424async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12425 let (buffer_id, mut cx) = setup_indent_guides_editor(
12426 &"
12427 fn main() {
12428 let a = 1;
12429 let b = 2;
12430 let c = 3;
12431 }"
12432 .unindent(),
12433 cx,
12434 )
12435 .await;
12436
12437 assert_indent_guides(
12438 0..5,
12439 vec![
12440 indent_guide(buffer_id, 1, 3, 0),
12441 indent_guide(buffer_id, 2, 2, 1),
12442 ],
12443 None,
12444 &mut cx,
12445 );
12446}
12447
12448#[gpui::test]
12449async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12450 let (buffer_id, mut cx) = setup_indent_guides_editor(
12451 &"
12452 fn main() {
12453 let a = 1;
12454
12455 let c = 3;
12456 }"
12457 .unindent(),
12458 cx,
12459 )
12460 .await;
12461
12462 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12463}
12464
12465#[gpui::test]
12466async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12467 let (buffer_id, mut cx) = setup_indent_guides_editor(
12468 &"
12469 fn main() {
12470 let a = 1;
12471
12472 let c = 3;
12473
12474 if a == 3 {
12475 let b = 2;
12476 } else {
12477 let c = 3;
12478 }
12479 }"
12480 .unindent(),
12481 cx,
12482 )
12483 .await;
12484
12485 assert_indent_guides(
12486 0..11,
12487 vec![
12488 indent_guide(buffer_id, 1, 9, 0),
12489 indent_guide(buffer_id, 6, 6, 1),
12490 indent_guide(buffer_id, 8, 8, 1),
12491 ],
12492 None,
12493 &mut cx,
12494 );
12495}
12496
12497#[gpui::test]
12498async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12499 let (buffer_id, mut cx) = setup_indent_guides_editor(
12500 &"
12501 fn main() {
12502 let a = 1;
12503
12504 let c = 3;
12505
12506 if a == 3 {
12507 let b = 2;
12508 } else {
12509 let c = 3;
12510 }
12511 }"
12512 .unindent(),
12513 cx,
12514 )
12515 .await;
12516
12517 assert_indent_guides(
12518 1..11,
12519 vec![
12520 indent_guide(buffer_id, 1, 9, 0),
12521 indent_guide(buffer_id, 6, 6, 1),
12522 indent_guide(buffer_id, 8, 8, 1),
12523 ],
12524 None,
12525 &mut cx,
12526 );
12527}
12528
12529#[gpui::test]
12530async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12531 let (buffer_id, mut cx) = setup_indent_guides_editor(
12532 &"
12533 fn main() {
12534 let a = 1;
12535
12536 let c = 3;
12537
12538 if a == 3 {
12539 let b = 2;
12540 } else {
12541 let c = 3;
12542 }
12543 }"
12544 .unindent(),
12545 cx,
12546 )
12547 .await;
12548
12549 assert_indent_guides(
12550 1..10,
12551 vec![
12552 indent_guide(buffer_id, 1, 9, 0),
12553 indent_guide(buffer_id, 6, 6, 1),
12554 indent_guide(buffer_id, 8, 8, 1),
12555 ],
12556 None,
12557 &mut cx,
12558 );
12559}
12560
12561#[gpui::test]
12562async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12563 let (buffer_id, mut cx) = setup_indent_guides_editor(
12564 &"
12565 block1
12566 block2
12567 block3
12568 block4
12569 block2
12570 block1
12571 block1"
12572 .unindent(),
12573 cx,
12574 )
12575 .await;
12576
12577 assert_indent_guides(
12578 1..10,
12579 vec![
12580 indent_guide(buffer_id, 1, 4, 0),
12581 indent_guide(buffer_id, 2, 3, 1),
12582 indent_guide(buffer_id, 3, 3, 2),
12583 ],
12584 None,
12585 &mut cx,
12586 );
12587}
12588
12589#[gpui::test]
12590async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12591 let (buffer_id, mut cx) = setup_indent_guides_editor(
12592 &"
12593 block1
12594 block2
12595 block3
12596
12597 block1
12598 block1"
12599 .unindent(),
12600 cx,
12601 )
12602 .await;
12603
12604 assert_indent_guides(
12605 0..6,
12606 vec![
12607 indent_guide(buffer_id, 1, 2, 0),
12608 indent_guide(buffer_id, 2, 2, 1),
12609 ],
12610 None,
12611 &mut cx,
12612 );
12613}
12614
12615#[gpui::test]
12616async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12617 let (buffer_id, mut cx) = setup_indent_guides_editor(
12618 &"
12619 block1
12620
12621
12622
12623 block2
12624 "
12625 .unindent(),
12626 cx,
12627 )
12628 .await;
12629
12630 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12631}
12632
12633#[gpui::test]
12634async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12635 let (buffer_id, mut cx) = setup_indent_guides_editor(
12636 &"
12637 def a:
12638 \tb = 3
12639 \tif True:
12640 \t\tc = 4
12641 \t\td = 5
12642 \tprint(b)
12643 "
12644 .unindent(),
12645 cx,
12646 )
12647 .await;
12648
12649 assert_indent_guides(
12650 0..6,
12651 vec![
12652 indent_guide(buffer_id, 1, 6, 0),
12653 indent_guide(buffer_id, 3, 4, 1),
12654 ],
12655 None,
12656 &mut cx,
12657 );
12658}
12659
12660#[gpui::test]
12661async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12662 let (buffer_id, mut cx) = setup_indent_guides_editor(
12663 &"
12664 fn main() {
12665 let a = 1;
12666 }"
12667 .unindent(),
12668 cx,
12669 )
12670 .await;
12671
12672 cx.update_editor(|editor, cx| {
12673 editor.change_selections(None, cx, |s| {
12674 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12675 });
12676 });
12677
12678 assert_indent_guides(
12679 0..3,
12680 vec![indent_guide(buffer_id, 1, 1, 0)],
12681 Some(vec![0]),
12682 &mut cx,
12683 );
12684}
12685
12686#[gpui::test]
12687async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12688 let (buffer_id, mut cx) = setup_indent_guides_editor(
12689 &"
12690 fn main() {
12691 if 1 == 2 {
12692 let a = 1;
12693 }
12694 }"
12695 .unindent(),
12696 cx,
12697 )
12698 .await;
12699
12700 cx.update_editor(|editor, cx| {
12701 editor.change_selections(None, cx, |s| {
12702 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12703 });
12704 });
12705
12706 assert_indent_guides(
12707 0..4,
12708 vec![
12709 indent_guide(buffer_id, 1, 3, 0),
12710 indent_guide(buffer_id, 2, 2, 1),
12711 ],
12712 Some(vec![1]),
12713 &mut cx,
12714 );
12715
12716 cx.update_editor(|editor, cx| {
12717 editor.change_selections(None, cx, |s| {
12718 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12719 });
12720 });
12721
12722 assert_indent_guides(
12723 0..4,
12724 vec![
12725 indent_guide(buffer_id, 1, 3, 0),
12726 indent_guide(buffer_id, 2, 2, 1),
12727 ],
12728 Some(vec![1]),
12729 &mut cx,
12730 );
12731
12732 cx.update_editor(|editor, cx| {
12733 editor.change_selections(None, cx, |s| {
12734 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12735 });
12736 });
12737
12738 assert_indent_guides(
12739 0..4,
12740 vec![
12741 indent_guide(buffer_id, 1, 3, 0),
12742 indent_guide(buffer_id, 2, 2, 1),
12743 ],
12744 Some(vec![0]),
12745 &mut cx,
12746 );
12747}
12748
12749#[gpui::test]
12750async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12751 let (buffer_id, mut cx) = setup_indent_guides_editor(
12752 &"
12753 fn main() {
12754 let a = 1;
12755
12756 let b = 2;
12757 }"
12758 .unindent(),
12759 cx,
12760 )
12761 .await;
12762
12763 cx.update_editor(|editor, cx| {
12764 editor.change_selections(None, cx, |s| {
12765 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12766 });
12767 });
12768
12769 assert_indent_guides(
12770 0..5,
12771 vec![indent_guide(buffer_id, 1, 3, 0)],
12772 Some(vec![0]),
12773 &mut cx,
12774 );
12775}
12776
12777#[gpui::test]
12778async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12779 let (buffer_id, mut cx) = setup_indent_guides_editor(
12780 &"
12781 def m:
12782 a = 1
12783 pass"
12784 .unindent(),
12785 cx,
12786 )
12787 .await;
12788
12789 cx.update_editor(|editor, cx| {
12790 editor.change_selections(None, cx, |s| {
12791 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12792 });
12793 });
12794
12795 assert_indent_guides(
12796 0..3,
12797 vec![indent_guide(buffer_id, 1, 2, 0)],
12798 Some(vec![0]),
12799 &mut cx,
12800 );
12801}
12802
12803#[gpui::test]
12804fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12805 init_test(cx, |_| {});
12806
12807 let editor = cx.add_window(|cx| {
12808 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12809 build_editor(buffer, cx)
12810 });
12811
12812 let render_args = Arc::new(Mutex::new(None));
12813 let snapshot = editor
12814 .update(cx, |editor, cx| {
12815 let snapshot = editor.buffer().read(cx).snapshot(cx);
12816 let range =
12817 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12818
12819 struct RenderArgs {
12820 row: MultiBufferRow,
12821 folded: bool,
12822 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12823 }
12824
12825 let crease = Crease::new(
12826 range,
12827 FoldPlaceholder::test(),
12828 {
12829 let toggle_callback = render_args.clone();
12830 move |row, folded, callback, _cx| {
12831 *toggle_callback.lock() = Some(RenderArgs {
12832 row,
12833 folded,
12834 callback,
12835 });
12836 div()
12837 }
12838 },
12839 |_row, _folded, _cx| div(),
12840 );
12841
12842 editor.insert_creases(Some(crease), cx);
12843 let snapshot = editor.snapshot(cx);
12844 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12845 snapshot
12846 })
12847 .unwrap();
12848
12849 let render_args = render_args.lock().take().unwrap();
12850 assert_eq!(render_args.row, MultiBufferRow(1));
12851 assert!(!render_args.folded);
12852 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12853
12854 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12855 .unwrap();
12856 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12857 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12858
12859 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12860 .unwrap();
12861 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12862 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12863}
12864
12865#[gpui::test]
12866async fn test_input_text(cx: &mut gpui::TestAppContext) {
12867 init_test(cx, |_| {});
12868 let mut cx = EditorTestContext::new(cx).await;
12869
12870 cx.set_state(
12871 &r#"ˇone
12872 two
12873
12874 three
12875 fourˇ
12876 five
12877
12878 siˇx"#
12879 .unindent(),
12880 );
12881
12882 cx.dispatch_action(HandleInput(String::new()));
12883 cx.assert_editor_state(
12884 &r#"ˇone
12885 two
12886
12887 three
12888 fourˇ
12889 five
12890
12891 siˇx"#
12892 .unindent(),
12893 );
12894
12895 cx.dispatch_action(HandleInput("AAAA".to_string()));
12896 cx.assert_editor_state(
12897 &r#"AAAAˇone
12898 two
12899
12900 three
12901 fourAAAAˇ
12902 five
12903
12904 siAAAAˇx"#
12905 .unindent(),
12906 );
12907}
12908
12909#[gpui::test]
12910async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
12911 init_test(cx, |_| {});
12912
12913 let mut cx = EditorTestContext::new(cx).await;
12914 cx.set_state(
12915 r#"let foo = 1;
12916let foo = 2;
12917let foo = 3;
12918let fooˇ = 4;
12919let foo = 5;
12920let foo = 6;
12921let foo = 7;
12922let foo = 8;
12923let foo = 9;
12924let foo = 10;
12925let foo = 11;
12926let foo = 12;
12927let foo = 13;
12928let foo = 14;
12929let foo = 15;"#,
12930 );
12931
12932 cx.update_editor(|e, cx| {
12933 assert_eq!(
12934 e.next_scroll_position,
12935 NextScrollCursorCenterTopBottom::Center,
12936 "Default next scroll direction is center",
12937 );
12938
12939 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12940 assert_eq!(
12941 e.next_scroll_position,
12942 NextScrollCursorCenterTopBottom::Top,
12943 "After center, next scroll direction should be top",
12944 );
12945
12946 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12947 assert_eq!(
12948 e.next_scroll_position,
12949 NextScrollCursorCenterTopBottom::Bottom,
12950 "After top, next scroll direction should be bottom",
12951 );
12952
12953 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12954 assert_eq!(
12955 e.next_scroll_position,
12956 NextScrollCursorCenterTopBottom::Center,
12957 "After bottom, scrolling should start over",
12958 );
12959
12960 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12961 assert_eq!(
12962 e.next_scroll_position,
12963 NextScrollCursorCenterTopBottom::Top,
12964 "Scrolling continues if retriggered fast enough"
12965 );
12966 });
12967
12968 cx.executor()
12969 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
12970 cx.executor().run_until_parked();
12971 cx.update_editor(|e, _| {
12972 assert_eq!(
12973 e.next_scroll_position,
12974 NextScrollCursorCenterTopBottom::Center,
12975 "If scrolling is not triggered fast enough, it should reset"
12976 );
12977 });
12978}
12979
12980#[gpui::test]
12981async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
12982 init_test(cx, |_| {});
12983 let mut cx = EditorLspTestContext::new_rust(
12984 lsp::ServerCapabilities {
12985 definition_provider: Some(lsp::OneOf::Left(true)),
12986 references_provider: Some(lsp::OneOf::Left(true)),
12987 ..lsp::ServerCapabilities::default()
12988 },
12989 cx,
12990 )
12991 .await;
12992
12993 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
12994 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
12995 move |params, _| async move {
12996 if empty_go_to_definition {
12997 Ok(None)
12998 } else {
12999 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13000 uri: params.text_document_position_params.text_document.uri,
13001 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13002 })))
13003 }
13004 },
13005 );
13006 let references =
13007 cx.lsp
13008 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13009 Ok(Some(vec![lsp::Location {
13010 uri: params.text_document_position.text_document.uri,
13011 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13012 }]))
13013 });
13014 (go_to_definition, references)
13015 };
13016
13017 cx.set_state(
13018 &r#"fn one() {
13019 let mut a = ˇtwo();
13020 }
13021
13022 fn two() {}"#
13023 .unindent(),
13024 );
13025 set_up_lsp_handlers(false, &mut cx);
13026 let navigated = cx
13027 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13028 .await
13029 .expect("Failed to navigate to definition");
13030 assert_eq!(
13031 navigated,
13032 Navigated::Yes,
13033 "Should have navigated to definition from the GetDefinition response"
13034 );
13035 cx.assert_editor_state(
13036 &r#"fn one() {
13037 let mut a = two();
13038 }
13039
13040 fn «twoˇ»() {}"#
13041 .unindent(),
13042 );
13043
13044 let editors = cx.update_workspace(|workspace, cx| {
13045 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13046 });
13047 cx.update_editor(|_, test_editor_cx| {
13048 assert_eq!(
13049 editors.len(),
13050 1,
13051 "Initially, only one, test, editor should be open in the workspace"
13052 );
13053 assert_eq!(
13054 test_editor_cx.view(),
13055 editors.last().expect("Asserted len is 1")
13056 );
13057 });
13058
13059 set_up_lsp_handlers(true, &mut cx);
13060 let navigated = cx
13061 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13062 .await
13063 .expect("Failed to navigate to lookup references");
13064 assert_eq!(
13065 navigated,
13066 Navigated::Yes,
13067 "Should have navigated to references as a fallback after empty GoToDefinition response"
13068 );
13069 // We should not change the selections in the existing file,
13070 // if opening another milti buffer with the references
13071 cx.assert_editor_state(
13072 &r#"fn one() {
13073 let mut a = two();
13074 }
13075
13076 fn «twoˇ»() {}"#
13077 .unindent(),
13078 );
13079 let editors = cx.update_workspace(|workspace, cx| {
13080 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13081 });
13082 cx.update_editor(|_, test_editor_cx| {
13083 assert_eq!(
13084 editors.len(),
13085 2,
13086 "After falling back to references search, we open a new editor with the results"
13087 );
13088 let references_fallback_text = editors
13089 .into_iter()
13090 .find(|new_editor| new_editor != test_editor_cx.view())
13091 .expect("Should have one non-test editor now")
13092 .read(test_editor_cx)
13093 .text(test_editor_cx);
13094 assert_eq!(
13095 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13096 "Should use the range from the references response and not the GoToDefinition one"
13097 );
13098 });
13099}
13100
13101fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13102 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13103 point..point
13104}
13105
13106fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13107 let (text, ranges) = marked_text_ranges(marked_text, true);
13108 assert_eq!(view.text(cx), text);
13109 assert_eq!(
13110 view.selections.ranges(cx),
13111 ranges,
13112 "Assert selections are {}",
13113 marked_text
13114 );
13115}
13116
13117pub fn handle_signature_help_request(
13118 cx: &mut EditorLspTestContext,
13119 mocked_response: lsp::SignatureHelp,
13120) -> impl Future<Output = ()> {
13121 let mut request =
13122 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13123 let mocked_response = mocked_response.clone();
13124 async move { Ok(Some(mocked_response)) }
13125 });
13126
13127 async move {
13128 request.next().await;
13129 }
13130}
13131
13132/// Handle completion request passing a marked string specifying where the completion
13133/// should be triggered from using '|' character, what range should be replaced, and what completions
13134/// should be returned using '<' and '>' to delimit the range
13135pub fn handle_completion_request(
13136 cx: &mut EditorLspTestContext,
13137 marked_string: &str,
13138 completions: Vec<&'static str>,
13139 counter: Arc<AtomicUsize>,
13140) -> impl Future<Output = ()> {
13141 let complete_from_marker: TextRangeMarker = '|'.into();
13142 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13143 let (_, mut marked_ranges) = marked_text_ranges_by(
13144 marked_string,
13145 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13146 );
13147
13148 let complete_from_position =
13149 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13150 let replace_range =
13151 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13152
13153 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13154 let completions = completions.clone();
13155 counter.fetch_add(1, atomic::Ordering::Release);
13156 async move {
13157 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13158 assert_eq!(
13159 params.text_document_position.position,
13160 complete_from_position
13161 );
13162 Ok(Some(lsp::CompletionResponse::Array(
13163 completions
13164 .iter()
13165 .map(|completion_text| lsp::CompletionItem {
13166 label: completion_text.to_string(),
13167 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13168 range: replace_range,
13169 new_text: completion_text.to_string(),
13170 })),
13171 ..Default::default()
13172 })
13173 .collect(),
13174 )))
13175 }
13176 });
13177
13178 async move {
13179 request.next().await;
13180 }
13181}
13182
13183fn handle_resolve_completion_request(
13184 cx: &mut EditorLspTestContext,
13185 edits: Option<Vec<(&'static str, &'static str)>>,
13186) -> impl Future<Output = ()> {
13187 let edits = edits.map(|edits| {
13188 edits
13189 .iter()
13190 .map(|(marked_string, new_text)| {
13191 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13192 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13193 lsp::TextEdit::new(replace_range, new_text.to_string())
13194 })
13195 .collect::<Vec<_>>()
13196 });
13197
13198 let mut request =
13199 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13200 let edits = edits.clone();
13201 async move {
13202 Ok(lsp::CompletionItem {
13203 additional_text_edits: edits,
13204 ..Default::default()
13205 })
13206 }
13207 });
13208
13209 async move {
13210 request.next().await;
13211 }
13212}
13213
13214pub(crate) fn update_test_language_settings(
13215 cx: &mut TestAppContext,
13216 f: impl Fn(&mut AllLanguageSettingsContent),
13217) {
13218 cx.update(|cx| {
13219 SettingsStore::update_global(cx, |store, cx| {
13220 store.update_user_settings::<AllLanguageSettings>(cx, f);
13221 });
13222 });
13223}
13224
13225pub(crate) fn update_test_project_settings(
13226 cx: &mut TestAppContext,
13227 f: impl Fn(&mut ProjectSettings),
13228) {
13229 cx.update(|cx| {
13230 SettingsStore::update_global(cx, |store, cx| {
13231 store.update_user_settings::<ProjectSettings>(cx, f);
13232 });
13233 });
13234}
13235
13236pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13237 cx.update(|cx| {
13238 assets::Assets.load_test_fonts(cx);
13239 let store = SettingsStore::test(cx);
13240 cx.set_global(store);
13241 theme::init(theme::LoadThemes::JustBase, cx);
13242 release_channel::init(SemanticVersion::default(), cx);
13243 client::init_settings(cx);
13244 language::init(cx);
13245 Project::init_settings(cx);
13246 workspace::init_settings(cx);
13247 crate::init(cx);
13248 });
13249
13250 update_test_language_settings(cx, f);
13251}
13252
13253pub(crate) fn rust_lang() -> Arc<Language> {
13254 Arc::new(Language::new(
13255 LanguageConfig {
13256 name: "Rust".into(),
13257 matcher: LanguageMatcher {
13258 path_suffixes: vec!["rs".to_string()],
13259 ..Default::default()
13260 },
13261 ..Default::default()
13262 },
13263 Some(tree_sitter_rust::LANGUAGE.into()),
13264 ))
13265}
13266
13267#[track_caller]
13268fn assert_hunk_revert(
13269 not_reverted_text_with_selections: &str,
13270 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13271 expected_reverted_text_with_selections: &str,
13272 base_text: &str,
13273 cx: &mut EditorLspTestContext,
13274) {
13275 cx.set_state(not_reverted_text_with_selections);
13276 cx.update_editor(|editor, cx| {
13277 editor
13278 .buffer()
13279 .read(cx)
13280 .as_singleton()
13281 .unwrap()
13282 .update(cx, |buffer, cx| {
13283 buffer.set_diff_base(Some(base_text.into()), cx);
13284 });
13285 });
13286 cx.executor().run_until_parked();
13287
13288 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13289 let snapshot = editor.buffer().read(cx).snapshot(cx);
13290 let reverted_hunk_statuses = snapshot
13291 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13292 .map(|hunk| hunk_status(&hunk))
13293 .collect::<Vec<_>>();
13294
13295 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13296 reverted_hunk_statuses
13297 });
13298 cx.executor().run_until_parked();
13299 cx.assert_editor_state(expected_reverted_text_with_selections);
13300 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13301}