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 position: snapshot.anchor_after(Point::new(2, 0)),
3872 disposition: BlockDisposition::Below,
3873 height: 1,
3874 render: Box::new(|_| div().into_any()),
3875 priority: 0,
3876 }],
3877 Some(Autoscroll::fit()),
3878 cx,
3879 );
3880 editor.change_selections(None, cx, |s| {
3881 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3882 });
3883 editor.move_line_down(&MoveLineDown, cx);
3884 });
3885}
3886
3887#[gpui::test]
3888fn test_transpose(cx: &mut TestAppContext) {
3889 init_test(cx, |_| {});
3890
3891 _ = cx.add_window(|cx| {
3892 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3893 editor.set_style(EditorStyle::default(), cx);
3894 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3895 editor.transpose(&Default::default(), cx);
3896 assert_eq!(editor.text(cx), "bac");
3897 assert_eq!(editor.selections.ranges(cx), [2..2]);
3898
3899 editor.transpose(&Default::default(), cx);
3900 assert_eq!(editor.text(cx), "bca");
3901 assert_eq!(editor.selections.ranges(cx), [3..3]);
3902
3903 editor.transpose(&Default::default(), cx);
3904 assert_eq!(editor.text(cx), "bac");
3905 assert_eq!(editor.selections.ranges(cx), [3..3]);
3906
3907 editor
3908 });
3909
3910 _ = cx.add_window(|cx| {
3911 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3912 editor.set_style(EditorStyle::default(), cx);
3913 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3914 editor.transpose(&Default::default(), cx);
3915 assert_eq!(editor.text(cx), "acb\nde");
3916 assert_eq!(editor.selections.ranges(cx), [3..3]);
3917
3918 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3919 editor.transpose(&Default::default(), cx);
3920 assert_eq!(editor.text(cx), "acbd\ne");
3921 assert_eq!(editor.selections.ranges(cx), [5..5]);
3922
3923 editor.transpose(&Default::default(), cx);
3924 assert_eq!(editor.text(cx), "acbde\n");
3925 assert_eq!(editor.selections.ranges(cx), [6..6]);
3926
3927 editor.transpose(&Default::default(), cx);
3928 assert_eq!(editor.text(cx), "acbd\ne");
3929 assert_eq!(editor.selections.ranges(cx), [6..6]);
3930
3931 editor
3932 });
3933
3934 _ = cx.add_window(|cx| {
3935 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3936 editor.set_style(EditorStyle::default(), cx);
3937 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3938 editor.transpose(&Default::default(), cx);
3939 assert_eq!(editor.text(cx), "bacd\ne");
3940 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3941
3942 editor.transpose(&Default::default(), cx);
3943 assert_eq!(editor.text(cx), "bcade\n");
3944 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3945
3946 editor.transpose(&Default::default(), cx);
3947 assert_eq!(editor.text(cx), "bcda\ne");
3948 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3949
3950 editor.transpose(&Default::default(), cx);
3951 assert_eq!(editor.text(cx), "bcade\n");
3952 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3953
3954 editor.transpose(&Default::default(), cx);
3955 assert_eq!(editor.text(cx), "bcaed\n");
3956 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3957
3958 editor
3959 });
3960
3961 _ = cx.add_window(|cx| {
3962 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3963 editor.set_style(EditorStyle::default(), cx);
3964 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3965 editor.transpose(&Default::default(), cx);
3966 assert_eq!(editor.text(cx), "🏀🍐✋");
3967 assert_eq!(editor.selections.ranges(cx), [8..8]);
3968
3969 editor.transpose(&Default::default(), cx);
3970 assert_eq!(editor.text(cx), "🏀✋🍐");
3971 assert_eq!(editor.selections.ranges(cx), [11..11]);
3972
3973 editor.transpose(&Default::default(), cx);
3974 assert_eq!(editor.text(cx), "🏀🍐✋");
3975 assert_eq!(editor.selections.ranges(cx), [11..11]);
3976
3977 editor
3978 });
3979}
3980
3981#[gpui::test]
3982async fn test_rewrap(cx: &mut TestAppContext) {
3983 init_test(cx, |_| {});
3984
3985 let mut cx = EditorTestContext::new(cx).await;
3986
3987 {
3988 let language = Arc::new(Language::new(
3989 LanguageConfig {
3990 line_comments: vec!["// ".into()],
3991 ..LanguageConfig::default()
3992 },
3993 None,
3994 ));
3995 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3996
3997 let unwrapped_text = indoc! {"
3998 // ˇ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.
3999 "};
4000
4001 let wrapped_text = indoc! {"
4002 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4003 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4004 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4005 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4006 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4007 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4008 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4009 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4010 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4011 // porttitor id. Aliquam id accumsan eros.ˇ
4012 "};
4013
4014 cx.set_state(unwrapped_text);
4015 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4016 cx.assert_editor_state(wrapped_text);
4017 }
4018
4019 // Test that rewrapping works inside of a selection
4020 {
4021 let language = Arc::new(Language::new(
4022 LanguageConfig {
4023 line_comments: vec!["// ".into()],
4024 ..LanguageConfig::default()
4025 },
4026 None,
4027 ));
4028 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4029
4030 let unwrapped_text = indoc! {"
4031 «// 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.ˇ»
4032 "};
4033
4034 let wrapped_text = indoc! {"
4035 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4036 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4037 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4038 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4039 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4040 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4041 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4042 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4043 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4044 // porttitor id. Aliquam id accumsan eros.ˇ
4045 "};
4046
4047 cx.set_state(unwrapped_text);
4048 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4049 cx.assert_editor_state(wrapped_text);
4050 }
4051
4052 // Test that cursors that expand to the same region are collapsed.
4053 {
4054 let language = Arc::new(Language::new(
4055 LanguageConfig {
4056 line_comments: vec!["// ".into()],
4057 ..LanguageConfig::default()
4058 },
4059 None,
4060 ));
4061 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4062
4063 let unwrapped_text = indoc! {"
4064 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4065 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4066 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4067 // ˇ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.
4068 "};
4069
4070 let wrapped_text = indoc! {"
4071 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4072 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4073 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4074 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4075 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4076 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4077 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4078 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4079 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4080 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4081 "};
4082
4083 cx.set_state(unwrapped_text);
4084 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4085 cx.assert_editor_state(wrapped_text);
4086 }
4087
4088 // Test that non-contiguous selections are treated separately.
4089 {
4090 let language = Arc::new(Language::new(
4091 LanguageConfig {
4092 line_comments: vec!["// ".into()],
4093 ..LanguageConfig::default()
4094 },
4095 None,
4096 ));
4097 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4098
4099 let unwrapped_text = indoc! {"
4100 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4101 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4102 //
4103 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4104 // ˇ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.
4105 "};
4106
4107 let wrapped_text = indoc! {"
4108 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4109 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4110 // auctor, eu lacinia sapien scelerisque.ˇˇ
4111 //
4112 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4113 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4114 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4115 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4116 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4117 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4118 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4119 "};
4120
4121 cx.set_state(unwrapped_text);
4122 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4123 cx.assert_editor_state(wrapped_text);
4124 }
4125
4126 // Test that different comment prefixes are supported.
4127 {
4128 let language = Arc::new(Language::new(
4129 LanguageConfig {
4130 line_comments: vec!["# ".into()],
4131 ..LanguageConfig::default()
4132 },
4133 None,
4134 ));
4135 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4136
4137 let unwrapped_text = indoc! {"
4138 # ˇ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.
4139 "};
4140
4141 let wrapped_text = indoc! {"
4142 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4143 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4144 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4145 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4146 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4147 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4148 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4149 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4150 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4151 # accumsan eros.ˇ
4152 "};
4153
4154 cx.set_state(unwrapped_text);
4155 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4156 cx.assert_editor_state(wrapped_text);
4157 }
4158
4159 // Test that rewrapping is ignored outside of comments in most languages.
4160 {
4161 let language = Arc::new(Language::new(
4162 LanguageConfig {
4163 line_comments: vec!["// ".into(), "/// ".into()],
4164 ..LanguageConfig::default()
4165 },
4166 Some(tree_sitter_rust::LANGUAGE.into()),
4167 ));
4168 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4169
4170 let unwrapped_text = indoc! {"
4171 /// Adds two numbers.
4172 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4173 fn add(a: u32, b: u32) -> u32 {
4174 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ˇ
4175 }
4176 "};
4177
4178 let wrapped_text = indoc! {"
4179 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4180 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4181 fn add(a: u32, b: u32) -> u32 {
4182 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ˇ
4183 }
4184 "};
4185
4186 cx.set_state(unwrapped_text);
4187 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4188 cx.assert_editor_state(wrapped_text);
4189 }
4190
4191 // Test that rewrapping works in Markdown and Plain Text languages.
4192 {
4193 let markdown_language = Arc::new(Language::new(
4194 LanguageConfig {
4195 name: "Markdown".into(),
4196 ..LanguageConfig::default()
4197 },
4198 None,
4199 ));
4200 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4201
4202 let unwrapped_text = indoc! {"
4203 # Hello
4204
4205 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.
4206 "};
4207
4208 let wrapped_text = indoc! {"
4209 # Hello
4210
4211 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4212 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4213 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4214 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4215 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4216 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4217 Integer sit amet scelerisque nisi.ˇ
4218 "};
4219
4220 cx.set_state(unwrapped_text);
4221 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4222 cx.assert_editor_state(wrapped_text);
4223
4224 let plaintext_language = Arc::new(Language::new(
4225 LanguageConfig {
4226 name: "Plain Text".into(),
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4232
4233 let unwrapped_text = indoc! {"
4234 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4235 "};
4236
4237 let wrapped_text = indoc! {"
4238 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4239 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4240 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4241 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4242 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4243 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4244 Integer sit amet scelerisque nisi.ˇ
4245 "};
4246
4247 cx.set_state(unwrapped_text);
4248 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4249 cx.assert_editor_state(wrapped_text);
4250 }
4251
4252 // Test rewrapping unaligned comments in a selection.
4253 {
4254 let language = Arc::new(Language::new(
4255 LanguageConfig {
4256 line_comments: vec!["// ".into(), "/// ".into()],
4257 ..LanguageConfig::default()
4258 },
4259 Some(tree_sitter_rust::LANGUAGE.into()),
4260 ));
4261 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4262
4263 let unwrapped_text = indoc! {"
4264 fn foo() {
4265 if true {
4266 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4267 // Praesent semper egestas tellus id dignissim.ˇ»
4268 do_something();
4269 } else {
4270 //
4271 }
4272 }
4273 "};
4274
4275 let wrapped_text = indoc! {"
4276 fn foo() {
4277 if true {
4278 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4279 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4280 // egestas tellus id dignissim.ˇ
4281 do_something();
4282 } else {
4283 //
4284 }
4285 }
4286 "};
4287
4288 cx.set_state(unwrapped_text);
4289 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4290 cx.assert_editor_state(wrapped_text);
4291
4292 let unwrapped_text = indoc! {"
4293 fn foo() {
4294 if true {
4295 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4296 // Praesent semper egestas tellus id dignissim.»
4297 do_something();
4298 } else {
4299 //
4300 }
4301
4302 }
4303 "};
4304
4305 let wrapped_text = indoc! {"
4306 fn foo() {
4307 if true {
4308 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4309 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4310 // egestas tellus id dignissim.ˇ
4311 do_something();
4312 } else {
4313 //
4314 }
4315
4316 }
4317 "};
4318
4319 cx.set_state(unwrapped_text);
4320 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4321 cx.assert_editor_state(wrapped_text);
4322 }
4323}
4324
4325#[gpui::test]
4326async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4327 init_test(cx, |_| {});
4328
4329 let mut cx = EditorTestContext::new(cx).await;
4330
4331 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4332 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4333 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4334
4335 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4336 cx.set_state("two ˇfour ˇsix ˇ");
4337 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4338 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4339
4340 // Paste again but with only two cursors. Since the number of cursors doesn't
4341 // match the number of slices in the clipboard, the entire clipboard text
4342 // is pasted at each cursor.
4343 cx.set_state("ˇtwo one✅ four three six five ˇ");
4344 cx.update_editor(|e, cx| {
4345 e.handle_input("( ", cx);
4346 e.paste(&Paste, cx);
4347 e.handle_input(") ", cx);
4348 });
4349 cx.assert_editor_state(
4350 &([
4351 "( one✅ ",
4352 "three ",
4353 "five ) ˇtwo one✅ four three six five ( one✅ ",
4354 "three ",
4355 "five ) ˇ",
4356 ]
4357 .join("\n")),
4358 );
4359
4360 // Cut with three selections, one of which is full-line.
4361 cx.set_state(indoc! {"
4362 1«2ˇ»3
4363 4ˇ567
4364 «8ˇ»9"});
4365 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4366 cx.assert_editor_state(indoc! {"
4367 1ˇ3
4368 ˇ9"});
4369
4370 // Paste with three selections, noticing how the copied selection that was full-line
4371 // gets inserted before the second cursor.
4372 cx.set_state(indoc! {"
4373 1ˇ3
4374 9ˇ
4375 «oˇ»ne"});
4376 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4377 cx.assert_editor_state(indoc! {"
4378 12ˇ3
4379 4567
4380 9ˇ
4381 8ˇne"});
4382
4383 // Copy with a single cursor only, which writes the whole line into the clipboard.
4384 cx.set_state(indoc! {"
4385 The quick brown
4386 fox juˇmps over
4387 the lazy dog"});
4388 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4389 assert_eq!(
4390 cx.read_from_clipboard()
4391 .and_then(|item| item.text().as_deref().map(str::to_string)),
4392 Some("fox jumps over\n".to_string())
4393 );
4394
4395 // Paste with three selections, noticing how the copied full-line selection is inserted
4396 // before the empty selections but replaces the selection that is non-empty.
4397 cx.set_state(indoc! {"
4398 Tˇhe quick brown
4399 «foˇ»x jumps over
4400 tˇhe lazy dog"});
4401 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4402 cx.assert_editor_state(indoc! {"
4403 fox jumps over
4404 Tˇhe quick brown
4405 fox jumps over
4406 ˇx jumps over
4407 fox jumps over
4408 tˇhe lazy dog"});
4409}
4410
4411#[gpui::test]
4412async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4413 init_test(cx, |_| {});
4414
4415 let mut cx = EditorTestContext::new(cx).await;
4416 let language = Arc::new(Language::new(
4417 LanguageConfig::default(),
4418 Some(tree_sitter_rust::LANGUAGE.into()),
4419 ));
4420 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4421
4422 // Cut an indented block, without the leading whitespace.
4423 cx.set_state(indoc! {"
4424 const a: B = (
4425 c(),
4426 «d(
4427 e,
4428 f
4429 )ˇ»
4430 );
4431 "});
4432 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4433 cx.assert_editor_state(indoc! {"
4434 const a: B = (
4435 c(),
4436 ˇ
4437 );
4438 "});
4439
4440 // Paste it at the same position.
4441 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4442 cx.assert_editor_state(indoc! {"
4443 const a: B = (
4444 c(),
4445 d(
4446 e,
4447 f
4448 )ˇ
4449 );
4450 "});
4451
4452 // Paste it at a line with a lower indent level.
4453 cx.set_state(indoc! {"
4454 ˇ
4455 const a: B = (
4456 c(),
4457 );
4458 "});
4459 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4460 cx.assert_editor_state(indoc! {"
4461 d(
4462 e,
4463 f
4464 )ˇ
4465 const a: B = (
4466 c(),
4467 );
4468 "});
4469
4470 // Cut an indented block, with the leading whitespace.
4471 cx.set_state(indoc! {"
4472 const a: B = (
4473 c(),
4474 « d(
4475 e,
4476 f
4477 )
4478 ˇ»);
4479 "});
4480 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4481 cx.assert_editor_state(indoc! {"
4482 const a: B = (
4483 c(),
4484 ˇ);
4485 "});
4486
4487 // Paste it at the same position.
4488 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4489 cx.assert_editor_state(indoc! {"
4490 const a: B = (
4491 c(),
4492 d(
4493 e,
4494 f
4495 )
4496 ˇ);
4497 "});
4498
4499 // Paste it at a line with a higher indent level.
4500 cx.set_state(indoc! {"
4501 const a: B = (
4502 c(),
4503 d(
4504 e,
4505 fˇ
4506 )
4507 );
4508 "});
4509 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4510 cx.assert_editor_state(indoc! {"
4511 const a: B = (
4512 c(),
4513 d(
4514 e,
4515 f d(
4516 e,
4517 f
4518 )
4519 ˇ
4520 )
4521 );
4522 "});
4523}
4524
4525#[gpui::test]
4526fn test_select_all(cx: &mut TestAppContext) {
4527 init_test(cx, |_| {});
4528
4529 let view = cx.add_window(|cx| {
4530 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4531 build_editor(buffer, cx)
4532 });
4533 _ = view.update(cx, |view, cx| {
4534 view.select_all(&SelectAll, cx);
4535 assert_eq!(
4536 view.selections.display_ranges(cx),
4537 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4538 );
4539 });
4540}
4541
4542#[gpui::test]
4543fn test_select_line(cx: &mut TestAppContext) {
4544 init_test(cx, |_| {});
4545
4546 let view = cx.add_window(|cx| {
4547 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4548 build_editor(buffer, cx)
4549 });
4550 _ = view.update(cx, |view, cx| {
4551 view.change_selections(None, cx, |s| {
4552 s.select_display_ranges([
4553 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4554 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4555 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4556 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4557 ])
4558 });
4559 view.select_line(&SelectLine, cx);
4560 assert_eq!(
4561 view.selections.display_ranges(cx),
4562 vec![
4563 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4564 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4565 ]
4566 );
4567 });
4568
4569 _ = view.update(cx, |view, cx| {
4570 view.select_line(&SelectLine, cx);
4571 assert_eq!(
4572 view.selections.display_ranges(cx),
4573 vec![
4574 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4575 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4576 ]
4577 );
4578 });
4579
4580 _ = view.update(cx, |view, cx| {
4581 view.select_line(&SelectLine, cx);
4582 assert_eq!(
4583 view.selections.display_ranges(cx),
4584 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4585 );
4586 });
4587}
4588
4589#[gpui::test]
4590fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4591 init_test(cx, |_| {});
4592
4593 let view = cx.add_window(|cx| {
4594 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4595 build_editor(buffer, cx)
4596 });
4597 _ = view.update(cx, |view, cx| {
4598 view.fold_ranges(
4599 vec![
4600 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4601 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4602 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4603 ],
4604 true,
4605 cx,
4606 );
4607 view.change_selections(None, cx, |s| {
4608 s.select_display_ranges([
4609 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4610 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4611 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4612 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4613 ])
4614 });
4615 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4616 });
4617
4618 _ = view.update(cx, |view, cx| {
4619 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4620 assert_eq!(
4621 view.display_text(cx),
4622 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4623 );
4624 assert_eq!(
4625 view.selections.display_ranges(cx),
4626 [
4627 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4628 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4629 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4630 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4631 ]
4632 );
4633 });
4634
4635 _ = view.update(cx, |view, cx| {
4636 view.change_selections(None, cx, |s| {
4637 s.select_display_ranges([
4638 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4639 ])
4640 });
4641 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4642 assert_eq!(
4643 view.display_text(cx),
4644 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4645 );
4646 assert_eq!(
4647 view.selections.display_ranges(cx),
4648 [
4649 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4650 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4651 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4652 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4653 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4654 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4655 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4656 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4657 ]
4658 );
4659 });
4660}
4661
4662#[gpui::test]
4663async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4664 init_test(cx, |_| {});
4665
4666 let mut cx = EditorTestContext::new(cx).await;
4667
4668 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4669 cx.set_state(indoc!(
4670 r#"abc
4671 defˇghi
4672
4673 jk
4674 nlmo
4675 "#
4676 ));
4677
4678 cx.update_editor(|editor, cx| {
4679 editor.add_selection_above(&Default::default(), cx);
4680 });
4681
4682 cx.assert_editor_state(indoc!(
4683 r#"abcˇ
4684 defˇghi
4685
4686 jk
4687 nlmo
4688 "#
4689 ));
4690
4691 cx.update_editor(|editor, cx| {
4692 editor.add_selection_above(&Default::default(), cx);
4693 });
4694
4695 cx.assert_editor_state(indoc!(
4696 r#"abcˇ
4697 defˇghi
4698
4699 jk
4700 nlmo
4701 "#
4702 ));
4703
4704 cx.update_editor(|view, cx| {
4705 view.add_selection_below(&Default::default(), cx);
4706 });
4707
4708 cx.assert_editor_state(indoc!(
4709 r#"abc
4710 defˇghi
4711
4712 jk
4713 nlmo
4714 "#
4715 ));
4716
4717 cx.update_editor(|view, cx| {
4718 view.undo_selection(&Default::default(), cx);
4719 });
4720
4721 cx.assert_editor_state(indoc!(
4722 r#"abcˇ
4723 defˇghi
4724
4725 jk
4726 nlmo
4727 "#
4728 ));
4729
4730 cx.update_editor(|view, cx| {
4731 view.redo_selection(&Default::default(), cx);
4732 });
4733
4734 cx.assert_editor_state(indoc!(
4735 r#"abc
4736 defˇghi
4737
4738 jk
4739 nlmo
4740 "#
4741 ));
4742
4743 cx.update_editor(|view, cx| {
4744 view.add_selection_below(&Default::default(), cx);
4745 });
4746
4747 cx.assert_editor_state(indoc!(
4748 r#"abc
4749 defˇghi
4750
4751 jk
4752 nlmˇo
4753 "#
4754 ));
4755
4756 cx.update_editor(|view, cx| {
4757 view.add_selection_below(&Default::default(), cx);
4758 });
4759
4760 cx.assert_editor_state(indoc!(
4761 r#"abc
4762 defˇghi
4763
4764 jk
4765 nlmˇo
4766 "#
4767 ));
4768
4769 // change selections
4770 cx.set_state(indoc!(
4771 r#"abc
4772 def«ˇg»hi
4773
4774 jk
4775 nlmo
4776 "#
4777 ));
4778
4779 cx.update_editor(|view, cx| {
4780 view.add_selection_below(&Default::default(), cx);
4781 });
4782
4783 cx.assert_editor_state(indoc!(
4784 r#"abc
4785 def«ˇg»hi
4786
4787 jk
4788 nlm«ˇo»
4789 "#
4790 ));
4791
4792 cx.update_editor(|view, cx| {
4793 view.add_selection_below(&Default::default(), cx);
4794 });
4795
4796 cx.assert_editor_state(indoc!(
4797 r#"abc
4798 def«ˇg»hi
4799
4800 jk
4801 nlm«ˇo»
4802 "#
4803 ));
4804
4805 cx.update_editor(|view, cx| {
4806 view.add_selection_above(&Default::default(), cx);
4807 });
4808
4809 cx.assert_editor_state(indoc!(
4810 r#"abc
4811 def«ˇg»hi
4812
4813 jk
4814 nlmo
4815 "#
4816 ));
4817
4818 cx.update_editor(|view, cx| {
4819 view.add_selection_above(&Default::default(), cx);
4820 });
4821
4822 cx.assert_editor_state(indoc!(
4823 r#"abc
4824 def«ˇg»hi
4825
4826 jk
4827 nlmo
4828 "#
4829 ));
4830
4831 // Change selections again
4832 cx.set_state(indoc!(
4833 r#"a«bc
4834 defgˇ»hi
4835
4836 jk
4837 nlmo
4838 "#
4839 ));
4840
4841 cx.update_editor(|view, cx| {
4842 view.add_selection_below(&Default::default(), cx);
4843 });
4844
4845 cx.assert_editor_state(indoc!(
4846 r#"a«bcˇ»
4847 d«efgˇ»hi
4848
4849 j«kˇ»
4850 nlmo
4851 "#
4852 ));
4853
4854 cx.update_editor(|view, cx| {
4855 view.add_selection_below(&Default::default(), cx);
4856 });
4857 cx.assert_editor_state(indoc!(
4858 r#"a«bcˇ»
4859 d«efgˇ»hi
4860
4861 j«kˇ»
4862 n«lmoˇ»
4863 "#
4864 ));
4865 cx.update_editor(|view, cx| {
4866 view.add_selection_above(&Default::default(), cx);
4867 });
4868
4869 cx.assert_editor_state(indoc!(
4870 r#"a«bcˇ»
4871 d«efgˇ»hi
4872
4873 j«kˇ»
4874 nlmo
4875 "#
4876 ));
4877
4878 // Change selections again
4879 cx.set_state(indoc!(
4880 r#"abc
4881 d«ˇefghi
4882
4883 jk
4884 nlm»o
4885 "#
4886 ));
4887
4888 cx.update_editor(|view, cx| {
4889 view.add_selection_above(&Default::default(), cx);
4890 });
4891
4892 cx.assert_editor_state(indoc!(
4893 r#"a«ˇbc»
4894 d«ˇef»ghi
4895
4896 j«ˇk»
4897 n«ˇlm»o
4898 "#
4899 ));
4900
4901 cx.update_editor(|view, cx| {
4902 view.add_selection_below(&Default::default(), cx);
4903 });
4904
4905 cx.assert_editor_state(indoc!(
4906 r#"abc
4907 d«ˇef»ghi
4908
4909 j«ˇk»
4910 n«ˇlm»o
4911 "#
4912 ));
4913}
4914
4915#[gpui::test]
4916async fn test_select_next(cx: &mut gpui::TestAppContext) {
4917 init_test(cx, |_| {});
4918
4919 let mut cx = EditorTestContext::new(cx).await;
4920 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4921
4922 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4923 .unwrap();
4924 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4925
4926 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4927 .unwrap();
4928 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4929
4930 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4931 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4932
4933 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4934 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4935
4936 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4937 .unwrap();
4938 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4939
4940 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4941 .unwrap();
4942 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4943}
4944
4945#[gpui::test]
4946async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4947 init_test(cx, |_| {});
4948
4949 let mut cx = EditorTestContext::new(cx).await;
4950 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4951
4952 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4953 .unwrap();
4954 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4955}
4956
4957#[gpui::test]
4958async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4959 init_test(cx, |_| {});
4960
4961 let mut cx = EditorTestContext::new(cx).await;
4962 cx.set_state(
4963 r#"let foo = 2;
4964lˇet foo = 2;
4965let fooˇ = 2;
4966let foo = 2;
4967let foo = ˇ2;"#,
4968 );
4969
4970 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4971 .unwrap();
4972 cx.assert_editor_state(
4973 r#"let foo = 2;
4974«letˇ» foo = 2;
4975let «fooˇ» = 2;
4976let foo = 2;
4977let foo = «2ˇ»;"#,
4978 );
4979
4980 // noop for multiple selections with different contents
4981 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4982 .unwrap();
4983 cx.assert_editor_state(
4984 r#"let foo = 2;
4985«letˇ» foo = 2;
4986let «fooˇ» = 2;
4987let foo = 2;
4988let foo = «2ˇ»;"#,
4989 );
4990}
4991
4992#[gpui::test]
4993async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4994 init_test(cx, |_| {});
4995
4996 let mut cx =
4997 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
4998
4999 cx.assert_editor_state(indoc! {"
5000 ˇbbb
5001 ccc
5002
5003 bbb
5004 ccc
5005 "});
5006 cx.dispatch_action(SelectPrevious::default());
5007 cx.assert_editor_state(indoc! {"
5008 «bbbˇ»
5009 ccc
5010
5011 bbb
5012 ccc
5013 "});
5014 cx.dispatch_action(SelectPrevious::default());
5015 cx.assert_editor_state(indoc! {"
5016 «bbbˇ»
5017 ccc
5018
5019 «bbbˇ»
5020 ccc
5021 "});
5022}
5023
5024#[gpui::test]
5025async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5026 init_test(cx, |_| {});
5027
5028 let mut cx = EditorTestContext::new(cx).await;
5029 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5030
5031 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5032 .unwrap();
5033 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5034
5035 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5036 .unwrap();
5037 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5038
5039 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5040 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5041
5042 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5043 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5044
5045 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5046 .unwrap();
5047 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5048
5049 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5050 .unwrap();
5051 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5052
5053 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5054 .unwrap();
5055 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5056}
5057
5058#[gpui::test]
5059async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5060 init_test(cx, |_| {});
5061
5062 let mut cx = EditorTestContext::new(cx).await;
5063 cx.set_state(
5064 r#"let foo = 2;
5065lˇet foo = 2;
5066let fooˇ = 2;
5067let foo = 2;
5068let foo = ˇ2;"#,
5069 );
5070
5071 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5072 .unwrap();
5073 cx.assert_editor_state(
5074 r#"let foo = 2;
5075«letˇ» foo = 2;
5076let «fooˇ» = 2;
5077let foo = 2;
5078let foo = «2ˇ»;"#,
5079 );
5080
5081 // noop for multiple selections with different contents
5082 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5083 .unwrap();
5084 cx.assert_editor_state(
5085 r#"let foo = 2;
5086«letˇ» foo = 2;
5087let «fooˇ» = 2;
5088let foo = 2;
5089let foo = «2ˇ»;"#,
5090 );
5091}
5092
5093#[gpui::test]
5094async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5095 init_test(cx, |_| {});
5096
5097 let mut cx = EditorTestContext::new(cx).await;
5098 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5099
5100 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5101 .unwrap();
5102 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5103
5104 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5105 .unwrap();
5106 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5107
5108 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5109 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5110
5111 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5112 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5113
5114 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5115 .unwrap();
5116 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5117
5118 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5119 .unwrap();
5120 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5121}
5122
5123#[gpui::test]
5124async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5125 init_test(cx, |_| {});
5126
5127 let language = Arc::new(Language::new(
5128 LanguageConfig::default(),
5129 Some(tree_sitter_rust::LANGUAGE.into()),
5130 ));
5131
5132 let text = r#"
5133 use mod1::mod2::{mod3, mod4};
5134
5135 fn fn_1(param1: bool, param2: &str) {
5136 let var1 = "text";
5137 }
5138 "#
5139 .unindent();
5140
5141 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5142 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5143 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5144
5145 editor
5146 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5147 .await;
5148
5149 editor.update(cx, |view, cx| {
5150 view.change_selections(None, cx, |s| {
5151 s.select_display_ranges([
5152 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5153 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5154 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5155 ]);
5156 });
5157 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5158 });
5159 editor.update(cx, |editor, cx| {
5160 assert_text_with_selections(
5161 editor,
5162 indoc! {r#"
5163 use mod1::mod2::{mod3, «mod4ˇ»};
5164
5165 fn fn_1«ˇ(param1: bool, param2: &str)» {
5166 let var1 = "«textˇ»";
5167 }
5168 "#},
5169 cx,
5170 );
5171 });
5172
5173 editor.update(cx, |view, cx| {
5174 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5175 });
5176 editor.update(cx, |editor, cx| {
5177 assert_text_with_selections(
5178 editor,
5179 indoc! {r#"
5180 use mod1::mod2::«{mod3, mod4}ˇ»;
5181
5182 «ˇfn fn_1(param1: bool, param2: &str) {
5183 let var1 = "text";
5184 }»
5185 "#},
5186 cx,
5187 );
5188 });
5189
5190 editor.update(cx, |view, cx| {
5191 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5192 });
5193 assert_eq!(
5194 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5195 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5196 );
5197
5198 // Trying to expand the selected syntax node one more time has no effect.
5199 editor.update(cx, |view, cx| {
5200 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5201 });
5202 assert_eq!(
5203 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5204 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5205 );
5206
5207 editor.update(cx, |view, cx| {
5208 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5209 });
5210 editor.update(cx, |editor, cx| {
5211 assert_text_with_selections(
5212 editor,
5213 indoc! {r#"
5214 use mod1::mod2::«{mod3, mod4}ˇ»;
5215
5216 «ˇfn fn_1(param1: bool, param2: &str) {
5217 let var1 = "text";
5218 }»
5219 "#},
5220 cx,
5221 );
5222 });
5223
5224 editor.update(cx, |view, cx| {
5225 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5226 });
5227 editor.update(cx, |editor, cx| {
5228 assert_text_with_selections(
5229 editor,
5230 indoc! {r#"
5231 use mod1::mod2::{mod3, «mod4ˇ»};
5232
5233 fn fn_1«ˇ(param1: bool, param2: &str)» {
5234 let var1 = "«textˇ»";
5235 }
5236 "#},
5237 cx,
5238 );
5239 });
5240
5241 editor.update(cx, |view, cx| {
5242 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5243 });
5244 editor.update(cx, |editor, cx| {
5245 assert_text_with_selections(
5246 editor,
5247 indoc! {r#"
5248 use mod1::mod2::{mod3, mo«ˇ»d4};
5249
5250 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5251 let var1 = "te«ˇ»xt";
5252 }
5253 "#},
5254 cx,
5255 );
5256 });
5257
5258 // Trying to shrink the selected syntax node one more time has no effect.
5259 editor.update(cx, |view, cx| {
5260 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5261 });
5262 editor.update(cx, |editor, cx| {
5263 assert_text_with_selections(
5264 editor,
5265 indoc! {r#"
5266 use mod1::mod2::{mod3, mo«ˇ»d4};
5267
5268 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5269 let var1 = "te«ˇ»xt";
5270 }
5271 "#},
5272 cx,
5273 );
5274 });
5275
5276 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5277 // a fold.
5278 editor.update(cx, |view, cx| {
5279 view.fold_ranges(
5280 vec![
5281 (
5282 Point::new(0, 21)..Point::new(0, 24),
5283 FoldPlaceholder::test(),
5284 ),
5285 (
5286 Point::new(3, 20)..Point::new(3, 22),
5287 FoldPlaceholder::test(),
5288 ),
5289 ],
5290 true,
5291 cx,
5292 );
5293 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5294 });
5295 editor.update(cx, |editor, cx| {
5296 assert_text_with_selections(
5297 editor,
5298 indoc! {r#"
5299 use mod1::mod2::«{mod3, mod4}ˇ»;
5300
5301 fn fn_1«ˇ(param1: bool, param2: &str)» {
5302 «let var1 = "text";ˇ»
5303 }
5304 "#},
5305 cx,
5306 );
5307 });
5308}
5309
5310#[gpui::test]
5311async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5312 init_test(cx, |_| {});
5313
5314 let language = Arc::new(
5315 Language::new(
5316 LanguageConfig {
5317 brackets: BracketPairConfig {
5318 pairs: vec![
5319 BracketPair {
5320 start: "{".to_string(),
5321 end: "}".to_string(),
5322 close: false,
5323 surround: false,
5324 newline: true,
5325 },
5326 BracketPair {
5327 start: "(".to_string(),
5328 end: ")".to_string(),
5329 close: false,
5330 surround: false,
5331 newline: true,
5332 },
5333 ],
5334 ..Default::default()
5335 },
5336 ..Default::default()
5337 },
5338 Some(tree_sitter_rust::LANGUAGE.into()),
5339 )
5340 .with_indents_query(
5341 r#"
5342 (_ "(" ")" @end) @indent
5343 (_ "{" "}" @end) @indent
5344 "#,
5345 )
5346 .unwrap(),
5347 );
5348
5349 let text = "fn a() {}";
5350
5351 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5352 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5353 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5354 editor
5355 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5356 .await;
5357
5358 editor.update(cx, |editor, cx| {
5359 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5360 editor.newline(&Newline, cx);
5361 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5362 assert_eq!(
5363 editor.selections.ranges(cx),
5364 &[
5365 Point::new(1, 4)..Point::new(1, 4),
5366 Point::new(3, 4)..Point::new(3, 4),
5367 Point::new(5, 0)..Point::new(5, 0)
5368 ]
5369 );
5370 });
5371}
5372
5373#[gpui::test]
5374async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5375 init_test(cx, |_| {});
5376
5377 let mut cx = EditorTestContext::new(cx).await;
5378
5379 let language = Arc::new(Language::new(
5380 LanguageConfig {
5381 brackets: BracketPairConfig {
5382 pairs: vec![
5383 BracketPair {
5384 start: "{".to_string(),
5385 end: "}".to_string(),
5386 close: true,
5387 surround: true,
5388 newline: true,
5389 },
5390 BracketPair {
5391 start: "(".to_string(),
5392 end: ")".to_string(),
5393 close: true,
5394 surround: true,
5395 newline: true,
5396 },
5397 BracketPair {
5398 start: "/*".to_string(),
5399 end: " */".to_string(),
5400 close: true,
5401 surround: true,
5402 newline: true,
5403 },
5404 BracketPair {
5405 start: "[".to_string(),
5406 end: "]".to_string(),
5407 close: false,
5408 surround: false,
5409 newline: true,
5410 },
5411 BracketPair {
5412 start: "\"".to_string(),
5413 end: "\"".to_string(),
5414 close: true,
5415 surround: true,
5416 newline: false,
5417 },
5418 BracketPair {
5419 start: "<".to_string(),
5420 end: ">".to_string(),
5421 close: false,
5422 surround: true,
5423 newline: true,
5424 },
5425 ],
5426 ..Default::default()
5427 },
5428 autoclose_before: "})]".to_string(),
5429 ..Default::default()
5430 },
5431 Some(tree_sitter_rust::LANGUAGE.into()),
5432 ));
5433
5434 cx.language_registry().add(language.clone());
5435 cx.update_buffer(|buffer, cx| {
5436 buffer.set_language(Some(language), cx);
5437 });
5438
5439 cx.set_state(
5440 &r#"
5441 🏀ˇ
5442 εˇ
5443 ❤️ˇ
5444 "#
5445 .unindent(),
5446 );
5447
5448 // autoclose multiple nested brackets at multiple cursors
5449 cx.update_editor(|view, cx| {
5450 view.handle_input("{", cx);
5451 view.handle_input("{", cx);
5452 view.handle_input("{", cx);
5453 });
5454 cx.assert_editor_state(
5455 &"
5456 🏀{{{ˇ}}}
5457 ε{{{ˇ}}}
5458 ❤️{{{ˇ}}}
5459 "
5460 .unindent(),
5461 );
5462
5463 // insert a different closing bracket
5464 cx.update_editor(|view, cx| {
5465 view.handle_input(")", cx);
5466 });
5467 cx.assert_editor_state(
5468 &"
5469 🏀{{{)ˇ}}}
5470 ε{{{)ˇ}}}
5471 ❤️{{{)ˇ}}}
5472 "
5473 .unindent(),
5474 );
5475
5476 // skip over the auto-closed brackets when typing a closing bracket
5477 cx.update_editor(|view, cx| {
5478 view.move_right(&MoveRight, cx);
5479 view.handle_input("}", cx);
5480 view.handle_input("}", cx);
5481 view.handle_input("}", cx);
5482 });
5483 cx.assert_editor_state(
5484 &"
5485 🏀{{{)}}}}ˇ
5486 ε{{{)}}}}ˇ
5487 ❤️{{{)}}}}ˇ
5488 "
5489 .unindent(),
5490 );
5491
5492 // autoclose multi-character pairs
5493 cx.set_state(
5494 &"
5495 ˇ
5496 ˇ
5497 "
5498 .unindent(),
5499 );
5500 cx.update_editor(|view, cx| {
5501 view.handle_input("/", cx);
5502 view.handle_input("*", cx);
5503 });
5504 cx.assert_editor_state(
5505 &"
5506 /*ˇ */
5507 /*ˇ */
5508 "
5509 .unindent(),
5510 );
5511
5512 // one cursor autocloses a multi-character pair, one cursor
5513 // does not autoclose.
5514 cx.set_state(
5515 &"
5516 /ˇ
5517 ˇ
5518 "
5519 .unindent(),
5520 );
5521 cx.update_editor(|view, cx| view.handle_input("*", cx));
5522 cx.assert_editor_state(
5523 &"
5524 /*ˇ */
5525 *ˇ
5526 "
5527 .unindent(),
5528 );
5529
5530 // Don't autoclose if the next character isn't whitespace and isn't
5531 // listed in the language's "autoclose_before" section.
5532 cx.set_state("ˇa b");
5533 cx.update_editor(|view, cx| view.handle_input("{", cx));
5534 cx.assert_editor_state("{ˇa b");
5535
5536 // Don't autoclose if `close` is false for the bracket pair
5537 cx.set_state("ˇ");
5538 cx.update_editor(|view, cx| view.handle_input("[", cx));
5539 cx.assert_editor_state("[ˇ");
5540
5541 // Surround with brackets if text is selected
5542 cx.set_state("«aˇ» b");
5543 cx.update_editor(|view, cx| view.handle_input("{", cx));
5544 cx.assert_editor_state("{«aˇ»} b");
5545
5546 // Autclose pair where the start and end characters are the same
5547 cx.set_state("aˇ");
5548 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5549 cx.assert_editor_state("a\"ˇ\"");
5550 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5551 cx.assert_editor_state("a\"\"ˇ");
5552
5553 // Don't autoclose pair if autoclose is disabled
5554 cx.set_state("ˇ");
5555 cx.update_editor(|view, cx| view.handle_input("<", cx));
5556 cx.assert_editor_state("<ˇ");
5557
5558 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5559 cx.set_state("«aˇ» b");
5560 cx.update_editor(|view, cx| view.handle_input("<", cx));
5561 cx.assert_editor_state("<«aˇ»> b");
5562}
5563
5564#[gpui::test]
5565async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5566 init_test(cx, |settings| {
5567 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5568 });
5569
5570 let mut cx = EditorTestContext::new(cx).await;
5571
5572 let language = Arc::new(Language::new(
5573 LanguageConfig {
5574 brackets: BracketPairConfig {
5575 pairs: vec![
5576 BracketPair {
5577 start: "{".to_string(),
5578 end: "}".to_string(),
5579 close: true,
5580 surround: true,
5581 newline: true,
5582 },
5583 BracketPair {
5584 start: "(".to_string(),
5585 end: ")".to_string(),
5586 close: true,
5587 surround: true,
5588 newline: true,
5589 },
5590 BracketPair {
5591 start: "[".to_string(),
5592 end: "]".to_string(),
5593 close: false,
5594 surround: false,
5595 newline: true,
5596 },
5597 ],
5598 ..Default::default()
5599 },
5600 autoclose_before: "})]".to_string(),
5601 ..Default::default()
5602 },
5603 Some(tree_sitter_rust::LANGUAGE.into()),
5604 ));
5605
5606 cx.language_registry().add(language.clone());
5607 cx.update_buffer(|buffer, cx| {
5608 buffer.set_language(Some(language), cx);
5609 });
5610
5611 cx.set_state(
5612 &"
5613 ˇ
5614 ˇ
5615 ˇ
5616 "
5617 .unindent(),
5618 );
5619
5620 // ensure only matching closing brackets are skipped over
5621 cx.update_editor(|view, cx| {
5622 view.handle_input("}", cx);
5623 view.move_left(&MoveLeft, cx);
5624 view.handle_input(")", cx);
5625 view.move_left(&MoveLeft, cx);
5626 });
5627 cx.assert_editor_state(
5628 &"
5629 ˇ)}
5630 ˇ)}
5631 ˇ)}
5632 "
5633 .unindent(),
5634 );
5635
5636 // skip-over closing brackets at multiple cursors
5637 cx.update_editor(|view, cx| {
5638 view.handle_input(")", cx);
5639 view.handle_input("}", cx);
5640 });
5641 cx.assert_editor_state(
5642 &"
5643 )}ˇ
5644 )}ˇ
5645 )}ˇ
5646 "
5647 .unindent(),
5648 );
5649
5650 // ignore non-close brackets
5651 cx.update_editor(|view, cx| {
5652 view.handle_input("]", cx);
5653 view.move_left(&MoveLeft, cx);
5654 view.handle_input("]", cx);
5655 });
5656 cx.assert_editor_state(
5657 &"
5658 )}]ˇ]
5659 )}]ˇ]
5660 )}]ˇ]
5661 "
5662 .unindent(),
5663 );
5664}
5665
5666#[gpui::test]
5667async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5668 init_test(cx, |_| {});
5669
5670 let mut cx = EditorTestContext::new(cx).await;
5671
5672 let html_language = Arc::new(
5673 Language::new(
5674 LanguageConfig {
5675 name: "HTML".into(),
5676 brackets: BracketPairConfig {
5677 pairs: vec![
5678 BracketPair {
5679 start: "<".into(),
5680 end: ">".into(),
5681 close: true,
5682 ..Default::default()
5683 },
5684 BracketPair {
5685 start: "{".into(),
5686 end: "}".into(),
5687 close: true,
5688 ..Default::default()
5689 },
5690 BracketPair {
5691 start: "(".into(),
5692 end: ")".into(),
5693 close: true,
5694 ..Default::default()
5695 },
5696 ],
5697 ..Default::default()
5698 },
5699 autoclose_before: "})]>".into(),
5700 ..Default::default()
5701 },
5702 Some(tree_sitter_html::language()),
5703 )
5704 .with_injection_query(
5705 r#"
5706 (script_element
5707 (raw_text) @content
5708 (#set! "language" "javascript"))
5709 "#,
5710 )
5711 .unwrap(),
5712 );
5713
5714 let javascript_language = Arc::new(Language::new(
5715 LanguageConfig {
5716 name: "JavaScript".into(),
5717 brackets: BracketPairConfig {
5718 pairs: vec![
5719 BracketPair {
5720 start: "/*".into(),
5721 end: " */".into(),
5722 close: true,
5723 ..Default::default()
5724 },
5725 BracketPair {
5726 start: "{".into(),
5727 end: "}".into(),
5728 close: true,
5729 ..Default::default()
5730 },
5731 BracketPair {
5732 start: "(".into(),
5733 end: ")".into(),
5734 close: true,
5735 ..Default::default()
5736 },
5737 ],
5738 ..Default::default()
5739 },
5740 autoclose_before: "})]>".into(),
5741 ..Default::default()
5742 },
5743 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5744 ));
5745
5746 cx.language_registry().add(html_language.clone());
5747 cx.language_registry().add(javascript_language.clone());
5748
5749 cx.update_buffer(|buffer, cx| {
5750 buffer.set_language(Some(html_language), cx);
5751 });
5752
5753 cx.set_state(
5754 &r#"
5755 <body>ˇ
5756 <script>
5757 var x = 1;ˇ
5758 </script>
5759 </body>ˇ
5760 "#
5761 .unindent(),
5762 );
5763
5764 // Precondition: different languages are active at different locations.
5765 cx.update_editor(|editor, cx| {
5766 let snapshot = editor.snapshot(cx);
5767 let cursors = editor.selections.ranges::<usize>(cx);
5768 let languages = cursors
5769 .iter()
5770 .map(|c| snapshot.language_at(c.start).unwrap().name())
5771 .collect::<Vec<_>>();
5772 assert_eq!(
5773 languages,
5774 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5775 );
5776 });
5777
5778 // Angle brackets autoclose in HTML, but not JavaScript.
5779 cx.update_editor(|editor, cx| {
5780 editor.handle_input("<", cx);
5781 editor.handle_input("a", cx);
5782 });
5783 cx.assert_editor_state(
5784 &r#"
5785 <body><aˇ>
5786 <script>
5787 var x = 1;<aˇ
5788 </script>
5789 </body><aˇ>
5790 "#
5791 .unindent(),
5792 );
5793
5794 // Curly braces and parens autoclose in both HTML and JavaScript.
5795 cx.update_editor(|editor, cx| {
5796 editor.handle_input(" b=", cx);
5797 editor.handle_input("{", cx);
5798 editor.handle_input("c", cx);
5799 editor.handle_input("(", cx);
5800 });
5801 cx.assert_editor_state(
5802 &r#"
5803 <body><a b={c(ˇ)}>
5804 <script>
5805 var x = 1;<a b={c(ˇ)}
5806 </script>
5807 </body><a b={c(ˇ)}>
5808 "#
5809 .unindent(),
5810 );
5811
5812 // Brackets that were already autoclosed are skipped.
5813 cx.update_editor(|editor, cx| {
5814 editor.handle_input(")", cx);
5815 editor.handle_input("d", cx);
5816 editor.handle_input("}", cx);
5817 });
5818 cx.assert_editor_state(
5819 &r#"
5820 <body><a b={c()d}ˇ>
5821 <script>
5822 var x = 1;<a b={c()d}ˇ
5823 </script>
5824 </body><a b={c()d}ˇ>
5825 "#
5826 .unindent(),
5827 );
5828 cx.update_editor(|editor, cx| {
5829 editor.handle_input(">", cx);
5830 });
5831 cx.assert_editor_state(
5832 &r#"
5833 <body><a b={c()d}>ˇ
5834 <script>
5835 var x = 1;<a b={c()d}>ˇ
5836 </script>
5837 </body><a b={c()d}>ˇ
5838 "#
5839 .unindent(),
5840 );
5841
5842 // Reset
5843 cx.set_state(
5844 &r#"
5845 <body>ˇ
5846 <script>
5847 var x = 1;ˇ
5848 </script>
5849 </body>ˇ
5850 "#
5851 .unindent(),
5852 );
5853
5854 cx.update_editor(|editor, cx| {
5855 editor.handle_input("<", cx);
5856 });
5857 cx.assert_editor_state(
5858 &r#"
5859 <body><ˇ>
5860 <script>
5861 var x = 1;<ˇ
5862 </script>
5863 </body><ˇ>
5864 "#
5865 .unindent(),
5866 );
5867
5868 // When backspacing, the closing angle brackets are removed.
5869 cx.update_editor(|editor, cx| {
5870 editor.backspace(&Backspace, cx);
5871 });
5872 cx.assert_editor_state(
5873 &r#"
5874 <body>ˇ
5875 <script>
5876 var x = 1;ˇ
5877 </script>
5878 </body>ˇ
5879 "#
5880 .unindent(),
5881 );
5882
5883 // Block comments autoclose in JavaScript, but not HTML.
5884 cx.update_editor(|editor, cx| {
5885 editor.handle_input("/", cx);
5886 editor.handle_input("*", cx);
5887 });
5888 cx.assert_editor_state(
5889 &r#"
5890 <body>/*ˇ
5891 <script>
5892 var x = 1;/*ˇ */
5893 </script>
5894 </body>/*ˇ
5895 "#
5896 .unindent(),
5897 );
5898}
5899
5900#[gpui::test]
5901async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5902 init_test(cx, |_| {});
5903
5904 let mut cx = EditorTestContext::new(cx).await;
5905
5906 let rust_language = Arc::new(
5907 Language::new(
5908 LanguageConfig {
5909 name: "Rust".into(),
5910 brackets: serde_json::from_value(json!([
5911 { "start": "{", "end": "}", "close": true, "newline": true },
5912 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5913 ]))
5914 .unwrap(),
5915 autoclose_before: "})]>".into(),
5916 ..Default::default()
5917 },
5918 Some(tree_sitter_rust::LANGUAGE.into()),
5919 )
5920 .with_override_query("(string_literal) @string")
5921 .unwrap(),
5922 );
5923
5924 cx.language_registry().add(rust_language.clone());
5925 cx.update_buffer(|buffer, cx| {
5926 buffer.set_language(Some(rust_language), cx);
5927 });
5928
5929 cx.set_state(
5930 &r#"
5931 let x = ˇ
5932 "#
5933 .unindent(),
5934 );
5935
5936 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5937 cx.update_editor(|editor, cx| {
5938 editor.handle_input("\"", cx);
5939 });
5940 cx.assert_editor_state(
5941 &r#"
5942 let x = "ˇ"
5943 "#
5944 .unindent(),
5945 );
5946
5947 // Inserting another quotation mark. The cursor moves across the existing
5948 // automatically-inserted quotation mark.
5949 cx.update_editor(|editor, cx| {
5950 editor.handle_input("\"", cx);
5951 });
5952 cx.assert_editor_state(
5953 &r#"
5954 let x = ""ˇ
5955 "#
5956 .unindent(),
5957 );
5958
5959 // Reset
5960 cx.set_state(
5961 &r#"
5962 let x = ˇ
5963 "#
5964 .unindent(),
5965 );
5966
5967 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5968 cx.update_editor(|editor, cx| {
5969 editor.handle_input("\"", cx);
5970 editor.handle_input(" ", cx);
5971 editor.move_left(&Default::default(), cx);
5972 editor.handle_input("\\", cx);
5973 editor.handle_input("\"", cx);
5974 });
5975 cx.assert_editor_state(
5976 &r#"
5977 let x = "\"ˇ "
5978 "#
5979 .unindent(),
5980 );
5981
5982 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5983 // mark. Nothing is inserted.
5984 cx.update_editor(|editor, cx| {
5985 editor.move_right(&Default::default(), cx);
5986 editor.handle_input("\"", cx);
5987 });
5988 cx.assert_editor_state(
5989 &r#"
5990 let x = "\" "ˇ
5991 "#
5992 .unindent(),
5993 );
5994}
5995
5996#[gpui::test]
5997async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5998 init_test(cx, |_| {});
5999
6000 let language = Arc::new(Language::new(
6001 LanguageConfig {
6002 brackets: BracketPairConfig {
6003 pairs: vec![
6004 BracketPair {
6005 start: "{".to_string(),
6006 end: "}".to_string(),
6007 close: true,
6008 surround: true,
6009 newline: true,
6010 },
6011 BracketPair {
6012 start: "/* ".to_string(),
6013 end: "*/".to_string(),
6014 close: true,
6015 surround: true,
6016 ..Default::default()
6017 },
6018 ],
6019 ..Default::default()
6020 },
6021 ..Default::default()
6022 },
6023 Some(tree_sitter_rust::LANGUAGE.into()),
6024 ));
6025
6026 let text = r#"
6027 a
6028 b
6029 c
6030 "#
6031 .unindent();
6032
6033 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6034 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6035 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6036 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6037 .await;
6038
6039 view.update(cx, |view, cx| {
6040 view.change_selections(None, cx, |s| {
6041 s.select_display_ranges([
6042 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6043 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6044 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6045 ])
6046 });
6047
6048 view.handle_input("{", cx);
6049 view.handle_input("{", cx);
6050 view.handle_input("{", cx);
6051 assert_eq!(
6052 view.text(cx),
6053 "
6054 {{{a}}}
6055 {{{b}}}
6056 {{{c}}}
6057 "
6058 .unindent()
6059 );
6060 assert_eq!(
6061 view.selections.display_ranges(cx),
6062 [
6063 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6064 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6065 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6066 ]
6067 );
6068
6069 view.undo(&Undo, cx);
6070 view.undo(&Undo, cx);
6071 view.undo(&Undo, cx);
6072 assert_eq!(
6073 view.text(cx),
6074 "
6075 a
6076 b
6077 c
6078 "
6079 .unindent()
6080 );
6081 assert_eq!(
6082 view.selections.display_ranges(cx),
6083 [
6084 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6085 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6086 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6087 ]
6088 );
6089
6090 // Ensure inserting the first character of a multi-byte bracket pair
6091 // doesn't surround the selections with the bracket.
6092 view.handle_input("/", cx);
6093 assert_eq!(
6094 view.text(cx),
6095 "
6096 /
6097 /
6098 /
6099 "
6100 .unindent()
6101 );
6102 assert_eq!(
6103 view.selections.display_ranges(cx),
6104 [
6105 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6106 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6107 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6108 ]
6109 );
6110
6111 view.undo(&Undo, cx);
6112 assert_eq!(
6113 view.text(cx),
6114 "
6115 a
6116 b
6117 c
6118 "
6119 .unindent()
6120 );
6121 assert_eq!(
6122 view.selections.display_ranges(cx),
6123 [
6124 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6125 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6126 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6127 ]
6128 );
6129
6130 // Ensure inserting the last character of a multi-byte bracket pair
6131 // doesn't surround the selections with the bracket.
6132 view.handle_input("*", cx);
6133 assert_eq!(
6134 view.text(cx),
6135 "
6136 *
6137 *
6138 *
6139 "
6140 .unindent()
6141 );
6142 assert_eq!(
6143 view.selections.display_ranges(cx),
6144 [
6145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6146 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6147 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6148 ]
6149 );
6150 });
6151}
6152
6153#[gpui::test]
6154async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6155 init_test(cx, |_| {});
6156
6157 let language = Arc::new(Language::new(
6158 LanguageConfig {
6159 brackets: BracketPairConfig {
6160 pairs: vec![BracketPair {
6161 start: "{".to_string(),
6162 end: "}".to_string(),
6163 close: true,
6164 surround: true,
6165 newline: true,
6166 }],
6167 ..Default::default()
6168 },
6169 autoclose_before: "}".to_string(),
6170 ..Default::default()
6171 },
6172 Some(tree_sitter_rust::LANGUAGE.into()),
6173 ));
6174
6175 let text = r#"
6176 a
6177 b
6178 c
6179 "#
6180 .unindent();
6181
6182 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6183 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6184 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6185 editor
6186 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6187 .await;
6188
6189 editor.update(cx, |editor, cx| {
6190 editor.change_selections(None, cx, |s| {
6191 s.select_ranges([
6192 Point::new(0, 1)..Point::new(0, 1),
6193 Point::new(1, 1)..Point::new(1, 1),
6194 Point::new(2, 1)..Point::new(2, 1),
6195 ])
6196 });
6197
6198 editor.handle_input("{", cx);
6199 editor.handle_input("{", cx);
6200 editor.handle_input("_", cx);
6201 assert_eq!(
6202 editor.text(cx),
6203 "
6204 a{{_}}
6205 b{{_}}
6206 c{{_}}
6207 "
6208 .unindent()
6209 );
6210 assert_eq!(
6211 editor.selections.ranges::<Point>(cx),
6212 [
6213 Point::new(0, 4)..Point::new(0, 4),
6214 Point::new(1, 4)..Point::new(1, 4),
6215 Point::new(2, 4)..Point::new(2, 4)
6216 ]
6217 );
6218
6219 editor.backspace(&Default::default(), cx);
6220 editor.backspace(&Default::default(), cx);
6221 assert_eq!(
6222 editor.text(cx),
6223 "
6224 a{}
6225 b{}
6226 c{}
6227 "
6228 .unindent()
6229 );
6230 assert_eq!(
6231 editor.selections.ranges::<Point>(cx),
6232 [
6233 Point::new(0, 2)..Point::new(0, 2),
6234 Point::new(1, 2)..Point::new(1, 2),
6235 Point::new(2, 2)..Point::new(2, 2)
6236 ]
6237 );
6238
6239 editor.delete_to_previous_word_start(&Default::default(), cx);
6240 assert_eq!(
6241 editor.text(cx),
6242 "
6243 a
6244 b
6245 c
6246 "
6247 .unindent()
6248 );
6249 assert_eq!(
6250 editor.selections.ranges::<Point>(cx),
6251 [
6252 Point::new(0, 1)..Point::new(0, 1),
6253 Point::new(1, 1)..Point::new(1, 1),
6254 Point::new(2, 1)..Point::new(2, 1)
6255 ]
6256 );
6257 });
6258}
6259
6260#[gpui::test]
6261async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6262 init_test(cx, |settings| {
6263 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6264 });
6265
6266 let mut cx = EditorTestContext::new(cx).await;
6267
6268 let language = Arc::new(Language::new(
6269 LanguageConfig {
6270 brackets: BracketPairConfig {
6271 pairs: vec![
6272 BracketPair {
6273 start: "{".to_string(),
6274 end: "}".to_string(),
6275 close: true,
6276 surround: true,
6277 newline: true,
6278 },
6279 BracketPair {
6280 start: "(".to_string(),
6281 end: ")".to_string(),
6282 close: true,
6283 surround: true,
6284 newline: true,
6285 },
6286 BracketPair {
6287 start: "[".to_string(),
6288 end: "]".to_string(),
6289 close: false,
6290 surround: true,
6291 newline: true,
6292 },
6293 ],
6294 ..Default::default()
6295 },
6296 autoclose_before: "})]".to_string(),
6297 ..Default::default()
6298 },
6299 Some(tree_sitter_rust::LANGUAGE.into()),
6300 ));
6301
6302 cx.language_registry().add(language.clone());
6303 cx.update_buffer(|buffer, cx| {
6304 buffer.set_language(Some(language), cx);
6305 });
6306
6307 cx.set_state(
6308 &"
6309 {(ˇ)}
6310 [[ˇ]]
6311 {(ˇ)}
6312 "
6313 .unindent(),
6314 );
6315
6316 cx.update_editor(|view, cx| {
6317 view.backspace(&Default::default(), cx);
6318 view.backspace(&Default::default(), cx);
6319 });
6320
6321 cx.assert_editor_state(
6322 &"
6323 ˇ
6324 ˇ]]
6325 ˇ
6326 "
6327 .unindent(),
6328 );
6329
6330 cx.update_editor(|view, cx| {
6331 view.handle_input("{", cx);
6332 view.handle_input("{", cx);
6333 view.move_right(&MoveRight, cx);
6334 view.move_right(&MoveRight, cx);
6335 view.move_left(&MoveLeft, cx);
6336 view.move_left(&MoveLeft, cx);
6337 view.backspace(&Default::default(), cx);
6338 });
6339
6340 cx.assert_editor_state(
6341 &"
6342 {ˇ}
6343 {ˇ}]]
6344 {ˇ}
6345 "
6346 .unindent(),
6347 );
6348
6349 cx.update_editor(|view, cx| {
6350 view.backspace(&Default::default(), cx);
6351 });
6352
6353 cx.assert_editor_state(
6354 &"
6355 ˇ
6356 ˇ]]
6357 ˇ
6358 "
6359 .unindent(),
6360 );
6361}
6362
6363#[gpui::test]
6364async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6365 init_test(cx, |_| {});
6366
6367 let language = Arc::new(Language::new(
6368 LanguageConfig::default(),
6369 Some(tree_sitter_rust::LANGUAGE.into()),
6370 ));
6371
6372 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6373 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6374 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6375 editor
6376 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6377 .await;
6378
6379 editor.update(cx, |editor, cx| {
6380 editor.set_auto_replace_emoji_shortcode(true);
6381
6382 editor.handle_input("Hello ", cx);
6383 editor.handle_input(":wave", cx);
6384 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6385
6386 editor.handle_input(":", cx);
6387 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6388
6389 editor.handle_input(" :smile", cx);
6390 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6391
6392 editor.handle_input(":", cx);
6393 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6394
6395 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6396 editor.handle_input(":wave", cx);
6397 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6398
6399 editor.handle_input(":", cx);
6400 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6401
6402 editor.handle_input(":1", cx);
6403 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6404
6405 editor.handle_input(":", cx);
6406 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6407
6408 // Ensure shortcode does not get replaced when it is part of a word
6409 editor.handle_input(" Test:wave", cx);
6410 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6411
6412 editor.handle_input(":", cx);
6413 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6414
6415 editor.set_auto_replace_emoji_shortcode(false);
6416
6417 // Ensure shortcode does not get replaced when auto replace is off
6418 editor.handle_input(" :wave", cx);
6419 assert_eq!(
6420 editor.text(cx),
6421 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6422 );
6423
6424 editor.handle_input(":", cx);
6425 assert_eq!(
6426 editor.text(cx),
6427 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6428 );
6429 });
6430}
6431
6432#[gpui::test]
6433async fn test_snippets(cx: &mut gpui::TestAppContext) {
6434 init_test(cx, |_| {});
6435
6436 let (text, insertion_ranges) = marked_text_ranges(
6437 indoc! {"
6438 a.ˇ b
6439 a.ˇ b
6440 a.ˇ b
6441 "},
6442 false,
6443 );
6444
6445 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6446 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6447
6448 editor.update(cx, |editor, cx| {
6449 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6450
6451 editor
6452 .insert_snippet(&insertion_ranges, snippet, cx)
6453 .unwrap();
6454
6455 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6456 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6457 assert_eq!(editor.text(cx), expected_text);
6458 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6459 }
6460
6461 assert(
6462 editor,
6463 cx,
6464 indoc! {"
6465 a.f(«one», two, «three») b
6466 a.f(«one», two, «three») b
6467 a.f(«one», two, «three») b
6468 "},
6469 );
6470
6471 // Can't move earlier than the first tab stop
6472 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6473 assert(
6474 editor,
6475 cx,
6476 indoc! {"
6477 a.f(«one», two, «three») b
6478 a.f(«one», two, «three») b
6479 a.f(«one», two, «three») b
6480 "},
6481 );
6482
6483 assert!(editor.move_to_next_snippet_tabstop(cx));
6484 assert(
6485 editor,
6486 cx,
6487 indoc! {"
6488 a.f(one, «two», three) b
6489 a.f(one, «two», three) b
6490 a.f(one, «two», three) b
6491 "},
6492 );
6493
6494 editor.move_to_prev_snippet_tabstop(cx);
6495 assert(
6496 editor,
6497 cx,
6498 indoc! {"
6499 a.f(«one», two, «three») b
6500 a.f(«one», two, «three») b
6501 a.f(«one», two, «three») b
6502 "},
6503 );
6504
6505 assert!(editor.move_to_next_snippet_tabstop(cx));
6506 assert(
6507 editor,
6508 cx,
6509 indoc! {"
6510 a.f(one, «two», three) b
6511 a.f(one, «two», three) b
6512 a.f(one, «two», three) b
6513 "},
6514 );
6515 assert!(editor.move_to_next_snippet_tabstop(cx));
6516 assert(
6517 editor,
6518 cx,
6519 indoc! {"
6520 a.f(one, two, three)ˇ b
6521 a.f(one, two, three)ˇ b
6522 a.f(one, two, three)ˇ b
6523 "},
6524 );
6525
6526 // As soon as the last tab stop is reached, snippet state is gone
6527 editor.move_to_prev_snippet_tabstop(cx);
6528 assert(
6529 editor,
6530 cx,
6531 indoc! {"
6532 a.f(one, two, three)ˇ b
6533 a.f(one, two, three)ˇ b
6534 a.f(one, two, three)ˇ b
6535 "},
6536 );
6537 });
6538}
6539
6540#[gpui::test]
6541async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6542 init_test(cx, |_| {});
6543
6544 let fs = FakeFs::new(cx.executor());
6545 fs.insert_file("/file.rs", Default::default()).await;
6546
6547 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6548
6549 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6550 language_registry.add(rust_lang());
6551 let mut fake_servers = language_registry.register_fake_lsp(
6552 "Rust",
6553 FakeLspAdapter {
6554 capabilities: lsp::ServerCapabilities {
6555 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6556 ..Default::default()
6557 },
6558 ..Default::default()
6559 },
6560 );
6561
6562 let buffer = project
6563 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6564 .await
6565 .unwrap();
6566
6567 cx.executor().start_waiting();
6568 let fake_server = fake_servers.next().await.unwrap();
6569
6570 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6571 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6572 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6573 assert!(cx.read(|cx| editor.is_dirty(cx)));
6574
6575 let save = editor
6576 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6577 .unwrap();
6578 fake_server
6579 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6580 assert_eq!(
6581 params.text_document.uri,
6582 lsp::Url::from_file_path("/file.rs").unwrap()
6583 );
6584 assert_eq!(params.options.tab_size, 4);
6585 Ok(Some(vec![lsp::TextEdit::new(
6586 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6587 ", ".to_string(),
6588 )]))
6589 })
6590 .next()
6591 .await;
6592 cx.executor().start_waiting();
6593 save.await;
6594
6595 assert_eq!(
6596 editor.update(cx, |editor, cx| editor.text(cx)),
6597 "one, two\nthree\n"
6598 );
6599 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6600
6601 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6602 assert!(cx.read(|cx| editor.is_dirty(cx)));
6603
6604 // Ensure we can still save even if formatting hangs.
6605 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6606 assert_eq!(
6607 params.text_document.uri,
6608 lsp::Url::from_file_path("/file.rs").unwrap()
6609 );
6610 futures::future::pending::<()>().await;
6611 unreachable!()
6612 });
6613 let save = editor
6614 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6615 .unwrap();
6616 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6617 cx.executor().start_waiting();
6618 save.await;
6619 assert_eq!(
6620 editor.update(cx, |editor, cx| editor.text(cx)),
6621 "one\ntwo\nthree\n"
6622 );
6623 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6624
6625 // For non-dirty buffer, no formatting request should be sent
6626 let save = editor
6627 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6628 .unwrap();
6629 let _pending_format_request = fake_server
6630 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6631 panic!("Should not be invoked on non-dirty buffer");
6632 })
6633 .next();
6634 cx.executor().start_waiting();
6635 save.await;
6636
6637 // Set rust language override and assert overridden tabsize is sent to language server
6638 update_test_language_settings(cx, |settings| {
6639 settings.languages.insert(
6640 "Rust".into(),
6641 LanguageSettingsContent {
6642 tab_size: NonZeroU32::new(8),
6643 ..Default::default()
6644 },
6645 );
6646 });
6647
6648 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6649 assert!(cx.read(|cx| editor.is_dirty(cx)));
6650 let save = editor
6651 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6652 .unwrap();
6653 fake_server
6654 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6655 assert_eq!(
6656 params.text_document.uri,
6657 lsp::Url::from_file_path("/file.rs").unwrap()
6658 );
6659 assert_eq!(params.options.tab_size, 8);
6660 Ok(Some(vec![]))
6661 })
6662 .next()
6663 .await;
6664 cx.executor().start_waiting();
6665 save.await;
6666}
6667
6668#[gpui::test]
6669async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6670 init_test(cx, |_| {});
6671
6672 let cols = 4;
6673 let rows = 10;
6674 let sample_text_1 = sample_text(rows, cols, 'a');
6675 assert_eq!(
6676 sample_text_1,
6677 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6678 );
6679 let sample_text_2 = sample_text(rows, cols, 'l');
6680 assert_eq!(
6681 sample_text_2,
6682 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6683 );
6684 let sample_text_3 = sample_text(rows, cols, 'v');
6685 assert_eq!(
6686 sample_text_3,
6687 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6688 );
6689
6690 let fs = FakeFs::new(cx.executor());
6691 fs.insert_tree(
6692 "/a",
6693 json!({
6694 "main.rs": sample_text_1,
6695 "other.rs": sample_text_2,
6696 "lib.rs": sample_text_3,
6697 }),
6698 )
6699 .await;
6700
6701 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6702 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6703 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6704
6705 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6706 language_registry.add(rust_lang());
6707 let mut fake_servers = language_registry.register_fake_lsp(
6708 "Rust",
6709 FakeLspAdapter {
6710 capabilities: lsp::ServerCapabilities {
6711 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6712 ..Default::default()
6713 },
6714 ..Default::default()
6715 },
6716 );
6717
6718 let worktree = project.update(cx, |project, cx| {
6719 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6720 assert_eq!(worktrees.len(), 1);
6721 worktrees.pop().unwrap()
6722 });
6723 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6724
6725 let buffer_1 = project
6726 .update(cx, |project, cx| {
6727 project.open_buffer((worktree_id, "main.rs"), cx)
6728 })
6729 .await
6730 .unwrap();
6731 let buffer_2 = project
6732 .update(cx, |project, cx| {
6733 project.open_buffer((worktree_id, "other.rs"), cx)
6734 })
6735 .await
6736 .unwrap();
6737 let buffer_3 = project
6738 .update(cx, |project, cx| {
6739 project.open_buffer((worktree_id, "lib.rs"), cx)
6740 })
6741 .await
6742 .unwrap();
6743
6744 let multi_buffer = cx.new_model(|cx| {
6745 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6746 multi_buffer.push_excerpts(
6747 buffer_1.clone(),
6748 [
6749 ExcerptRange {
6750 context: Point::new(0, 0)..Point::new(3, 0),
6751 primary: None,
6752 },
6753 ExcerptRange {
6754 context: Point::new(5, 0)..Point::new(7, 0),
6755 primary: None,
6756 },
6757 ExcerptRange {
6758 context: Point::new(9, 0)..Point::new(10, 4),
6759 primary: None,
6760 },
6761 ],
6762 cx,
6763 );
6764 multi_buffer.push_excerpts(
6765 buffer_2.clone(),
6766 [
6767 ExcerptRange {
6768 context: Point::new(0, 0)..Point::new(3, 0),
6769 primary: None,
6770 },
6771 ExcerptRange {
6772 context: Point::new(5, 0)..Point::new(7, 0),
6773 primary: None,
6774 },
6775 ExcerptRange {
6776 context: Point::new(9, 0)..Point::new(10, 4),
6777 primary: None,
6778 },
6779 ],
6780 cx,
6781 );
6782 multi_buffer.push_excerpts(
6783 buffer_3.clone(),
6784 [
6785 ExcerptRange {
6786 context: Point::new(0, 0)..Point::new(3, 0),
6787 primary: None,
6788 },
6789 ExcerptRange {
6790 context: Point::new(5, 0)..Point::new(7, 0),
6791 primary: None,
6792 },
6793 ExcerptRange {
6794 context: Point::new(9, 0)..Point::new(10, 4),
6795 primary: None,
6796 },
6797 ],
6798 cx,
6799 );
6800 multi_buffer
6801 });
6802 let multi_buffer_editor = cx.new_view(|cx| {
6803 Editor::new(
6804 EditorMode::Full,
6805 multi_buffer,
6806 Some(project.clone()),
6807 true,
6808 cx,
6809 )
6810 });
6811
6812 multi_buffer_editor.update(cx, |editor, cx| {
6813 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6814 editor.insert("|one|two|three|", cx);
6815 });
6816 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6817 multi_buffer_editor.update(cx, |editor, cx| {
6818 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6819 s.select_ranges(Some(60..70))
6820 });
6821 editor.insert("|four|five|six|", cx);
6822 });
6823 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6824
6825 // First two buffers should be edited, but not the third one.
6826 assert_eq!(
6827 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6828 "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}",
6829 );
6830 buffer_1.update(cx, |buffer, _| {
6831 assert!(buffer.is_dirty());
6832 assert_eq!(
6833 buffer.text(),
6834 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6835 )
6836 });
6837 buffer_2.update(cx, |buffer, _| {
6838 assert!(buffer.is_dirty());
6839 assert_eq!(
6840 buffer.text(),
6841 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6842 )
6843 });
6844 buffer_3.update(cx, |buffer, _| {
6845 assert!(!buffer.is_dirty());
6846 assert_eq!(buffer.text(), sample_text_3,)
6847 });
6848
6849 cx.executor().start_waiting();
6850 let save = multi_buffer_editor
6851 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6852 .unwrap();
6853
6854 let fake_server = fake_servers.next().await.unwrap();
6855 fake_server
6856 .server
6857 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6858 Ok(Some(vec![lsp::TextEdit::new(
6859 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6860 format!("[{} formatted]", params.text_document.uri),
6861 )]))
6862 })
6863 .detach();
6864 save.await;
6865
6866 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6867 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6868 assert_eq!(
6869 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6870 "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}",
6871 );
6872 buffer_1.update(cx, |buffer, _| {
6873 assert!(!buffer.is_dirty());
6874 assert_eq!(
6875 buffer.text(),
6876 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6877 )
6878 });
6879 buffer_2.update(cx, |buffer, _| {
6880 assert!(!buffer.is_dirty());
6881 assert_eq!(
6882 buffer.text(),
6883 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6884 )
6885 });
6886 buffer_3.update(cx, |buffer, _| {
6887 assert!(!buffer.is_dirty());
6888 assert_eq!(buffer.text(), sample_text_3,)
6889 });
6890}
6891
6892#[gpui::test]
6893async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6894 init_test(cx, |_| {});
6895
6896 let fs = FakeFs::new(cx.executor());
6897 fs.insert_file("/file.rs", Default::default()).await;
6898
6899 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6900
6901 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6902 language_registry.add(rust_lang());
6903 let mut fake_servers = language_registry.register_fake_lsp(
6904 "Rust",
6905 FakeLspAdapter {
6906 capabilities: lsp::ServerCapabilities {
6907 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6908 ..Default::default()
6909 },
6910 ..Default::default()
6911 },
6912 );
6913
6914 let buffer = project
6915 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6916 .await
6917 .unwrap();
6918
6919 cx.executor().start_waiting();
6920 let fake_server = fake_servers.next().await.unwrap();
6921
6922 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6923 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6924 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6925 assert!(cx.read(|cx| editor.is_dirty(cx)));
6926
6927 let save = editor
6928 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6929 .unwrap();
6930 fake_server
6931 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6932 assert_eq!(
6933 params.text_document.uri,
6934 lsp::Url::from_file_path("/file.rs").unwrap()
6935 );
6936 assert_eq!(params.options.tab_size, 4);
6937 Ok(Some(vec![lsp::TextEdit::new(
6938 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6939 ", ".to_string(),
6940 )]))
6941 })
6942 .next()
6943 .await;
6944 cx.executor().start_waiting();
6945 save.await;
6946 assert_eq!(
6947 editor.update(cx, |editor, cx| editor.text(cx)),
6948 "one, two\nthree\n"
6949 );
6950 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6951
6952 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6953 assert!(cx.read(|cx| editor.is_dirty(cx)));
6954
6955 // Ensure we can still save even if formatting hangs.
6956 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6957 move |params, _| async move {
6958 assert_eq!(
6959 params.text_document.uri,
6960 lsp::Url::from_file_path("/file.rs").unwrap()
6961 );
6962 futures::future::pending::<()>().await;
6963 unreachable!()
6964 },
6965 );
6966 let save = editor
6967 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6968 .unwrap();
6969 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6970 cx.executor().start_waiting();
6971 save.await;
6972 assert_eq!(
6973 editor.update(cx, |editor, cx| editor.text(cx)),
6974 "one\ntwo\nthree\n"
6975 );
6976 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6977
6978 // For non-dirty buffer, no formatting request should be sent
6979 let save = editor
6980 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6981 .unwrap();
6982 let _pending_format_request = fake_server
6983 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6984 panic!("Should not be invoked on non-dirty buffer");
6985 })
6986 .next();
6987 cx.executor().start_waiting();
6988 save.await;
6989
6990 // Set Rust language override and assert overridden tabsize is sent to language server
6991 update_test_language_settings(cx, |settings| {
6992 settings.languages.insert(
6993 "Rust".into(),
6994 LanguageSettingsContent {
6995 tab_size: NonZeroU32::new(8),
6996 ..Default::default()
6997 },
6998 );
6999 });
7000
7001 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7002 assert!(cx.read(|cx| editor.is_dirty(cx)));
7003 let save = editor
7004 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7005 .unwrap();
7006 fake_server
7007 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7008 assert_eq!(
7009 params.text_document.uri,
7010 lsp::Url::from_file_path("/file.rs").unwrap()
7011 );
7012 assert_eq!(params.options.tab_size, 8);
7013 Ok(Some(vec![]))
7014 })
7015 .next()
7016 .await;
7017 cx.executor().start_waiting();
7018 save.await;
7019}
7020
7021#[gpui::test]
7022async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7023 init_test(cx, |settings| {
7024 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7025 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7026 ))
7027 });
7028
7029 let fs = FakeFs::new(cx.executor());
7030 fs.insert_file("/file.rs", Default::default()).await;
7031
7032 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7033
7034 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7035 language_registry.add(Arc::new(Language::new(
7036 LanguageConfig {
7037 name: "Rust".into(),
7038 matcher: LanguageMatcher {
7039 path_suffixes: vec!["rs".to_string()],
7040 ..Default::default()
7041 },
7042 ..LanguageConfig::default()
7043 },
7044 Some(tree_sitter_rust::LANGUAGE.into()),
7045 )));
7046 update_test_language_settings(cx, |settings| {
7047 // Enable Prettier formatting for the same buffer, and ensure
7048 // LSP is called instead of Prettier.
7049 settings.defaults.prettier = Some(PrettierSettings {
7050 allowed: true,
7051 ..PrettierSettings::default()
7052 });
7053 });
7054 let mut fake_servers = language_registry.register_fake_lsp(
7055 "Rust",
7056 FakeLspAdapter {
7057 capabilities: lsp::ServerCapabilities {
7058 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7059 ..Default::default()
7060 },
7061 ..Default::default()
7062 },
7063 );
7064
7065 let buffer = project
7066 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7067 .await
7068 .unwrap();
7069
7070 cx.executor().start_waiting();
7071 let fake_server = fake_servers.next().await.unwrap();
7072
7073 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7074 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7075 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7076
7077 let format = editor
7078 .update(cx, |editor, cx| {
7079 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
7080 })
7081 .unwrap();
7082 fake_server
7083 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7084 assert_eq!(
7085 params.text_document.uri,
7086 lsp::Url::from_file_path("/file.rs").unwrap()
7087 );
7088 assert_eq!(params.options.tab_size, 4);
7089 Ok(Some(vec![lsp::TextEdit::new(
7090 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7091 ", ".to_string(),
7092 )]))
7093 })
7094 .next()
7095 .await;
7096 cx.executor().start_waiting();
7097 format.await;
7098 assert_eq!(
7099 editor.update(cx, |editor, cx| editor.text(cx)),
7100 "one, two\nthree\n"
7101 );
7102
7103 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7104 // Ensure we don't lock if formatting hangs.
7105 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7106 assert_eq!(
7107 params.text_document.uri,
7108 lsp::Url::from_file_path("/file.rs").unwrap()
7109 );
7110 futures::future::pending::<()>().await;
7111 unreachable!()
7112 });
7113 let format = editor
7114 .update(cx, |editor, cx| {
7115 editor.perform_format(project, FormatTrigger::Manual, cx)
7116 })
7117 .unwrap();
7118 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7119 cx.executor().start_waiting();
7120 format.await;
7121 assert_eq!(
7122 editor.update(cx, |editor, cx| editor.text(cx)),
7123 "one\ntwo\nthree\n"
7124 );
7125}
7126
7127#[gpui::test]
7128async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7129 init_test(cx, |_| {});
7130
7131 let mut cx = EditorLspTestContext::new_rust(
7132 lsp::ServerCapabilities {
7133 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7134 ..Default::default()
7135 },
7136 cx,
7137 )
7138 .await;
7139
7140 cx.set_state(indoc! {"
7141 one.twoˇ
7142 "});
7143
7144 // The format request takes a long time. When it completes, it inserts
7145 // a newline and an indent before the `.`
7146 cx.lsp
7147 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7148 let executor = cx.background_executor().clone();
7149 async move {
7150 executor.timer(Duration::from_millis(100)).await;
7151 Ok(Some(vec![lsp::TextEdit {
7152 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7153 new_text: "\n ".into(),
7154 }]))
7155 }
7156 });
7157
7158 // Submit a format request.
7159 let format_1 = cx
7160 .update_editor(|editor, cx| editor.format(&Format, cx))
7161 .unwrap();
7162 cx.executor().run_until_parked();
7163
7164 // Submit a second format request.
7165 let format_2 = cx
7166 .update_editor(|editor, cx| editor.format(&Format, cx))
7167 .unwrap();
7168 cx.executor().run_until_parked();
7169
7170 // Wait for both format requests to complete
7171 cx.executor().advance_clock(Duration::from_millis(200));
7172 cx.executor().start_waiting();
7173 format_1.await.unwrap();
7174 cx.executor().start_waiting();
7175 format_2.await.unwrap();
7176
7177 // The formatting edits only happens once.
7178 cx.assert_editor_state(indoc! {"
7179 one
7180 .twoˇ
7181 "});
7182}
7183
7184#[gpui::test]
7185async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7186 init_test(cx, |settings| {
7187 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7188 });
7189
7190 let mut cx = EditorLspTestContext::new_rust(
7191 lsp::ServerCapabilities {
7192 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7193 ..Default::default()
7194 },
7195 cx,
7196 )
7197 .await;
7198
7199 // Set up a buffer white some trailing whitespace and no trailing newline.
7200 cx.set_state(
7201 &[
7202 "one ", //
7203 "twoˇ", //
7204 "three ", //
7205 "four", //
7206 ]
7207 .join("\n"),
7208 );
7209
7210 // Submit a format request.
7211 let format = cx
7212 .update_editor(|editor, cx| editor.format(&Format, cx))
7213 .unwrap();
7214
7215 // Record which buffer changes have been sent to the language server
7216 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7217 cx.lsp
7218 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7219 let buffer_changes = buffer_changes.clone();
7220 move |params, _| {
7221 buffer_changes.lock().extend(
7222 params
7223 .content_changes
7224 .into_iter()
7225 .map(|e| (e.range.unwrap(), e.text)),
7226 );
7227 }
7228 });
7229
7230 // Handle formatting requests to the language server.
7231 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7232 let buffer_changes = buffer_changes.clone();
7233 move |_, _| {
7234 // When formatting is requested, trailing whitespace has already been stripped,
7235 // and the trailing newline has already been added.
7236 assert_eq!(
7237 &buffer_changes.lock()[1..],
7238 &[
7239 (
7240 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7241 "".into()
7242 ),
7243 (
7244 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7245 "".into()
7246 ),
7247 (
7248 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7249 "\n".into()
7250 ),
7251 ]
7252 );
7253
7254 // Insert blank lines between each line of the buffer.
7255 async move {
7256 Ok(Some(vec![
7257 lsp::TextEdit {
7258 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7259 new_text: "\n".into(),
7260 },
7261 lsp::TextEdit {
7262 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7263 new_text: "\n".into(),
7264 },
7265 ]))
7266 }
7267 }
7268 });
7269
7270 // After formatting the buffer, the trailing whitespace is stripped,
7271 // a newline is appended, and the edits provided by the language server
7272 // have been applied.
7273 format.await.unwrap();
7274 cx.assert_editor_state(
7275 &[
7276 "one", //
7277 "", //
7278 "twoˇ", //
7279 "", //
7280 "three", //
7281 "four", //
7282 "", //
7283 ]
7284 .join("\n"),
7285 );
7286
7287 // Undoing the formatting undoes the trailing whitespace removal, the
7288 // trailing newline, and the LSP edits.
7289 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7290 cx.assert_editor_state(
7291 &[
7292 "one ", //
7293 "twoˇ", //
7294 "three ", //
7295 "four", //
7296 ]
7297 .join("\n"),
7298 );
7299}
7300
7301#[gpui::test]
7302async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7303 cx: &mut gpui::TestAppContext,
7304) {
7305 init_test(cx, |_| {});
7306
7307 cx.update(|cx| {
7308 cx.update_global::<SettingsStore, _>(|settings, cx| {
7309 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7310 settings.auto_signature_help = Some(true);
7311 });
7312 });
7313 });
7314
7315 let mut cx = EditorLspTestContext::new_rust(
7316 lsp::ServerCapabilities {
7317 signature_help_provider: Some(lsp::SignatureHelpOptions {
7318 ..Default::default()
7319 }),
7320 ..Default::default()
7321 },
7322 cx,
7323 )
7324 .await;
7325
7326 let language = Language::new(
7327 LanguageConfig {
7328 name: "Rust".into(),
7329 brackets: BracketPairConfig {
7330 pairs: vec![
7331 BracketPair {
7332 start: "{".to_string(),
7333 end: "}".to_string(),
7334 close: true,
7335 surround: true,
7336 newline: true,
7337 },
7338 BracketPair {
7339 start: "(".to_string(),
7340 end: ")".to_string(),
7341 close: true,
7342 surround: true,
7343 newline: true,
7344 },
7345 BracketPair {
7346 start: "/*".to_string(),
7347 end: " */".to_string(),
7348 close: true,
7349 surround: true,
7350 newline: true,
7351 },
7352 BracketPair {
7353 start: "[".to_string(),
7354 end: "]".to_string(),
7355 close: false,
7356 surround: false,
7357 newline: true,
7358 },
7359 BracketPair {
7360 start: "\"".to_string(),
7361 end: "\"".to_string(),
7362 close: true,
7363 surround: true,
7364 newline: false,
7365 },
7366 BracketPair {
7367 start: "<".to_string(),
7368 end: ">".to_string(),
7369 close: false,
7370 surround: true,
7371 newline: true,
7372 },
7373 ],
7374 ..Default::default()
7375 },
7376 autoclose_before: "})]".to_string(),
7377 ..Default::default()
7378 },
7379 Some(tree_sitter_rust::LANGUAGE.into()),
7380 );
7381 let language = Arc::new(language);
7382
7383 cx.language_registry().add(language.clone());
7384 cx.update_buffer(|buffer, cx| {
7385 buffer.set_language(Some(language), cx);
7386 });
7387
7388 cx.set_state(
7389 &r#"
7390 fn main() {
7391 sampleˇ
7392 }
7393 "#
7394 .unindent(),
7395 );
7396
7397 cx.update_editor(|view, cx| {
7398 view.handle_input("(", cx);
7399 });
7400 cx.assert_editor_state(
7401 &"
7402 fn main() {
7403 sample(ˇ)
7404 }
7405 "
7406 .unindent(),
7407 );
7408
7409 let mocked_response = lsp::SignatureHelp {
7410 signatures: vec![lsp::SignatureInformation {
7411 label: "fn sample(param1: u8, param2: u8)".to_string(),
7412 documentation: None,
7413 parameters: Some(vec![
7414 lsp::ParameterInformation {
7415 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7416 documentation: None,
7417 },
7418 lsp::ParameterInformation {
7419 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7420 documentation: None,
7421 },
7422 ]),
7423 active_parameter: None,
7424 }],
7425 active_signature: Some(0),
7426 active_parameter: Some(0),
7427 };
7428 handle_signature_help_request(&mut cx, mocked_response).await;
7429
7430 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7431 .await;
7432
7433 cx.editor(|editor, _| {
7434 let signature_help_state = editor.signature_help_state.popover().cloned();
7435 assert!(signature_help_state.is_some());
7436 let ParsedMarkdown {
7437 text, highlights, ..
7438 } = signature_help_state.unwrap().parsed_content;
7439 assert_eq!(text, "param1: u8, param2: u8");
7440 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7441 });
7442}
7443
7444#[gpui::test]
7445async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7446 init_test(cx, |_| {});
7447
7448 cx.update(|cx| {
7449 cx.update_global::<SettingsStore, _>(|settings, cx| {
7450 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7451 settings.auto_signature_help = Some(false);
7452 settings.show_signature_help_after_edits = Some(false);
7453 });
7454 });
7455 });
7456
7457 let mut cx = EditorLspTestContext::new_rust(
7458 lsp::ServerCapabilities {
7459 signature_help_provider: Some(lsp::SignatureHelpOptions {
7460 ..Default::default()
7461 }),
7462 ..Default::default()
7463 },
7464 cx,
7465 )
7466 .await;
7467
7468 let language = Language::new(
7469 LanguageConfig {
7470 name: "Rust".into(),
7471 brackets: BracketPairConfig {
7472 pairs: vec![
7473 BracketPair {
7474 start: "{".to_string(),
7475 end: "}".to_string(),
7476 close: true,
7477 surround: true,
7478 newline: true,
7479 },
7480 BracketPair {
7481 start: "(".to_string(),
7482 end: ")".to_string(),
7483 close: true,
7484 surround: true,
7485 newline: true,
7486 },
7487 BracketPair {
7488 start: "/*".to_string(),
7489 end: " */".to_string(),
7490 close: true,
7491 surround: true,
7492 newline: true,
7493 },
7494 BracketPair {
7495 start: "[".to_string(),
7496 end: "]".to_string(),
7497 close: false,
7498 surround: false,
7499 newline: true,
7500 },
7501 BracketPair {
7502 start: "\"".to_string(),
7503 end: "\"".to_string(),
7504 close: true,
7505 surround: true,
7506 newline: false,
7507 },
7508 BracketPair {
7509 start: "<".to_string(),
7510 end: ">".to_string(),
7511 close: false,
7512 surround: true,
7513 newline: true,
7514 },
7515 ],
7516 ..Default::default()
7517 },
7518 autoclose_before: "})]".to_string(),
7519 ..Default::default()
7520 },
7521 Some(tree_sitter_rust::LANGUAGE.into()),
7522 );
7523 let language = Arc::new(language);
7524
7525 cx.language_registry().add(language.clone());
7526 cx.update_buffer(|buffer, cx| {
7527 buffer.set_language(Some(language), cx);
7528 });
7529
7530 // Ensure that signature_help is not called when no signature help is enabled.
7531 cx.set_state(
7532 &r#"
7533 fn main() {
7534 sampleˇ
7535 }
7536 "#
7537 .unindent(),
7538 );
7539 cx.update_editor(|view, cx| {
7540 view.handle_input("(", cx);
7541 });
7542 cx.assert_editor_state(
7543 &"
7544 fn main() {
7545 sample(ˇ)
7546 }
7547 "
7548 .unindent(),
7549 );
7550 cx.editor(|editor, _| {
7551 assert!(editor.signature_help_state.task().is_none());
7552 });
7553
7554 let mocked_response = lsp::SignatureHelp {
7555 signatures: vec![lsp::SignatureInformation {
7556 label: "fn sample(param1: u8, param2: u8)".to_string(),
7557 documentation: None,
7558 parameters: Some(vec![
7559 lsp::ParameterInformation {
7560 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7561 documentation: None,
7562 },
7563 lsp::ParameterInformation {
7564 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7565 documentation: None,
7566 },
7567 ]),
7568 active_parameter: None,
7569 }],
7570 active_signature: Some(0),
7571 active_parameter: Some(0),
7572 };
7573
7574 // Ensure that signature_help is called when enabled afte edits
7575 cx.update(|cx| {
7576 cx.update_global::<SettingsStore, _>(|settings, cx| {
7577 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7578 settings.auto_signature_help = Some(false);
7579 settings.show_signature_help_after_edits = Some(true);
7580 });
7581 });
7582 });
7583 cx.set_state(
7584 &r#"
7585 fn main() {
7586 sampleˇ
7587 }
7588 "#
7589 .unindent(),
7590 );
7591 cx.update_editor(|view, cx| {
7592 view.handle_input("(", cx);
7593 });
7594 cx.assert_editor_state(
7595 &"
7596 fn main() {
7597 sample(ˇ)
7598 }
7599 "
7600 .unindent(),
7601 );
7602 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7603 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7604 .await;
7605 cx.update_editor(|editor, _| {
7606 let signature_help_state = editor.signature_help_state.popover().cloned();
7607 assert!(signature_help_state.is_some());
7608 let ParsedMarkdown {
7609 text, highlights, ..
7610 } = signature_help_state.unwrap().parsed_content;
7611 assert_eq!(text, "param1: u8, param2: u8");
7612 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7613 editor.signature_help_state = SignatureHelpState::default();
7614 });
7615
7616 // Ensure that signature_help is called when auto signature help override is enabled
7617 cx.update(|cx| {
7618 cx.update_global::<SettingsStore, _>(|settings, cx| {
7619 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7620 settings.auto_signature_help = Some(true);
7621 settings.show_signature_help_after_edits = Some(false);
7622 });
7623 });
7624 });
7625 cx.set_state(
7626 &r#"
7627 fn main() {
7628 sampleˇ
7629 }
7630 "#
7631 .unindent(),
7632 );
7633 cx.update_editor(|view, cx| {
7634 view.handle_input("(", cx);
7635 });
7636 cx.assert_editor_state(
7637 &"
7638 fn main() {
7639 sample(ˇ)
7640 }
7641 "
7642 .unindent(),
7643 );
7644 handle_signature_help_request(&mut cx, mocked_response).await;
7645 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7646 .await;
7647 cx.editor(|editor, _| {
7648 let signature_help_state = editor.signature_help_state.popover().cloned();
7649 assert!(signature_help_state.is_some());
7650 let ParsedMarkdown {
7651 text, highlights, ..
7652 } = signature_help_state.unwrap().parsed_content;
7653 assert_eq!(text, "param1: u8, param2: u8");
7654 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7655 });
7656}
7657
7658#[gpui::test]
7659async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7660 init_test(cx, |_| {});
7661 cx.update(|cx| {
7662 cx.update_global::<SettingsStore, _>(|settings, cx| {
7663 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7664 settings.auto_signature_help = Some(true);
7665 });
7666 });
7667 });
7668
7669 let mut cx = EditorLspTestContext::new_rust(
7670 lsp::ServerCapabilities {
7671 signature_help_provider: Some(lsp::SignatureHelpOptions {
7672 ..Default::default()
7673 }),
7674 ..Default::default()
7675 },
7676 cx,
7677 )
7678 .await;
7679
7680 // A test that directly calls `show_signature_help`
7681 cx.update_editor(|editor, cx| {
7682 editor.show_signature_help(&ShowSignatureHelp, cx);
7683 });
7684
7685 let mocked_response = lsp::SignatureHelp {
7686 signatures: vec![lsp::SignatureInformation {
7687 label: "fn sample(param1: u8, param2: u8)".to_string(),
7688 documentation: None,
7689 parameters: Some(vec![
7690 lsp::ParameterInformation {
7691 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7692 documentation: None,
7693 },
7694 lsp::ParameterInformation {
7695 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7696 documentation: None,
7697 },
7698 ]),
7699 active_parameter: None,
7700 }],
7701 active_signature: Some(0),
7702 active_parameter: Some(0),
7703 };
7704 handle_signature_help_request(&mut cx, mocked_response).await;
7705
7706 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7707 .await;
7708
7709 cx.editor(|editor, _| {
7710 let signature_help_state = editor.signature_help_state.popover().cloned();
7711 assert!(signature_help_state.is_some());
7712 let ParsedMarkdown {
7713 text, highlights, ..
7714 } = signature_help_state.unwrap().parsed_content;
7715 assert_eq!(text, "param1: u8, param2: u8");
7716 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7717 });
7718
7719 // When exiting outside from inside the brackets, `signature_help` is closed.
7720 cx.set_state(indoc! {"
7721 fn main() {
7722 sample(ˇ);
7723 }
7724
7725 fn sample(param1: u8, param2: u8) {}
7726 "});
7727
7728 cx.update_editor(|editor, cx| {
7729 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7730 });
7731
7732 let mocked_response = lsp::SignatureHelp {
7733 signatures: Vec::new(),
7734 active_signature: None,
7735 active_parameter: None,
7736 };
7737 handle_signature_help_request(&mut cx, mocked_response).await;
7738
7739 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7740 .await;
7741
7742 cx.editor(|editor, _| {
7743 assert!(!editor.signature_help_state.is_shown());
7744 });
7745
7746 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7747 cx.set_state(indoc! {"
7748 fn main() {
7749 sample(ˇ);
7750 }
7751
7752 fn sample(param1: u8, param2: u8) {}
7753 "});
7754
7755 let mocked_response = lsp::SignatureHelp {
7756 signatures: vec![lsp::SignatureInformation {
7757 label: "fn sample(param1: u8, param2: u8)".to_string(),
7758 documentation: None,
7759 parameters: Some(vec![
7760 lsp::ParameterInformation {
7761 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7762 documentation: None,
7763 },
7764 lsp::ParameterInformation {
7765 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7766 documentation: None,
7767 },
7768 ]),
7769 active_parameter: None,
7770 }],
7771 active_signature: Some(0),
7772 active_parameter: Some(0),
7773 };
7774 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7775 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7776 .await;
7777 cx.editor(|editor, _| {
7778 assert!(editor.signature_help_state.is_shown());
7779 });
7780
7781 // Restore the popover with more parameter input
7782 cx.set_state(indoc! {"
7783 fn main() {
7784 sample(param1, param2ˇ);
7785 }
7786
7787 fn sample(param1: u8, param2: u8) {}
7788 "});
7789
7790 let mocked_response = lsp::SignatureHelp {
7791 signatures: vec![lsp::SignatureInformation {
7792 label: "fn sample(param1: u8, param2: u8)".to_string(),
7793 documentation: None,
7794 parameters: Some(vec![
7795 lsp::ParameterInformation {
7796 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7797 documentation: None,
7798 },
7799 lsp::ParameterInformation {
7800 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7801 documentation: None,
7802 },
7803 ]),
7804 active_parameter: None,
7805 }],
7806 active_signature: Some(0),
7807 active_parameter: Some(1),
7808 };
7809 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7810 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7811 .await;
7812
7813 // When selecting a range, the popover is gone.
7814 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7815 cx.update_editor(|editor, cx| {
7816 editor.change_selections(None, cx, |s| {
7817 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7818 })
7819 });
7820 cx.assert_editor_state(indoc! {"
7821 fn main() {
7822 sample(param1, «ˇparam2»);
7823 }
7824
7825 fn sample(param1: u8, param2: u8) {}
7826 "});
7827 cx.editor(|editor, _| {
7828 assert!(!editor.signature_help_state.is_shown());
7829 });
7830
7831 // When unselecting again, the popover is back if within the brackets.
7832 cx.update_editor(|editor, cx| {
7833 editor.change_selections(None, cx, |s| {
7834 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7835 })
7836 });
7837 cx.assert_editor_state(indoc! {"
7838 fn main() {
7839 sample(param1, ˇparam2);
7840 }
7841
7842 fn sample(param1: u8, param2: u8) {}
7843 "});
7844 handle_signature_help_request(&mut cx, mocked_response).await;
7845 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7846 .await;
7847 cx.editor(|editor, _| {
7848 assert!(editor.signature_help_state.is_shown());
7849 });
7850
7851 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7852 cx.update_editor(|editor, cx| {
7853 editor.change_selections(None, cx, |s| {
7854 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7855 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7856 })
7857 });
7858 cx.assert_editor_state(indoc! {"
7859 fn main() {
7860 sample(param1, ˇparam2);
7861 }
7862
7863 fn sample(param1: u8, param2: u8) {}
7864 "});
7865
7866 let mocked_response = lsp::SignatureHelp {
7867 signatures: vec![lsp::SignatureInformation {
7868 label: "fn sample(param1: u8, param2: u8)".to_string(),
7869 documentation: None,
7870 parameters: Some(vec![
7871 lsp::ParameterInformation {
7872 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7873 documentation: None,
7874 },
7875 lsp::ParameterInformation {
7876 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7877 documentation: None,
7878 },
7879 ]),
7880 active_parameter: None,
7881 }],
7882 active_signature: Some(0),
7883 active_parameter: Some(1),
7884 };
7885 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7886 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7887 .await;
7888 cx.update_editor(|editor, cx| {
7889 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7890 });
7891 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7892 .await;
7893 cx.update_editor(|editor, cx| {
7894 editor.change_selections(None, cx, |s| {
7895 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7896 })
7897 });
7898 cx.assert_editor_state(indoc! {"
7899 fn main() {
7900 sample(param1, «ˇparam2»);
7901 }
7902
7903 fn sample(param1: u8, param2: u8) {}
7904 "});
7905 cx.update_editor(|editor, cx| {
7906 editor.change_selections(None, cx, |s| {
7907 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7908 })
7909 });
7910 cx.assert_editor_state(indoc! {"
7911 fn main() {
7912 sample(param1, ˇparam2);
7913 }
7914
7915 fn sample(param1: u8, param2: u8) {}
7916 "});
7917 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7918 .await;
7919}
7920
7921#[gpui::test]
7922async fn test_completion(cx: &mut gpui::TestAppContext) {
7923 init_test(cx, |_| {});
7924
7925 let mut cx = EditorLspTestContext::new_rust(
7926 lsp::ServerCapabilities {
7927 completion_provider: Some(lsp::CompletionOptions {
7928 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7929 resolve_provider: Some(true),
7930 ..Default::default()
7931 }),
7932 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7933 ..Default::default()
7934 },
7935 cx,
7936 )
7937 .await;
7938 let counter = Arc::new(AtomicUsize::new(0));
7939
7940 cx.set_state(indoc! {"
7941 oneˇ
7942 two
7943 three
7944 "});
7945 cx.simulate_keystroke(".");
7946 handle_completion_request(
7947 &mut cx,
7948 indoc! {"
7949 one.|<>
7950 two
7951 three
7952 "},
7953 vec!["first_completion", "second_completion"],
7954 counter.clone(),
7955 )
7956 .await;
7957 cx.condition(|editor, _| editor.context_menu_visible())
7958 .await;
7959 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7960
7961 let _handler = handle_signature_help_request(
7962 &mut cx,
7963 lsp::SignatureHelp {
7964 signatures: vec![lsp::SignatureInformation {
7965 label: "test signature".to_string(),
7966 documentation: None,
7967 parameters: Some(vec![lsp::ParameterInformation {
7968 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7969 documentation: None,
7970 }]),
7971 active_parameter: None,
7972 }],
7973 active_signature: None,
7974 active_parameter: None,
7975 },
7976 );
7977 cx.update_editor(|editor, cx| {
7978 assert!(
7979 !editor.signature_help_state.is_shown(),
7980 "No signature help was called for"
7981 );
7982 editor.show_signature_help(&ShowSignatureHelp, cx);
7983 });
7984 cx.run_until_parked();
7985 cx.update_editor(|editor, _| {
7986 assert!(
7987 !editor.signature_help_state.is_shown(),
7988 "No signature help should be shown when completions menu is open"
7989 );
7990 });
7991
7992 let apply_additional_edits = cx.update_editor(|editor, cx| {
7993 editor.context_menu_next(&Default::default(), cx);
7994 editor
7995 .confirm_completion(&ConfirmCompletion::default(), cx)
7996 .unwrap()
7997 });
7998 cx.assert_editor_state(indoc! {"
7999 one.second_completionˇ
8000 two
8001 three
8002 "});
8003
8004 handle_resolve_completion_request(
8005 &mut cx,
8006 Some(vec![
8007 (
8008 //This overlaps with the primary completion edit which is
8009 //misbehavior from the LSP spec, test that we filter it out
8010 indoc! {"
8011 one.second_ˇcompletion
8012 two
8013 threeˇ
8014 "},
8015 "overlapping additional edit",
8016 ),
8017 (
8018 indoc! {"
8019 one.second_completion
8020 two
8021 threeˇ
8022 "},
8023 "\nadditional edit",
8024 ),
8025 ]),
8026 )
8027 .await;
8028 apply_additional_edits.await.unwrap();
8029 cx.assert_editor_state(indoc! {"
8030 one.second_completionˇ
8031 two
8032 three
8033 additional edit
8034 "});
8035
8036 cx.set_state(indoc! {"
8037 one.second_completion
8038 twoˇ
8039 threeˇ
8040 additional edit
8041 "});
8042 cx.simulate_keystroke(" ");
8043 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8044 cx.simulate_keystroke("s");
8045 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8046
8047 cx.assert_editor_state(indoc! {"
8048 one.second_completion
8049 two sˇ
8050 three sˇ
8051 additional edit
8052 "});
8053 handle_completion_request(
8054 &mut cx,
8055 indoc! {"
8056 one.second_completion
8057 two s
8058 three <s|>
8059 additional edit
8060 "},
8061 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8062 counter.clone(),
8063 )
8064 .await;
8065 cx.condition(|editor, _| editor.context_menu_visible())
8066 .await;
8067 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8068
8069 cx.simulate_keystroke("i");
8070
8071 handle_completion_request(
8072 &mut cx,
8073 indoc! {"
8074 one.second_completion
8075 two si
8076 three <si|>
8077 additional edit
8078 "},
8079 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8080 counter.clone(),
8081 )
8082 .await;
8083 cx.condition(|editor, _| editor.context_menu_visible())
8084 .await;
8085 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8086
8087 let apply_additional_edits = cx.update_editor(|editor, cx| {
8088 editor
8089 .confirm_completion(&ConfirmCompletion::default(), cx)
8090 .unwrap()
8091 });
8092 cx.assert_editor_state(indoc! {"
8093 one.second_completion
8094 two sixth_completionˇ
8095 three sixth_completionˇ
8096 additional edit
8097 "});
8098
8099 handle_resolve_completion_request(&mut cx, None).await;
8100 apply_additional_edits.await.unwrap();
8101
8102 cx.update(|cx| {
8103 cx.update_global::<SettingsStore, _>(|settings, cx| {
8104 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8105 settings.show_completions_on_input = Some(false);
8106 });
8107 })
8108 });
8109 cx.set_state("editorˇ");
8110 cx.simulate_keystroke(".");
8111 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8112 cx.simulate_keystroke("c");
8113 cx.simulate_keystroke("l");
8114 cx.simulate_keystroke("o");
8115 cx.assert_editor_state("editor.cloˇ");
8116 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8117 cx.update_editor(|editor, cx| {
8118 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8119 });
8120 handle_completion_request(
8121 &mut cx,
8122 "editor.<clo|>",
8123 vec!["close", "clobber"],
8124 counter.clone(),
8125 )
8126 .await;
8127 cx.condition(|editor, _| editor.context_menu_visible())
8128 .await;
8129 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8130
8131 let apply_additional_edits = cx.update_editor(|editor, cx| {
8132 editor
8133 .confirm_completion(&ConfirmCompletion::default(), cx)
8134 .unwrap()
8135 });
8136 cx.assert_editor_state("editor.closeˇ");
8137 handle_resolve_completion_request(&mut cx, None).await;
8138 apply_additional_edits.await.unwrap();
8139}
8140
8141#[gpui::test]
8142async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8143 init_test(cx, |_| {});
8144 let mut cx = EditorLspTestContext::new_rust(
8145 lsp::ServerCapabilities {
8146 completion_provider: Some(lsp::CompletionOptions {
8147 trigger_characters: Some(vec![".".to_string()]),
8148 ..Default::default()
8149 }),
8150 ..Default::default()
8151 },
8152 cx,
8153 )
8154 .await;
8155 cx.lsp
8156 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8157 Ok(Some(lsp::CompletionResponse::Array(vec![
8158 lsp::CompletionItem {
8159 label: "first".into(),
8160 ..Default::default()
8161 },
8162 lsp::CompletionItem {
8163 label: "last".into(),
8164 ..Default::default()
8165 },
8166 ])))
8167 });
8168 cx.set_state("variableˇ");
8169 cx.simulate_keystroke(".");
8170 cx.executor().run_until_parked();
8171
8172 cx.update_editor(|editor, _| {
8173 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8174 assert_eq!(
8175 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8176 &["first", "last"]
8177 );
8178 } else {
8179 panic!("expected completion menu to be open");
8180 }
8181 });
8182
8183 cx.update_editor(|editor, cx| {
8184 editor.move_page_down(&MovePageDown::default(), cx);
8185 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8186 assert!(
8187 menu.selected_item == 1,
8188 "expected PageDown to select the last item from the context menu"
8189 );
8190 } else {
8191 panic!("expected completion menu to stay open after PageDown");
8192 }
8193 });
8194
8195 cx.update_editor(|editor, cx| {
8196 editor.move_page_up(&MovePageUp::default(), cx);
8197 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8198 assert!(
8199 menu.selected_item == 0,
8200 "expected PageUp to select the first item from the context menu"
8201 );
8202 } else {
8203 panic!("expected completion menu to stay open after PageUp");
8204 }
8205 });
8206}
8207
8208#[gpui::test]
8209async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8210 init_test(cx, |_| {});
8211
8212 let mut cx = EditorLspTestContext::new_rust(
8213 lsp::ServerCapabilities {
8214 completion_provider: Some(lsp::CompletionOptions {
8215 trigger_characters: Some(vec![".".to_string()]),
8216 resolve_provider: Some(true),
8217 ..Default::default()
8218 }),
8219 ..Default::default()
8220 },
8221 cx,
8222 )
8223 .await;
8224
8225 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8226 cx.simulate_keystroke(".");
8227 let completion_item = lsp::CompletionItem {
8228 label: "Some".into(),
8229 kind: Some(lsp::CompletionItemKind::SNIPPET),
8230 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8231 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8232 kind: lsp::MarkupKind::Markdown,
8233 value: "```rust\nSome(2)\n```".to_string(),
8234 })),
8235 deprecated: Some(false),
8236 sort_text: Some("Some".to_string()),
8237 filter_text: Some("Some".to_string()),
8238 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8239 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8240 range: lsp::Range {
8241 start: lsp::Position {
8242 line: 0,
8243 character: 22,
8244 },
8245 end: lsp::Position {
8246 line: 0,
8247 character: 22,
8248 },
8249 },
8250 new_text: "Some(2)".to_string(),
8251 })),
8252 additional_text_edits: Some(vec![lsp::TextEdit {
8253 range: lsp::Range {
8254 start: lsp::Position {
8255 line: 0,
8256 character: 20,
8257 },
8258 end: lsp::Position {
8259 line: 0,
8260 character: 22,
8261 },
8262 },
8263 new_text: "".to_string(),
8264 }]),
8265 ..Default::default()
8266 };
8267
8268 let closure_completion_item = completion_item.clone();
8269 let counter = Arc::new(AtomicUsize::new(0));
8270 let counter_clone = counter.clone();
8271 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8272 let task_completion_item = closure_completion_item.clone();
8273 counter_clone.fetch_add(1, atomic::Ordering::Release);
8274 async move {
8275 Ok(Some(lsp::CompletionResponse::Array(vec![
8276 task_completion_item,
8277 ])))
8278 }
8279 });
8280
8281 cx.condition(|editor, _| editor.context_menu_visible())
8282 .await;
8283 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8284 assert!(request.next().await.is_some());
8285 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8286
8287 cx.simulate_keystroke("S");
8288 cx.simulate_keystroke("o");
8289 cx.simulate_keystroke("m");
8290 cx.condition(|editor, _| editor.context_menu_visible())
8291 .await;
8292 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8293 assert!(request.next().await.is_some());
8294 assert!(request.next().await.is_some());
8295 assert!(request.next().await.is_some());
8296 request.close();
8297 assert!(request.next().await.is_none());
8298 assert_eq!(
8299 counter.load(atomic::Ordering::Acquire),
8300 4,
8301 "With the completions menu open, only one LSP request should happen per input"
8302 );
8303}
8304
8305#[gpui::test]
8306async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8307 init_test(cx, |_| {});
8308 let mut cx = EditorTestContext::new(cx).await;
8309 let language = Arc::new(Language::new(
8310 LanguageConfig {
8311 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8312 ..Default::default()
8313 },
8314 Some(tree_sitter_rust::LANGUAGE.into()),
8315 ));
8316 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8317
8318 // If multiple selections intersect a line, the line is only toggled once.
8319 cx.set_state(indoc! {"
8320 fn a() {
8321 «//b();
8322 ˇ»// «c();
8323 //ˇ» d();
8324 }
8325 "});
8326
8327 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8328
8329 cx.assert_editor_state(indoc! {"
8330 fn a() {
8331 «b();
8332 c();
8333 ˇ» d();
8334 }
8335 "});
8336
8337 // The comment prefix is inserted at the same column for every line in a
8338 // selection.
8339 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8340
8341 cx.assert_editor_state(indoc! {"
8342 fn a() {
8343 // «b();
8344 // c();
8345 ˇ»// d();
8346 }
8347 "});
8348
8349 // If a selection ends at the beginning of a line, that line is not toggled.
8350 cx.set_selections_state(indoc! {"
8351 fn a() {
8352 // b();
8353 «// c();
8354 ˇ» // d();
8355 }
8356 "});
8357
8358 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8359
8360 cx.assert_editor_state(indoc! {"
8361 fn a() {
8362 // b();
8363 «c();
8364 ˇ» // d();
8365 }
8366 "});
8367
8368 // If a selection span a single line and is empty, the line is toggled.
8369 cx.set_state(indoc! {"
8370 fn a() {
8371 a();
8372 b();
8373 ˇ
8374 }
8375 "});
8376
8377 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8378
8379 cx.assert_editor_state(indoc! {"
8380 fn a() {
8381 a();
8382 b();
8383 //•ˇ
8384 }
8385 "});
8386
8387 // If a selection span multiple lines, empty lines are not toggled.
8388 cx.set_state(indoc! {"
8389 fn a() {
8390 «a();
8391
8392 c();ˇ»
8393 }
8394 "});
8395
8396 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8397
8398 cx.assert_editor_state(indoc! {"
8399 fn a() {
8400 // «a();
8401
8402 // c();ˇ»
8403 }
8404 "});
8405
8406 // If a selection includes multiple comment prefixes, all lines are uncommented.
8407 cx.set_state(indoc! {"
8408 fn a() {
8409 «// a();
8410 /// b();
8411 //! c();ˇ»
8412 }
8413 "});
8414
8415 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8416
8417 cx.assert_editor_state(indoc! {"
8418 fn a() {
8419 «a();
8420 b();
8421 c();ˇ»
8422 }
8423 "});
8424}
8425
8426#[gpui::test]
8427async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8428 init_test(cx, |_| {});
8429
8430 let language = Arc::new(Language::new(
8431 LanguageConfig {
8432 line_comments: vec!["// ".into()],
8433 ..Default::default()
8434 },
8435 Some(tree_sitter_rust::LANGUAGE.into()),
8436 ));
8437
8438 let mut cx = EditorTestContext::new(cx).await;
8439
8440 cx.language_registry().add(language.clone());
8441 cx.update_buffer(|buffer, cx| {
8442 buffer.set_language(Some(language), cx);
8443 });
8444
8445 let toggle_comments = &ToggleComments {
8446 advance_downwards: true,
8447 };
8448
8449 // Single cursor on one line -> advance
8450 // Cursor moves horizontally 3 characters as well on non-blank line
8451 cx.set_state(indoc!(
8452 "fn a() {
8453 ˇdog();
8454 cat();
8455 }"
8456 ));
8457 cx.update_editor(|editor, cx| {
8458 editor.toggle_comments(toggle_comments, cx);
8459 });
8460 cx.assert_editor_state(indoc!(
8461 "fn a() {
8462 // dog();
8463 catˇ();
8464 }"
8465 ));
8466
8467 // Single selection on one line -> don't advance
8468 cx.set_state(indoc!(
8469 "fn a() {
8470 «dog()ˇ»;
8471 cat();
8472 }"
8473 ));
8474 cx.update_editor(|editor, cx| {
8475 editor.toggle_comments(toggle_comments, cx);
8476 });
8477 cx.assert_editor_state(indoc!(
8478 "fn a() {
8479 // «dog()ˇ»;
8480 cat();
8481 }"
8482 ));
8483
8484 // Multiple cursors on one line -> advance
8485 cx.set_state(indoc!(
8486 "fn a() {
8487 ˇdˇog();
8488 cat();
8489 }"
8490 ));
8491 cx.update_editor(|editor, cx| {
8492 editor.toggle_comments(toggle_comments, cx);
8493 });
8494 cx.assert_editor_state(indoc!(
8495 "fn a() {
8496 // dog();
8497 catˇ(ˇ);
8498 }"
8499 ));
8500
8501 // Multiple cursors on one line, with selection -> don't advance
8502 cx.set_state(indoc!(
8503 "fn a() {
8504 ˇdˇog«()ˇ»;
8505 cat();
8506 }"
8507 ));
8508 cx.update_editor(|editor, cx| {
8509 editor.toggle_comments(toggle_comments, cx);
8510 });
8511 cx.assert_editor_state(indoc!(
8512 "fn a() {
8513 // ˇdˇog«()ˇ»;
8514 cat();
8515 }"
8516 ));
8517
8518 // Single cursor on one line -> advance
8519 // Cursor moves to column 0 on blank line
8520 cx.set_state(indoc!(
8521 "fn a() {
8522 ˇdog();
8523
8524 cat();
8525 }"
8526 ));
8527 cx.update_editor(|editor, cx| {
8528 editor.toggle_comments(toggle_comments, cx);
8529 });
8530 cx.assert_editor_state(indoc!(
8531 "fn a() {
8532 // dog();
8533 ˇ
8534 cat();
8535 }"
8536 ));
8537
8538 // Single cursor on one line -> advance
8539 // Cursor starts and ends at column 0
8540 cx.set_state(indoc!(
8541 "fn a() {
8542 ˇ dog();
8543 cat();
8544 }"
8545 ));
8546 cx.update_editor(|editor, cx| {
8547 editor.toggle_comments(toggle_comments, cx);
8548 });
8549 cx.assert_editor_state(indoc!(
8550 "fn a() {
8551 // dog();
8552 ˇ cat();
8553 }"
8554 ));
8555}
8556
8557#[gpui::test]
8558async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8559 init_test(cx, |_| {});
8560
8561 let mut cx = EditorTestContext::new(cx).await;
8562
8563 let html_language = Arc::new(
8564 Language::new(
8565 LanguageConfig {
8566 name: "HTML".into(),
8567 block_comment: Some(("<!-- ".into(), " -->".into())),
8568 ..Default::default()
8569 },
8570 Some(tree_sitter_html::language()),
8571 )
8572 .with_injection_query(
8573 r#"
8574 (script_element
8575 (raw_text) @content
8576 (#set! "language" "javascript"))
8577 "#,
8578 )
8579 .unwrap(),
8580 );
8581
8582 let javascript_language = Arc::new(Language::new(
8583 LanguageConfig {
8584 name: "JavaScript".into(),
8585 line_comments: vec!["// ".into()],
8586 ..Default::default()
8587 },
8588 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8589 ));
8590
8591 cx.language_registry().add(html_language.clone());
8592 cx.language_registry().add(javascript_language.clone());
8593 cx.update_buffer(|buffer, cx| {
8594 buffer.set_language(Some(html_language), cx);
8595 });
8596
8597 // Toggle comments for empty selections
8598 cx.set_state(
8599 &r#"
8600 <p>A</p>ˇ
8601 <p>B</p>ˇ
8602 <p>C</p>ˇ
8603 "#
8604 .unindent(),
8605 );
8606 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8607 cx.assert_editor_state(
8608 &r#"
8609 <!-- <p>A</p>ˇ -->
8610 <!-- <p>B</p>ˇ -->
8611 <!-- <p>C</p>ˇ -->
8612 "#
8613 .unindent(),
8614 );
8615 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8616 cx.assert_editor_state(
8617 &r#"
8618 <p>A</p>ˇ
8619 <p>B</p>ˇ
8620 <p>C</p>ˇ
8621 "#
8622 .unindent(),
8623 );
8624
8625 // Toggle comments for mixture of empty and non-empty selections, where
8626 // multiple selections occupy a given line.
8627 cx.set_state(
8628 &r#"
8629 <p>A«</p>
8630 <p>ˇ»B</p>ˇ
8631 <p>C«</p>
8632 <p>ˇ»D</p>ˇ
8633 "#
8634 .unindent(),
8635 );
8636
8637 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8638 cx.assert_editor_state(
8639 &r#"
8640 <!-- <p>A«</p>
8641 <p>ˇ»B</p>ˇ -->
8642 <!-- <p>C«</p>
8643 <p>ˇ»D</p>ˇ -->
8644 "#
8645 .unindent(),
8646 );
8647 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8648 cx.assert_editor_state(
8649 &r#"
8650 <p>A«</p>
8651 <p>ˇ»B</p>ˇ
8652 <p>C«</p>
8653 <p>ˇ»D</p>ˇ
8654 "#
8655 .unindent(),
8656 );
8657
8658 // Toggle comments when different languages are active for different
8659 // selections.
8660 cx.set_state(
8661 &r#"
8662 ˇ<script>
8663 ˇvar x = new Y();
8664 ˇ</script>
8665 "#
8666 .unindent(),
8667 );
8668 cx.executor().run_until_parked();
8669 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8670 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8671 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8672 cx.assert_editor_state(
8673 &r#"
8674 <!-- ˇ<script> -->
8675 // ˇvar x = new Y();
8676 // ˇ</script>
8677 "#
8678 .unindent(),
8679 );
8680}
8681
8682#[gpui::test]
8683fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8684 init_test(cx, |_| {});
8685
8686 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8687 let multibuffer = cx.new_model(|cx| {
8688 let mut multibuffer = MultiBuffer::new(ReadWrite);
8689 multibuffer.push_excerpts(
8690 buffer.clone(),
8691 [
8692 ExcerptRange {
8693 context: Point::new(0, 0)..Point::new(0, 4),
8694 primary: None,
8695 },
8696 ExcerptRange {
8697 context: Point::new(1, 0)..Point::new(1, 4),
8698 primary: None,
8699 },
8700 ],
8701 cx,
8702 );
8703 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8704 multibuffer
8705 });
8706
8707 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8708 view.update(cx, |view, cx| {
8709 assert_eq!(view.text(cx), "aaaa\nbbbb");
8710 view.change_selections(None, cx, |s| {
8711 s.select_ranges([
8712 Point::new(0, 0)..Point::new(0, 0),
8713 Point::new(1, 0)..Point::new(1, 0),
8714 ])
8715 });
8716
8717 view.handle_input("X", cx);
8718 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8719 assert_eq!(
8720 view.selections.ranges(cx),
8721 [
8722 Point::new(0, 1)..Point::new(0, 1),
8723 Point::new(1, 1)..Point::new(1, 1),
8724 ]
8725 );
8726
8727 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8728 view.change_selections(None, cx, |s| {
8729 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8730 });
8731 view.backspace(&Default::default(), cx);
8732 assert_eq!(view.text(cx), "Xa\nbbb");
8733 assert_eq!(
8734 view.selections.ranges(cx),
8735 [Point::new(1, 0)..Point::new(1, 0)]
8736 );
8737
8738 view.change_selections(None, cx, |s| {
8739 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8740 });
8741 view.backspace(&Default::default(), cx);
8742 assert_eq!(view.text(cx), "X\nbb");
8743 assert_eq!(
8744 view.selections.ranges(cx),
8745 [Point::new(0, 1)..Point::new(0, 1)]
8746 );
8747 });
8748}
8749
8750#[gpui::test]
8751fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8752 init_test(cx, |_| {});
8753
8754 let markers = vec![('[', ']').into(), ('(', ')').into()];
8755 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8756 indoc! {"
8757 [aaaa
8758 (bbbb]
8759 cccc)",
8760 },
8761 markers.clone(),
8762 );
8763 let excerpt_ranges = markers.into_iter().map(|marker| {
8764 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8765 ExcerptRange {
8766 context,
8767 primary: None,
8768 }
8769 });
8770 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8771 let multibuffer = cx.new_model(|cx| {
8772 let mut multibuffer = MultiBuffer::new(ReadWrite);
8773 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8774 multibuffer
8775 });
8776
8777 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8778 view.update(cx, |view, cx| {
8779 let (expected_text, selection_ranges) = marked_text_ranges(
8780 indoc! {"
8781 aaaa
8782 bˇbbb
8783 bˇbbˇb
8784 cccc"
8785 },
8786 true,
8787 );
8788 assert_eq!(view.text(cx), expected_text);
8789 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8790
8791 view.handle_input("X", cx);
8792
8793 let (expected_text, expected_selections) = marked_text_ranges(
8794 indoc! {"
8795 aaaa
8796 bXˇbbXb
8797 bXˇbbXˇb
8798 cccc"
8799 },
8800 false,
8801 );
8802 assert_eq!(view.text(cx), expected_text);
8803 assert_eq!(view.selections.ranges(cx), expected_selections);
8804
8805 view.newline(&Newline, cx);
8806 let (expected_text, expected_selections) = marked_text_ranges(
8807 indoc! {"
8808 aaaa
8809 bX
8810 ˇbbX
8811 b
8812 bX
8813 ˇbbX
8814 ˇb
8815 cccc"
8816 },
8817 false,
8818 );
8819 assert_eq!(view.text(cx), expected_text);
8820 assert_eq!(view.selections.ranges(cx), expected_selections);
8821 });
8822}
8823
8824#[gpui::test]
8825fn test_refresh_selections(cx: &mut TestAppContext) {
8826 init_test(cx, |_| {});
8827
8828 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8829 let mut excerpt1_id = None;
8830 let multibuffer = cx.new_model(|cx| {
8831 let mut multibuffer = MultiBuffer::new(ReadWrite);
8832 excerpt1_id = multibuffer
8833 .push_excerpts(
8834 buffer.clone(),
8835 [
8836 ExcerptRange {
8837 context: Point::new(0, 0)..Point::new(1, 4),
8838 primary: None,
8839 },
8840 ExcerptRange {
8841 context: Point::new(1, 0)..Point::new(2, 4),
8842 primary: None,
8843 },
8844 ],
8845 cx,
8846 )
8847 .into_iter()
8848 .next();
8849 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8850 multibuffer
8851 });
8852
8853 let editor = cx.add_window(|cx| {
8854 let mut editor = build_editor(multibuffer.clone(), cx);
8855 let snapshot = editor.snapshot(cx);
8856 editor.change_selections(None, cx, |s| {
8857 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8858 });
8859 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8860 assert_eq!(
8861 editor.selections.ranges(cx),
8862 [
8863 Point::new(1, 3)..Point::new(1, 3),
8864 Point::new(2, 1)..Point::new(2, 1),
8865 ]
8866 );
8867 editor
8868 });
8869
8870 // Refreshing selections is a no-op when excerpts haven't changed.
8871 _ = editor.update(cx, |editor, cx| {
8872 editor.change_selections(None, cx, |s| s.refresh());
8873 assert_eq!(
8874 editor.selections.ranges(cx),
8875 [
8876 Point::new(1, 3)..Point::new(1, 3),
8877 Point::new(2, 1)..Point::new(2, 1),
8878 ]
8879 );
8880 });
8881
8882 multibuffer.update(cx, |multibuffer, cx| {
8883 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8884 });
8885 _ = editor.update(cx, |editor, cx| {
8886 // Removing an excerpt causes the first selection to become degenerate.
8887 assert_eq!(
8888 editor.selections.ranges(cx),
8889 [
8890 Point::new(0, 0)..Point::new(0, 0),
8891 Point::new(0, 1)..Point::new(0, 1)
8892 ]
8893 );
8894
8895 // Refreshing selections will relocate the first selection to the original buffer
8896 // location.
8897 editor.change_selections(None, cx, |s| s.refresh());
8898 assert_eq!(
8899 editor.selections.ranges(cx),
8900 [
8901 Point::new(0, 1)..Point::new(0, 1),
8902 Point::new(0, 3)..Point::new(0, 3)
8903 ]
8904 );
8905 assert!(editor.selections.pending_anchor().is_some());
8906 });
8907}
8908
8909#[gpui::test]
8910fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8911 init_test(cx, |_| {});
8912
8913 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8914 let mut excerpt1_id = None;
8915 let multibuffer = cx.new_model(|cx| {
8916 let mut multibuffer = MultiBuffer::new(ReadWrite);
8917 excerpt1_id = multibuffer
8918 .push_excerpts(
8919 buffer.clone(),
8920 [
8921 ExcerptRange {
8922 context: Point::new(0, 0)..Point::new(1, 4),
8923 primary: None,
8924 },
8925 ExcerptRange {
8926 context: Point::new(1, 0)..Point::new(2, 4),
8927 primary: None,
8928 },
8929 ],
8930 cx,
8931 )
8932 .into_iter()
8933 .next();
8934 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8935 multibuffer
8936 });
8937
8938 let editor = cx.add_window(|cx| {
8939 let mut editor = build_editor(multibuffer.clone(), cx);
8940 let snapshot = editor.snapshot(cx);
8941 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8942 assert_eq!(
8943 editor.selections.ranges(cx),
8944 [Point::new(1, 3)..Point::new(1, 3)]
8945 );
8946 editor
8947 });
8948
8949 multibuffer.update(cx, |multibuffer, cx| {
8950 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8951 });
8952 _ = editor.update(cx, |editor, cx| {
8953 assert_eq!(
8954 editor.selections.ranges(cx),
8955 [Point::new(0, 0)..Point::new(0, 0)]
8956 );
8957
8958 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8959 editor.change_selections(None, cx, |s| s.refresh());
8960 assert_eq!(
8961 editor.selections.ranges(cx),
8962 [Point::new(0, 3)..Point::new(0, 3)]
8963 );
8964 assert!(editor.selections.pending_anchor().is_some());
8965 });
8966}
8967
8968#[gpui::test]
8969async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8970 init_test(cx, |_| {});
8971
8972 let language = Arc::new(
8973 Language::new(
8974 LanguageConfig {
8975 brackets: BracketPairConfig {
8976 pairs: vec![
8977 BracketPair {
8978 start: "{".to_string(),
8979 end: "}".to_string(),
8980 close: true,
8981 surround: true,
8982 newline: true,
8983 },
8984 BracketPair {
8985 start: "/* ".to_string(),
8986 end: " */".to_string(),
8987 close: true,
8988 surround: true,
8989 newline: true,
8990 },
8991 ],
8992 ..Default::default()
8993 },
8994 ..Default::default()
8995 },
8996 Some(tree_sitter_rust::LANGUAGE.into()),
8997 )
8998 .with_indents_query("")
8999 .unwrap(),
9000 );
9001
9002 let text = concat!(
9003 "{ }\n", //
9004 " x\n", //
9005 " /* */\n", //
9006 "x\n", //
9007 "{{} }\n", //
9008 );
9009
9010 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9011 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9012 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9013 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9014 .await;
9015
9016 view.update(cx, |view, cx| {
9017 view.change_selections(None, cx, |s| {
9018 s.select_display_ranges([
9019 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9020 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9021 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9022 ])
9023 });
9024 view.newline(&Newline, cx);
9025
9026 assert_eq!(
9027 view.buffer().read(cx).read(cx).text(),
9028 concat!(
9029 "{ \n", // Suppress rustfmt
9030 "\n", //
9031 "}\n", //
9032 " x\n", //
9033 " /* \n", //
9034 " \n", //
9035 " */\n", //
9036 "x\n", //
9037 "{{} \n", //
9038 "}\n", //
9039 )
9040 );
9041 });
9042}
9043
9044#[gpui::test]
9045fn test_highlighted_ranges(cx: &mut TestAppContext) {
9046 init_test(cx, |_| {});
9047
9048 let editor = cx.add_window(|cx| {
9049 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9050 build_editor(buffer.clone(), cx)
9051 });
9052
9053 _ = editor.update(cx, |editor, cx| {
9054 struct Type1;
9055 struct Type2;
9056
9057 let buffer = editor.buffer.read(cx).snapshot(cx);
9058
9059 let anchor_range =
9060 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9061
9062 editor.highlight_background::<Type1>(
9063 &[
9064 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9065 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9066 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9067 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9068 ],
9069 |_| Hsla::red(),
9070 cx,
9071 );
9072 editor.highlight_background::<Type2>(
9073 &[
9074 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9075 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9076 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9077 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9078 ],
9079 |_| Hsla::green(),
9080 cx,
9081 );
9082
9083 let snapshot = editor.snapshot(cx);
9084 let mut highlighted_ranges = editor.background_highlights_in_range(
9085 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9086 &snapshot,
9087 cx.theme().colors(),
9088 );
9089 // Enforce a consistent ordering based on color without relying on the ordering of the
9090 // highlight's `TypeId` which is non-executor.
9091 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9092 assert_eq!(
9093 highlighted_ranges,
9094 &[
9095 (
9096 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9097 Hsla::red(),
9098 ),
9099 (
9100 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9101 Hsla::red(),
9102 ),
9103 (
9104 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9105 Hsla::green(),
9106 ),
9107 (
9108 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9109 Hsla::green(),
9110 ),
9111 ]
9112 );
9113 assert_eq!(
9114 editor.background_highlights_in_range(
9115 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9116 &snapshot,
9117 cx.theme().colors(),
9118 ),
9119 &[(
9120 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9121 Hsla::red(),
9122 )]
9123 );
9124 });
9125}
9126
9127#[gpui::test]
9128async fn test_following(cx: &mut gpui::TestAppContext) {
9129 init_test(cx, |_| {});
9130
9131 let fs = FakeFs::new(cx.executor());
9132 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9133
9134 let buffer = project.update(cx, |project, cx| {
9135 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9136 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9137 });
9138 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9139 let follower = cx.update(|cx| {
9140 cx.open_window(
9141 WindowOptions {
9142 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9143 gpui::Point::new(px(0.), px(0.)),
9144 gpui::Point::new(px(10.), px(80.)),
9145 ))),
9146 ..Default::default()
9147 },
9148 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9149 )
9150 .unwrap()
9151 });
9152
9153 let is_still_following = Rc::new(RefCell::new(true));
9154 let follower_edit_event_count = Rc::new(RefCell::new(0));
9155 let pending_update = Rc::new(RefCell::new(None));
9156 _ = follower.update(cx, {
9157 let update = pending_update.clone();
9158 let is_still_following = is_still_following.clone();
9159 let follower_edit_event_count = follower_edit_event_count.clone();
9160 |_, cx| {
9161 cx.subscribe(
9162 &leader.root_view(cx).unwrap(),
9163 move |_, leader, event, cx| {
9164 leader
9165 .read(cx)
9166 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9167 },
9168 )
9169 .detach();
9170
9171 cx.subscribe(
9172 &follower.root_view(cx).unwrap(),
9173 move |_, _, event: &EditorEvent, _cx| {
9174 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9175 *is_still_following.borrow_mut() = false;
9176 }
9177
9178 if let EditorEvent::BufferEdited = event {
9179 *follower_edit_event_count.borrow_mut() += 1;
9180 }
9181 },
9182 )
9183 .detach();
9184 }
9185 });
9186
9187 // Update the selections only
9188 _ = leader.update(cx, |leader, cx| {
9189 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9190 });
9191 follower
9192 .update(cx, |follower, cx| {
9193 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9194 })
9195 .unwrap()
9196 .await
9197 .unwrap();
9198 _ = follower.update(cx, |follower, cx| {
9199 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9200 });
9201 assert!(*is_still_following.borrow());
9202 assert_eq!(*follower_edit_event_count.borrow(), 0);
9203
9204 // Update the scroll position only
9205 _ = leader.update(cx, |leader, cx| {
9206 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9207 });
9208 follower
9209 .update(cx, |follower, cx| {
9210 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9211 })
9212 .unwrap()
9213 .await
9214 .unwrap();
9215 assert_eq!(
9216 follower
9217 .update(cx, |follower, cx| follower.scroll_position(cx))
9218 .unwrap(),
9219 gpui::Point::new(1.5, 3.5)
9220 );
9221 assert!(*is_still_following.borrow());
9222 assert_eq!(*follower_edit_event_count.borrow(), 0);
9223
9224 // Update the selections and scroll position. The follower's scroll position is updated
9225 // via autoscroll, not via the leader's exact scroll position.
9226 _ = leader.update(cx, |leader, cx| {
9227 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9228 leader.request_autoscroll(Autoscroll::newest(), cx);
9229 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9230 });
9231 follower
9232 .update(cx, |follower, cx| {
9233 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9234 })
9235 .unwrap()
9236 .await
9237 .unwrap();
9238 _ = follower.update(cx, |follower, cx| {
9239 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9240 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9241 });
9242 assert!(*is_still_following.borrow());
9243
9244 // Creating a pending selection that precedes another selection
9245 _ = leader.update(cx, |leader, cx| {
9246 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9247 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9248 });
9249 follower
9250 .update(cx, |follower, cx| {
9251 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9252 })
9253 .unwrap()
9254 .await
9255 .unwrap();
9256 _ = follower.update(cx, |follower, cx| {
9257 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9258 });
9259 assert!(*is_still_following.borrow());
9260
9261 // Extend the pending selection so that it surrounds another selection
9262 _ = leader.update(cx, |leader, cx| {
9263 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9264 });
9265 follower
9266 .update(cx, |follower, cx| {
9267 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9268 })
9269 .unwrap()
9270 .await
9271 .unwrap();
9272 _ = follower.update(cx, |follower, cx| {
9273 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9274 });
9275
9276 // Scrolling locally breaks the follow
9277 _ = follower.update(cx, |follower, cx| {
9278 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9279 follower.set_scroll_anchor(
9280 ScrollAnchor {
9281 anchor: top_anchor,
9282 offset: gpui::Point::new(0.0, 0.5),
9283 },
9284 cx,
9285 );
9286 });
9287 assert!(!(*is_still_following.borrow()));
9288}
9289
9290#[gpui::test]
9291async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9292 init_test(cx, |_| {});
9293
9294 let fs = FakeFs::new(cx.executor());
9295 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9296 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9297 let pane = workspace
9298 .update(cx, |workspace, _| workspace.active_pane().clone())
9299 .unwrap();
9300
9301 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9302
9303 let leader = pane.update(cx, |_, cx| {
9304 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9305 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9306 });
9307
9308 // Start following the editor when it has no excerpts.
9309 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9310 let follower_1 = cx
9311 .update_window(*workspace.deref(), |_, cx| {
9312 Editor::from_state_proto(
9313 workspace.root_view(cx).unwrap(),
9314 ViewId {
9315 creator: Default::default(),
9316 id: 0,
9317 },
9318 &mut state_message,
9319 cx,
9320 )
9321 })
9322 .unwrap()
9323 .unwrap()
9324 .await
9325 .unwrap();
9326
9327 let update_message = Rc::new(RefCell::new(None));
9328 follower_1.update(cx, {
9329 let update = update_message.clone();
9330 |_, cx| {
9331 cx.subscribe(&leader, move |_, leader, event, cx| {
9332 leader
9333 .read(cx)
9334 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9335 })
9336 .detach();
9337 }
9338 });
9339
9340 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9341 (
9342 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9343 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9344 )
9345 });
9346
9347 // Insert some excerpts.
9348 leader.update(cx, |leader, cx| {
9349 leader.buffer.update(cx, |multibuffer, cx| {
9350 let excerpt_ids = multibuffer.push_excerpts(
9351 buffer_1.clone(),
9352 [
9353 ExcerptRange {
9354 context: 1..6,
9355 primary: None,
9356 },
9357 ExcerptRange {
9358 context: 12..15,
9359 primary: None,
9360 },
9361 ExcerptRange {
9362 context: 0..3,
9363 primary: None,
9364 },
9365 ],
9366 cx,
9367 );
9368 multibuffer.insert_excerpts_after(
9369 excerpt_ids[0],
9370 buffer_2.clone(),
9371 [
9372 ExcerptRange {
9373 context: 8..12,
9374 primary: None,
9375 },
9376 ExcerptRange {
9377 context: 0..6,
9378 primary: None,
9379 },
9380 ],
9381 cx,
9382 );
9383 });
9384 });
9385
9386 // Apply the update of adding the excerpts.
9387 follower_1
9388 .update(cx, |follower, cx| {
9389 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9390 })
9391 .await
9392 .unwrap();
9393 assert_eq!(
9394 follower_1.update(cx, |editor, cx| editor.text(cx)),
9395 leader.update(cx, |editor, cx| editor.text(cx))
9396 );
9397 update_message.borrow_mut().take();
9398
9399 // Start following separately after it already has excerpts.
9400 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9401 let follower_2 = cx
9402 .update_window(*workspace.deref(), |_, cx| {
9403 Editor::from_state_proto(
9404 workspace.root_view(cx).unwrap().clone(),
9405 ViewId {
9406 creator: Default::default(),
9407 id: 0,
9408 },
9409 &mut state_message,
9410 cx,
9411 )
9412 })
9413 .unwrap()
9414 .unwrap()
9415 .await
9416 .unwrap();
9417 assert_eq!(
9418 follower_2.update(cx, |editor, cx| editor.text(cx)),
9419 leader.update(cx, |editor, cx| editor.text(cx))
9420 );
9421
9422 // Remove some excerpts.
9423 leader.update(cx, |leader, cx| {
9424 leader.buffer.update(cx, |multibuffer, cx| {
9425 let excerpt_ids = multibuffer.excerpt_ids();
9426 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9427 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9428 });
9429 });
9430
9431 // Apply the update of removing the excerpts.
9432 follower_1
9433 .update(cx, |follower, cx| {
9434 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9435 })
9436 .await
9437 .unwrap();
9438 follower_2
9439 .update(cx, |follower, cx| {
9440 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9441 })
9442 .await
9443 .unwrap();
9444 update_message.borrow_mut().take();
9445 assert_eq!(
9446 follower_1.update(cx, |editor, cx| editor.text(cx)),
9447 leader.update(cx, |editor, cx| editor.text(cx))
9448 );
9449}
9450
9451#[gpui::test]
9452async fn go_to_prev_overlapping_diagnostic(
9453 executor: BackgroundExecutor,
9454 cx: &mut gpui::TestAppContext,
9455) {
9456 init_test(cx, |_| {});
9457
9458 let mut cx = EditorTestContext::new(cx).await;
9459 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9460
9461 cx.set_state(indoc! {"
9462 ˇfn func(abc def: i32) -> u32 {
9463 }
9464 "});
9465
9466 cx.update(|cx| {
9467 project.update(cx, |project, cx| {
9468 project
9469 .update_diagnostics(
9470 LanguageServerId(0),
9471 lsp::PublishDiagnosticsParams {
9472 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9473 version: None,
9474 diagnostics: vec![
9475 lsp::Diagnostic {
9476 range: lsp::Range::new(
9477 lsp::Position::new(0, 11),
9478 lsp::Position::new(0, 12),
9479 ),
9480 severity: Some(lsp::DiagnosticSeverity::ERROR),
9481 ..Default::default()
9482 },
9483 lsp::Diagnostic {
9484 range: lsp::Range::new(
9485 lsp::Position::new(0, 12),
9486 lsp::Position::new(0, 15),
9487 ),
9488 severity: Some(lsp::DiagnosticSeverity::ERROR),
9489 ..Default::default()
9490 },
9491 lsp::Diagnostic {
9492 range: lsp::Range::new(
9493 lsp::Position::new(0, 25),
9494 lsp::Position::new(0, 28),
9495 ),
9496 severity: Some(lsp::DiagnosticSeverity::ERROR),
9497 ..Default::default()
9498 },
9499 ],
9500 },
9501 &[],
9502 cx,
9503 )
9504 .unwrap()
9505 });
9506 });
9507
9508 executor.run_until_parked();
9509
9510 cx.update_editor(|editor, cx| {
9511 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9512 });
9513
9514 cx.assert_editor_state(indoc! {"
9515 fn func(abc def: i32) -> ˇu32 {
9516 }
9517 "});
9518
9519 cx.update_editor(|editor, cx| {
9520 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9521 });
9522
9523 cx.assert_editor_state(indoc! {"
9524 fn func(abc ˇdef: i32) -> u32 {
9525 }
9526 "});
9527
9528 cx.update_editor(|editor, cx| {
9529 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9530 });
9531
9532 cx.assert_editor_state(indoc! {"
9533 fn func(abcˇ def: i32) -> u32 {
9534 }
9535 "});
9536
9537 cx.update_editor(|editor, cx| {
9538 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9539 });
9540
9541 cx.assert_editor_state(indoc! {"
9542 fn func(abc def: i32) -> ˇu32 {
9543 }
9544 "});
9545}
9546
9547#[gpui::test]
9548async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9549 init_test(cx, |_| {});
9550
9551 let mut cx = EditorTestContext::new(cx).await;
9552
9553 cx.set_state(indoc! {"
9554 fn func(abˇc def: i32) -> u32 {
9555 }
9556 "});
9557 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9558
9559 cx.update(|cx| {
9560 project.update(cx, |project, cx| {
9561 project.update_diagnostics(
9562 LanguageServerId(0),
9563 lsp::PublishDiagnosticsParams {
9564 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9565 version: None,
9566 diagnostics: vec![lsp::Diagnostic {
9567 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9568 severity: Some(lsp::DiagnosticSeverity::ERROR),
9569 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9570 ..Default::default()
9571 }],
9572 },
9573 &[],
9574 cx,
9575 )
9576 })
9577 }).unwrap();
9578 cx.run_until_parked();
9579 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9580 cx.run_until_parked();
9581 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9582}
9583
9584#[gpui::test]
9585async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9586 init_test(cx, |_| {});
9587
9588 let mut cx = EditorTestContext::new(cx).await;
9589
9590 let diff_base = r#"
9591 use some::mod;
9592
9593 const A: u32 = 42;
9594
9595 fn main() {
9596 println!("hello");
9597
9598 println!("world");
9599 }
9600 "#
9601 .unindent();
9602
9603 // Edits are modified, removed, modified, added
9604 cx.set_state(
9605 &r#"
9606 use some::modified;
9607
9608 ˇ
9609 fn main() {
9610 println!("hello there");
9611
9612 println!("around the");
9613 println!("world");
9614 }
9615 "#
9616 .unindent(),
9617 );
9618
9619 cx.set_diff_base(Some(&diff_base));
9620 executor.run_until_parked();
9621
9622 cx.update_editor(|editor, cx| {
9623 //Wrap around the bottom of the buffer
9624 for _ in 0..3 {
9625 editor.go_to_next_hunk(&GoToHunk, cx);
9626 }
9627 });
9628
9629 cx.assert_editor_state(
9630 &r#"
9631 ˇuse some::modified;
9632
9633
9634 fn main() {
9635 println!("hello there");
9636
9637 println!("around the");
9638 println!("world");
9639 }
9640 "#
9641 .unindent(),
9642 );
9643
9644 cx.update_editor(|editor, cx| {
9645 //Wrap around the top of the buffer
9646 for _ in 0..2 {
9647 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9648 }
9649 });
9650
9651 cx.assert_editor_state(
9652 &r#"
9653 use some::modified;
9654
9655
9656 fn main() {
9657 ˇ println!("hello there");
9658
9659 println!("around the");
9660 println!("world");
9661 }
9662 "#
9663 .unindent(),
9664 );
9665
9666 cx.update_editor(|editor, cx| {
9667 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9668 });
9669
9670 cx.assert_editor_state(
9671 &r#"
9672 use some::modified;
9673
9674 ˇ
9675 fn main() {
9676 println!("hello there");
9677
9678 println!("around the");
9679 println!("world");
9680 }
9681 "#
9682 .unindent(),
9683 );
9684
9685 cx.update_editor(|editor, cx| {
9686 for _ in 0..3 {
9687 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9688 }
9689 });
9690
9691 cx.assert_editor_state(
9692 &r#"
9693 use some::modified;
9694
9695
9696 fn main() {
9697 ˇ println!("hello there");
9698
9699 println!("around the");
9700 println!("world");
9701 }
9702 "#
9703 .unindent(),
9704 );
9705
9706 cx.update_editor(|editor, cx| {
9707 editor.fold(&Fold, cx);
9708
9709 //Make sure that the fold only gets one hunk
9710 for _ in 0..4 {
9711 editor.go_to_next_hunk(&GoToHunk, cx);
9712 }
9713 });
9714
9715 cx.assert_editor_state(
9716 &r#"
9717 ˇuse some::modified;
9718
9719
9720 fn main() {
9721 println!("hello there");
9722
9723 println!("around the");
9724 println!("world");
9725 }
9726 "#
9727 .unindent(),
9728 );
9729}
9730
9731#[test]
9732fn test_split_words() {
9733 fn split(text: &str) -> Vec<&str> {
9734 split_words(text).collect()
9735 }
9736
9737 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9738 assert_eq!(split("hello_world"), &["hello_", "world"]);
9739 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9740 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9741 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9742 assert_eq!(split("helloworld"), &["helloworld"]);
9743
9744 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9745}
9746
9747#[gpui::test]
9748async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9749 init_test(cx, |_| {});
9750
9751 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9752 let mut assert = |before, after| {
9753 let _state_context = cx.set_state(before);
9754 cx.update_editor(|editor, cx| {
9755 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9756 });
9757 cx.assert_editor_state(after);
9758 };
9759
9760 // Outside bracket jumps to outside of matching bracket
9761 assert("console.logˇ(var);", "console.log(var)ˇ;");
9762 assert("console.log(var)ˇ;", "console.logˇ(var);");
9763
9764 // Inside bracket jumps to inside of matching bracket
9765 assert("console.log(ˇvar);", "console.log(varˇ);");
9766 assert("console.log(varˇ);", "console.log(ˇvar);");
9767
9768 // When outside a bracket and inside, favor jumping to the inside bracket
9769 assert(
9770 "console.log('foo', [1, 2, 3]ˇ);",
9771 "console.log(ˇ'foo', [1, 2, 3]);",
9772 );
9773 assert(
9774 "console.log(ˇ'foo', [1, 2, 3]);",
9775 "console.log('foo', [1, 2, 3]ˇ);",
9776 );
9777
9778 // Bias forward if two options are equally likely
9779 assert(
9780 "let result = curried_fun()ˇ();",
9781 "let result = curried_fun()()ˇ;",
9782 );
9783
9784 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9785 assert(
9786 indoc! {"
9787 function test() {
9788 console.log('test')ˇ
9789 }"},
9790 indoc! {"
9791 function test() {
9792 console.logˇ('test')
9793 }"},
9794 );
9795}
9796
9797#[gpui::test]
9798async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9799 init_test(cx, |_| {});
9800
9801 let fs = FakeFs::new(cx.executor());
9802 fs.insert_tree(
9803 "/a",
9804 json!({
9805 "main.rs": "fn main() { let a = 5; }",
9806 "other.rs": "// Test file",
9807 }),
9808 )
9809 .await;
9810 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9811
9812 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9813 language_registry.add(Arc::new(Language::new(
9814 LanguageConfig {
9815 name: "Rust".into(),
9816 matcher: LanguageMatcher {
9817 path_suffixes: vec!["rs".to_string()],
9818 ..Default::default()
9819 },
9820 brackets: BracketPairConfig {
9821 pairs: vec![BracketPair {
9822 start: "{".to_string(),
9823 end: "}".to_string(),
9824 close: true,
9825 surround: true,
9826 newline: true,
9827 }],
9828 disabled_scopes_by_bracket_ix: Vec::new(),
9829 },
9830 ..Default::default()
9831 },
9832 Some(tree_sitter_rust::LANGUAGE.into()),
9833 )));
9834 let mut fake_servers = language_registry.register_fake_lsp(
9835 "Rust",
9836 FakeLspAdapter {
9837 capabilities: lsp::ServerCapabilities {
9838 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9839 first_trigger_character: "{".to_string(),
9840 more_trigger_character: None,
9841 }),
9842 ..Default::default()
9843 },
9844 ..Default::default()
9845 },
9846 );
9847
9848 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9849
9850 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9851
9852 let worktree_id = workspace
9853 .update(cx, |workspace, cx| {
9854 workspace.project().update(cx, |project, cx| {
9855 project.worktrees(cx).next().unwrap().read(cx).id()
9856 })
9857 })
9858 .unwrap();
9859
9860 let buffer = project
9861 .update(cx, |project, cx| {
9862 project.open_local_buffer("/a/main.rs", cx)
9863 })
9864 .await
9865 .unwrap();
9866 cx.executor().run_until_parked();
9867 cx.executor().start_waiting();
9868 let fake_server = fake_servers.next().await.unwrap();
9869 let editor_handle = workspace
9870 .update(cx, |workspace, cx| {
9871 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9872 })
9873 .unwrap()
9874 .await
9875 .unwrap()
9876 .downcast::<Editor>()
9877 .unwrap();
9878
9879 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9880 assert_eq!(
9881 params.text_document_position.text_document.uri,
9882 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9883 );
9884 assert_eq!(
9885 params.text_document_position.position,
9886 lsp::Position::new(0, 21),
9887 );
9888
9889 Ok(Some(vec![lsp::TextEdit {
9890 new_text: "]".to_string(),
9891 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9892 }]))
9893 });
9894
9895 editor_handle.update(cx, |editor, cx| {
9896 editor.focus(cx);
9897 editor.change_selections(None, cx, |s| {
9898 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9899 });
9900 editor.handle_input("{", cx);
9901 });
9902
9903 cx.executor().run_until_parked();
9904
9905 buffer.update(cx, |buffer, _| {
9906 assert_eq!(
9907 buffer.text(),
9908 "fn main() { let a = {5}; }",
9909 "No extra braces from on type formatting should appear in the buffer"
9910 )
9911 });
9912}
9913
9914#[gpui::test]
9915async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9916 init_test(cx, |_| {});
9917
9918 let fs = FakeFs::new(cx.executor());
9919 fs.insert_tree(
9920 "/a",
9921 json!({
9922 "main.rs": "fn main() { let a = 5; }",
9923 "other.rs": "// Test file",
9924 }),
9925 )
9926 .await;
9927
9928 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9929
9930 let server_restarts = Arc::new(AtomicUsize::new(0));
9931 let closure_restarts = Arc::clone(&server_restarts);
9932 let language_server_name = "test language server";
9933 let language_name: LanguageName = "Rust".into();
9934
9935 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9936 language_registry.add(Arc::new(Language::new(
9937 LanguageConfig {
9938 name: language_name.clone(),
9939 matcher: LanguageMatcher {
9940 path_suffixes: vec!["rs".to_string()],
9941 ..Default::default()
9942 },
9943 ..Default::default()
9944 },
9945 Some(tree_sitter_rust::LANGUAGE.into()),
9946 )));
9947 let mut fake_servers = language_registry.register_fake_lsp(
9948 "Rust",
9949 FakeLspAdapter {
9950 name: language_server_name,
9951 initialization_options: Some(json!({
9952 "testOptionValue": true
9953 })),
9954 initializer: Some(Box::new(move |fake_server| {
9955 let task_restarts = Arc::clone(&closure_restarts);
9956 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9957 task_restarts.fetch_add(1, atomic::Ordering::Release);
9958 futures::future::ready(Ok(()))
9959 });
9960 })),
9961 ..Default::default()
9962 },
9963 );
9964
9965 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9966 let _buffer = project
9967 .update(cx, |project, cx| {
9968 project.open_local_buffer("/a/main.rs", cx)
9969 })
9970 .await
9971 .unwrap();
9972 let _fake_server = fake_servers.next().await.unwrap();
9973 update_test_language_settings(cx, |language_settings| {
9974 language_settings.languages.insert(
9975 language_name.clone(),
9976 LanguageSettingsContent {
9977 tab_size: NonZeroU32::new(8),
9978 ..Default::default()
9979 },
9980 );
9981 });
9982 cx.executor().run_until_parked();
9983 assert_eq!(
9984 server_restarts.load(atomic::Ordering::Acquire),
9985 0,
9986 "Should not restart LSP server on an unrelated change"
9987 );
9988
9989 update_test_project_settings(cx, |project_settings| {
9990 project_settings.lsp.insert(
9991 "Some other server name".into(),
9992 LspSettings {
9993 binary: None,
9994 settings: None,
9995 initialization_options: Some(json!({
9996 "some other init value": false
9997 })),
9998 },
9999 );
10000 });
10001 cx.executor().run_until_parked();
10002 assert_eq!(
10003 server_restarts.load(atomic::Ordering::Acquire),
10004 0,
10005 "Should not restart LSP server on an unrelated LSP settings change"
10006 );
10007
10008 update_test_project_settings(cx, |project_settings| {
10009 project_settings.lsp.insert(
10010 language_server_name.into(),
10011 LspSettings {
10012 binary: None,
10013 settings: None,
10014 initialization_options: Some(json!({
10015 "anotherInitValue": false
10016 })),
10017 },
10018 );
10019 });
10020 cx.executor().run_until_parked();
10021 assert_eq!(
10022 server_restarts.load(atomic::Ordering::Acquire),
10023 1,
10024 "Should restart LSP server on a related LSP settings change"
10025 );
10026
10027 update_test_project_settings(cx, |project_settings| {
10028 project_settings.lsp.insert(
10029 language_server_name.into(),
10030 LspSettings {
10031 binary: None,
10032 settings: None,
10033 initialization_options: Some(json!({
10034 "anotherInitValue": false
10035 })),
10036 },
10037 );
10038 });
10039 cx.executor().run_until_parked();
10040 assert_eq!(
10041 server_restarts.load(atomic::Ordering::Acquire),
10042 1,
10043 "Should not restart LSP server on a related LSP settings change that is the same"
10044 );
10045
10046 update_test_project_settings(cx, |project_settings| {
10047 project_settings.lsp.insert(
10048 language_server_name.into(),
10049 LspSettings {
10050 binary: None,
10051 settings: None,
10052 initialization_options: None,
10053 },
10054 );
10055 });
10056 cx.executor().run_until_parked();
10057 assert_eq!(
10058 server_restarts.load(atomic::Ordering::Acquire),
10059 2,
10060 "Should restart LSP server on another related LSP settings change"
10061 );
10062}
10063
10064#[gpui::test]
10065async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10066 init_test(cx, |_| {});
10067
10068 let mut cx = EditorLspTestContext::new_rust(
10069 lsp::ServerCapabilities {
10070 completion_provider: Some(lsp::CompletionOptions {
10071 trigger_characters: Some(vec![".".to_string()]),
10072 resolve_provider: Some(true),
10073 ..Default::default()
10074 }),
10075 ..Default::default()
10076 },
10077 cx,
10078 )
10079 .await;
10080
10081 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10082 cx.simulate_keystroke(".");
10083 let completion_item = lsp::CompletionItem {
10084 label: "some".into(),
10085 kind: Some(lsp::CompletionItemKind::SNIPPET),
10086 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10087 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10088 kind: lsp::MarkupKind::Markdown,
10089 value: "```rust\nSome(2)\n```".to_string(),
10090 })),
10091 deprecated: Some(false),
10092 sort_text: Some("fffffff2".to_string()),
10093 filter_text: Some("some".to_string()),
10094 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10095 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10096 range: lsp::Range {
10097 start: lsp::Position {
10098 line: 0,
10099 character: 22,
10100 },
10101 end: lsp::Position {
10102 line: 0,
10103 character: 22,
10104 },
10105 },
10106 new_text: "Some(2)".to_string(),
10107 })),
10108 additional_text_edits: Some(vec![lsp::TextEdit {
10109 range: lsp::Range {
10110 start: lsp::Position {
10111 line: 0,
10112 character: 20,
10113 },
10114 end: lsp::Position {
10115 line: 0,
10116 character: 22,
10117 },
10118 },
10119 new_text: "".to_string(),
10120 }]),
10121 ..Default::default()
10122 };
10123
10124 let closure_completion_item = completion_item.clone();
10125 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10126 let task_completion_item = closure_completion_item.clone();
10127 async move {
10128 Ok(Some(lsp::CompletionResponse::Array(vec![
10129 task_completion_item,
10130 ])))
10131 }
10132 });
10133
10134 request.next().await;
10135
10136 cx.condition(|editor, _| editor.context_menu_visible())
10137 .await;
10138 let apply_additional_edits = cx.update_editor(|editor, cx| {
10139 editor
10140 .confirm_completion(&ConfirmCompletion::default(), cx)
10141 .unwrap()
10142 });
10143 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10144
10145 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10146 let task_completion_item = completion_item.clone();
10147 async move { Ok(task_completion_item) }
10148 })
10149 .next()
10150 .await
10151 .unwrap();
10152 apply_additional_edits.await.unwrap();
10153 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10154}
10155
10156#[gpui::test]
10157async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10158 init_test(cx, |_| {});
10159
10160 let mut cx = EditorLspTestContext::new(
10161 Language::new(
10162 LanguageConfig {
10163 matcher: LanguageMatcher {
10164 path_suffixes: vec!["jsx".into()],
10165 ..Default::default()
10166 },
10167 overrides: [(
10168 "element".into(),
10169 LanguageConfigOverride {
10170 word_characters: Override::Set(['-'].into_iter().collect()),
10171 ..Default::default()
10172 },
10173 )]
10174 .into_iter()
10175 .collect(),
10176 ..Default::default()
10177 },
10178 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10179 )
10180 .with_override_query("(jsx_self_closing_element) @element")
10181 .unwrap(),
10182 lsp::ServerCapabilities {
10183 completion_provider: Some(lsp::CompletionOptions {
10184 trigger_characters: Some(vec![":".to_string()]),
10185 ..Default::default()
10186 }),
10187 ..Default::default()
10188 },
10189 cx,
10190 )
10191 .await;
10192
10193 cx.lsp
10194 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10195 Ok(Some(lsp::CompletionResponse::Array(vec![
10196 lsp::CompletionItem {
10197 label: "bg-blue".into(),
10198 ..Default::default()
10199 },
10200 lsp::CompletionItem {
10201 label: "bg-red".into(),
10202 ..Default::default()
10203 },
10204 lsp::CompletionItem {
10205 label: "bg-yellow".into(),
10206 ..Default::default()
10207 },
10208 ])))
10209 });
10210
10211 cx.set_state(r#"<p class="bgˇ" />"#);
10212
10213 // Trigger completion when typing a dash, because the dash is an extra
10214 // word character in the 'element' scope, which contains the cursor.
10215 cx.simulate_keystroke("-");
10216 cx.executor().run_until_parked();
10217 cx.update_editor(|editor, _| {
10218 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10219 assert_eq!(
10220 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10221 &["bg-red", "bg-blue", "bg-yellow"]
10222 );
10223 } else {
10224 panic!("expected completion menu to be open");
10225 }
10226 });
10227
10228 cx.simulate_keystroke("l");
10229 cx.executor().run_until_parked();
10230 cx.update_editor(|editor, _| {
10231 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10232 assert_eq!(
10233 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10234 &["bg-blue", "bg-yellow"]
10235 );
10236 } else {
10237 panic!("expected completion menu to be open");
10238 }
10239 });
10240
10241 // When filtering completions, consider the character after the '-' to
10242 // be the start of a subword.
10243 cx.set_state(r#"<p class="yelˇ" />"#);
10244 cx.simulate_keystroke("l");
10245 cx.executor().run_until_parked();
10246 cx.update_editor(|editor, _| {
10247 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10248 assert_eq!(
10249 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10250 &["bg-yellow"]
10251 );
10252 } else {
10253 panic!("expected completion menu to be open");
10254 }
10255 });
10256}
10257
10258#[gpui::test]
10259async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10260 init_test(cx, |settings| {
10261 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10262 FormatterList(vec![Formatter::Prettier].into()),
10263 ))
10264 });
10265
10266 let fs = FakeFs::new(cx.executor());
10267 fs.insert_file("/file.ts", Default::default()).await;
10268
10269 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10270 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10271
10272 language_registry.add(Arc::new(Language::new(
10273 LanguageConfig {
10274 name: "TypeScript".into(),
10275 matcher: LanguageMatcher {
10276 path_suffixes: vec!["ts".to_string()],
10277 ..Default::default()
10278 },
10279 ..Default::default()
10280 },
10281 Some(tree_sitter_rust::LANGUAGE.into()),
10282 )));
10283 update_test_language_settings(cx, |settings| {
10284 settings.defaults.prettier = Some(PrettierSettings {
10285 allowed: true,
10286 ..PrettierSettings::default()
10287 });
10288 });
10289
10290 let test_plugin = "test_plugin";
10291 let _ = language_registry.register_fake_lsp(
10292 "TypeScript",
10293 FakeLspAdapter {
10294 prettier_plugins: vec![test_plugin],
10295 ..Default::default()
10296 },
10297 );
10298
10299 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10300 let buffer = project
10301 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10302 .await
10303 .unwrap();
10304
10305 let buffer_text = "one\ntwo\nthree\n";
10306 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10307 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10308 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10309
10310 editor
10311 .update(cx, |editor, cx| {
10312 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10313 })
10314 .unwrap()
10315 .await;
10316 assert_eq!(
10317 editor.update(cx, |editor, cx| editor.text(cx)),
10318 buffer_text.to_string() + prettier_format_suffix,
10319 "Test prettier formatting was not applied to the original buffer text",
10320 );
10321
10322 update_test_language_settings(cx, |settings| {
10323 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10324 });
10325 let format = editor.update(cx, |editor, cx| {
10326 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10327 });
10328 format.await.unwrap();
10329 assert_eq!(
10330 editor.update(cx, |editor, cx| editor.text(cx)),
10331 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10332 "Autoformatting (via test prettier) was not applied to the original buffer text",
10333 );
10334}
10335
10336#[gpui::test]
10337async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10338 init_test(cx, |_| {});
10339 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10340 let base_text = indoc! {r#"struct Row;
10341struct Row1;
10342struct Row2;
10343
10344struct Row4;
10345struct Row5;
10346struct Row6;
10347
10348struct Row8;
10349struct Row9;
10350struct Row10;"#};
10351
10352 // When addition hunks are not adjacent to carets, no hunk revert is performed
10353 assert_hunk_revert(
10354 indoc! {r#"struct Row;
10355 struct Row1;
10356 struct Row1.1;
10357 struct Row1.2;
10358 struct Row2;ˇ
10359
10360 struct Row4;
10361 struct Row5;
10362 struct Row6;
10363
10364 struct Row8;
10365 ˇstruct Row9;
10366 struct Row9.1;
10367 struct Row9.2;
10368 struct Row9.3;
10369 struct Row10;"#},
10370 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10371 indoc! {r#"struct Row;
10372 struct Row1;
10373 struct Row1.1;
10374 struct Row1.2;
10375 struct Row2;ˇ
10376
10377 struct Row4;
10378 struct Row5;
10379 struct Row6;
10380
10381 struct Row8;
10382 ˇstruct Row9;
10383 struct Row9.1;
10384 struct Row9.2;
10385 struct Row9.3;
10386 struct Row10;"#},
10387 base_text,
10388 &mut cx,
10389 );
10390 // Same for selections
10391 assert_hunk_revert(
10392 indoc! {r#"struct Row;
10393 struct Row1;
10394 struct Row2;
10395 struct Row2.1;
10396 struct Row2.2;
10397 «ˇ
10398 struct Row4;
10399 struct» Row5;
10400 «struct Row6;
10401 ˇ»
10402 struct Row9.1;
10403 struct Row9.2;
10404 struct Row9.3;
10405 struct Row8;
10406 struct Row9;
10407 struct Row10;"#},
10408 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10409 indoc! {r#"struct Row;
10410 struct Row1;
10411 struct Row2;
10412 struct Row2.1;
10413 struct Row2.2;
10414 «ˇ
10415 struct Row4;
10416 struct» Row5;
10417 «struct Row6;
10418 ˇ»
10419 struct Row9.1;
10420 struct Row9.2;
10421 struct Row9.3;
10422 struct Row8;
10423 struct Row9;
10424 struct Row10;"#},
10425 base_text,
10426 &mut cx,
10427 );
10428
10429 // When carets and selections intersect the addition hunks, those are reverted.
10430 // Adjacent carets got merged.
10431 assert_hunk_revert(
10432 indoc! {r#"struct Row;
10433 ˇ// something on the top
10434 struct Row1;
10435 struct Row2;
10436 struct Roˇw3.1;
10437 struct Row2.2;
10438 struct Row2.3;ˇ
10439
10440 struct Row4;
10441 struct ˇRow5.1;
10442 struct Row5.2;
10443 struct «Rowˇ»5.3;
10444 struct Row5;
10445 struct Row6;
10446 ˇ
10447 struct Row9.1;
10448 struct «Rowˇ»9.2;
10449 struct «ˇRow»9.3;
10450 struct Row8;
10451 struct Row9;
10452 «ˇ// something on bottom»
10453 struct Row10;"#},
10454 vec![
10455 DiffHunkStatus::Added,
10456 DiffHunkStatus::Added,
10457 DiffHunkStatus::Added,
10458 DiffHunkStatus::Added,
10459 DiffHunkStatus::Added,
10460 ],
10461 indoc! {r#"struct Row;
10462 ˇstruct Row1;
10463 struct Row2;
10464 ˇ
10465 struct Row4;
10466 ˇstruct Row5;
10467 struct Row6;
10468 ˇ
10469 ˇstruct Row8;
10470 struct Row9;
10471 ˇstruct Row10;"#},
10472 base_text,
10473 &mut cx,
10474 );
10475}
10476
10477#[gpui::test]
10478async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10479 init_test(cx, |_| {});
10480 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10481 let base_text = indoc! {r#"struct Row;
10482struct Row1;
10483struct Row2;
10484
10485struct Row4;
10486struct Row5;
10487struct Row6;
10488
10489struct Row8;
10490struct Row9;
10491struct Row10;"#};
10492
10493 // Modification hunks behave the same as the addition ones.
10494 assert_hunk_revert(
10495 indoc! {r#"struct Row;
10496 struct Row1;
10497 struct Row33;
10498 ˇ
10499 struct Row4;
10500 struct Row5;
10501 struct Row6;
10502 ˇ
10503 struct Row99;
10504 struct Row9;
10505 struct Row10;"#},
10506 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10507 indoc! {r#"struct Row;
10508 struct Row1;
10509 struct Row33;
10510 ˇ
10511 struct Row4;
10512 struct Row5;
10513 struct Row6;
10514 ˇ
10515 struct Row99;
10516 struct Row9;
10517 struct Row10;"#},
10518 base_text,
10519 &mut cx,
10520 );
10521 assert_hunk_revert(
10522 indoc! {r#"struct Row;
10523 struct Row1;
10524 struct Row33;
10525 «ˇ
10526 struct Row4;
10527 struct» Row5;
10528 «struct Row6;
10529 ˇ»
10530 struct Row99;
10531 struct Row9;
10532 struct Row10;"#},
10533 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10534 indoc! {r#"struct Row;
10535 struct Row1;
10536 struct Row33;
10537 «ˇ
10538 struct Row4;
10539 struct» Row5;
10540 «struct Row6;
10541 ˇ»
10542 struct Row99;
10543 struct Row9;
10544 struct Row10;"#},
10545 base_text,
10546 &mut cx,
10547 );
10548
10549 assert_hunk_revert(
10550 indoc! {r#"ˇstruct Row1.1;
10551 struct Row1;
10552 «ˇstr»uct Row22;
10553
10554 struct ˇRow44;
10555 struct Row5;
10556 struct «Rˇ»ow66;ˇ
10557
10558 «struˇ»ct Row88;
10559 struct Row9;
10560 struct Row1011;ˇ"#},
10561 vec![
10562 DiffHunkStatus::Modified,
10563 DiffHunkStatus::Modified,
10564 DiffHunkStatus::Modified,
10565 DiffHunkStatus::Modified,
10566 DiffHunkStatus::Modified,
10567 DiffHunkStatus::Modified,
10568 ],
10569 indoc! {r#"struct Row;
10570 ˇstruct Row1;
10571 struct Row2;
10572 ˇ
10573 struct Row4;
10574 ˇstruct Row5;
10575 struct Row6;
10576 ˇ
10577 struct Row8;
10578 ˇstruct Row9;
10579 struct Row10;ˇ"#},
10580 base_text,
10581 &mut cx,
10582 );
10583}
10584
10585#[gpui::test]
10586async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10587 init_test(cx, |_| {});
10588 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10589 let base_text = indoc! {r#"struct Row;
10590struct Row1;
10591struct Row2;
10592
10593struct Row4;
10594struct Row5;
10595struct Row6;
10596
10597struct Row8;
10598struct Row9;
10599struct Row10;"#};
10600
10601 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10602 assert_hunk_revert(
10603 indoc! {r#"struct Row;
10604 struct Row2;
10605
10606 ˇstruct Row4;
10607 struct Row5;
10608 struct Row6;
10609 ˇ
10610 struct Row8;
10611 struct Row10;"#},
10612 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10613 indoc! {r#"struct Row;
10614 struct Row2;
10615
10616 ˇstruct Row4;
10617 struct Row5;
10618 struct Row6;
10619 ˇ
10620 struct Row8;
10621 struct Row10;"#},
10622 base_text,
10623 &mut cx,
10624 );
10625 assert_hunk_revert(
10626 indoc! {r#"struct Row;
10627 struct Row2;
10628
10629 «ˇstruct Row4;
10630 struct» Row5;
10631 «struct Row6;
10632 ˇ»
10633 struct Row8;
10634 struct Row10;"#},
10635 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10636 indoc! {r#"struct Row;
10637 struct Row2;
10638
10639 «ˇstruct Row4;
10640 struct» Row5;
10641 «struct Row6;
10642 ˇ»
10643 struct Row8;
10644 struct Row10;"#},
10645 base_text,
10646 &mut cx,
10647 );
10648
10649 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10650 assert_hunk_revert(
10651 indoc! {r#"struct Row;
10652 ˇstruct Row2;
10653
10654 struct Row4;
10655 struct Row5;
10656 struct Row6;
10657
10658 struct Row8;ˇ
10659 struct Row10;"#},
10660 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10661 indoc! {r#"struct Row;
10662 struct Row1;
10663 ˇstruct Row2;
10664
10665 struct Row4;
10666 struct Row5;
10667 struct Row6;
10668
10669 struct Row8;ˇ
10670 struct Row9;
10671 struct Row10;"#},
10672 base_text,
10673 &mut cx,
10674 );
10675 assert_hunk_revert(
10676 indoc! {r#"struct Row;
10677 struct Row2«ˇ;
10678 struct Row4;
10679 struct» Row5;
10680 «struct Row6;
10681
10682 struct Row8;ˇ»
10683 struct Row10;"#},
10684 vec![
10685 DiffHunkStatus::Removed,
10686 DiffHunkStatus::Removed,
10687 DiffHunkStatus::Removed,
10688 ],
10689 indoc! {r#"struct Row;
10690 struct Row1;
10691 struct Row2«ˇ;
10692
10693 struct Row4;
10694 struct» Row5;
10695 «struct Row6;
10696
10697 struct Row8;ˇ»
10698 struct Row9;
10699 struct Row10;"#},
10700 base_text,
10701 &mut cx,
10702 );
10703}
10704
10705#[gpui::test]
10706async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10707 init_test(cx, |_| {});
10708
10709 let cols = 4;
10710 let rows = 10;
10711 let sample_text_1 = sample_text(rows, cols, 'a');
10712 assert_eq!(
10713 sample_text_1,
10714 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10715 );
10716 let sample_text_2 = sample_text(rows, cols, 'l');
10717 assert_eq!(
10718 sample_text_2,
10719 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10720 );
10721 let sample_text_3 = sample_text(rows, cols, 'v');
10722 assert_eq!(
10723 sample_text_3,
10724 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10725 );
10726
10727 fn diff_every_buffer_row(
10728 buffer: &Model<Buffer>,
10729 sample_text: String,
10730 cols: usize,
10731 cx: &mut gpui::TestAppContext,
10732 ) {
10733 // revert first character in each row, creating one large diff hunk per buffer
10734 let is_first_char = |offset: usize| offset % cols == 0;
10735 buffer.update(cx, |buffer, cx| {
10736 buffer.set_text(
10737 sample_text
10738 .chars()
10739 .enumerate()
10740 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10741 .collect::<String>(),
10742 cx,
10743 );
10744 buffer.set_diff_base(Some(sample_text), cx);
10745 });
10746 cx.executor().run_until_parked();
10747 }
10748
10749 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10750 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10751
10752 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10753 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10754
10755 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10756 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10757
10758 let multibuffer = cx.new_model(|cx| {
10759 let mut multibuffer = MultiBuffer::new(ReadWrite);
10760 multibuffer.push_excerpts(
10761 buffer_1.clone(),
10762 [
10763 ExcerptRange {
10764 context: Point::new(0, 0)..Point::new(3, 0),
10765 primary: None,
10766 },
10767 ExcerptRange {
10768 context: Point::new(5, 0)..Point::new(7, 0),
10769 primary: None,
10770 },
10771 ExcerptRange {
10772 context: Point::new(9, 0)..Point::new(10, 4),
10773 primary: None,
10774 },
10775 ],
10776 cx,
10777 );
10778 multibuffer.push_excerpts(
10779 buffer_2.clone(),
10780 [
10781 ExcerptRange {
10782 context: Point::new(0, 0)..Point::new(3, 0),
10783 primary: None,
10784 },
10785 ExcerptRange {
10786 context: Point::new(5, 0)..Point::new(7, 0),
10787 primary: None,
10788 },
10789 ExcerptRange {
10790 context: Point::new(9, 0)..Point::new(10, 4),
10791 primary: None,
10792 },
10793 ],
10794 cx,
10795 );
10796 multibuffer.push_excerpts(
10797 buffer_3.clone(),
10798 [
10799 ExcerptRange {
10800 context: Point::new(0, 0)..Point::new(3, 0),
10801 primary: None,
10802 },
10803 ExcerptRange {
10804 context: Point::new(5, 0)..Point::new(7, 0),
10805 primary: None,
10806 },
10807 ExcerptRange {
10808 context: Point::new(9, 0)..Point::new(10, 4),
10809 primary: None,
10810 },
10811 ],
10812 cx,
10813 );
10814 multibuffer
10815 });
10816
10817 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10818 editor.update(cx, |editor, cx| {
10819 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");
10820 editor.select_all(&SelectAll, cx);
10821 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10822 });
10823 cx.executor().run_until_parked();
10824 // When all ranges are selected, all buffer hunks are reverted.
10825 editor.update(cx, |editor, cx| {
10826 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");
10827 });
10828 buffer_1.update(cx, |buffer, _| {
10829 assert_eq!(buffer.text(), sample_text_1);
10830 });
10831 buffer_2.update(cx, |buffer, _| {
10832 assert_eq!(buffer.text(), sample_text_2);
10833 });
10834 buffer_3.update(cx, |buffer, _| {
10835 assert_eq!(buffer.text(), sample_text_3);
10836 });
10837
10838 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10839 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10840 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10841 editor.update(cx, |editor, cx| {
10842 editor.change_selections(None, cx, |s| {
10843 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10844 });
10845 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10846 });
10847 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10848 // but not affect buffer_2 and its related excerpts.
10849 editor.update(cx, |editor, cx| {
10850 assert_eq!(
10851 editor.text(cx),
10852 "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"
10853 );
10854 });
10855 buffer_1.update(cx, |buffer, _| {
10856 assert_eq!(buffer.text(), sample_text_1);
10857 });
10858 buffer_2.update(cx, |buffer, _| {
10859 assert_eq!(
10860 buffer.text(),
10861 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10862 );
10863 });
10864 buffer_3.update(cx, |buffer, _| {
10865 assert_eq!(
10866 buffer.text(),
10867 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10868 );
10869 });
10870}
10871
10872#[gpui::test]
10873async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10874 init_test(cx, |_| {});
10875
10876 let cols = 4;
10877 let rows = 10;
10878 let sample_text_1 = sample_text(rows, cols, 'a');
10879 assert_eq!(
10880 sample_text_1,
10881 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10882 );
10883 let sample_text_2 = sample_text(rows, cols, 'l');
10884 assert_eq!(
10885 sample_text_2,
10886 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10887 );
10888 let sample_text_3 = sample_text(rows, cols, 'v');
10889 assert_eq!(
10890 sample_text_3,
10891 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10892 );
10893
10894 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10895 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10896 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10897
10898 let multi_buffer = cx.new_model(|cx| {
10899 let mut multibuffer = MultiBuffer::new(ReadWrite);
10900 multibuffer.push_excerpts(
10901 buffer_1.clone(),
10902 [
10903 ExcerptRange {
10904 context: Point::new(0, 0)..Point::new(3, 0),
10905 primary: None,
10906 },
10907 ExcerptRange {
10908 context: Point::new(5, 0)..Point::new(7, 0),
10909 primary: None,
10910 },
10911 ExcerptRange {
10912 context: Point::new(9, 0)..Point::new(10, 4),
10913 primary: None,
10914 },
10915 ],
10916 cx,
10917 );
10918 multibuffer.push_excerpts(
10919 buffer_2.clone(),
10920 [
10921 ExcerptRange {
10922 context: Point::new(0, 0)..Point::new(3, 0),
10923 primary: None,
10924 },
10925 ExcerptRange {
10926 context: Point::new(5, 0)..Point::new(7, 0),
10927 primary: None,
10928 },
10929 ExcerptRange {
10930 context: Point::new(9, 0)..Point::new(10, 4),
10931 primary: None,
10932 },
10933 ],
10934 cx,
10935 );
10936 multibuffer.push_excerpts(
10937 buffer_3.clone(),
10938 [
10939 ExcerptRange {
10940 context: Point::new(0, 0)..Point::new(3, 0),
10941 primary: None,
10942 },
10943 ExcerptRange {
10944 context: Point::new(5, 0)..Point::new(7, 0),
10945 primary: None,
10946 },
10947 ExcerptRange {
10948 context: Point::new(9, 0)..Point::new(10, 4),
10949 primary: None,
10950 },
10951 ],
10952 cx,
10953 );
10954 multibuffer
10955 });
10956
10957 let fs = FakeFs::new(cx.executor());
10958 fs.insert_tree(
10959 "/a",
10960 json!({
10961 "main.rs": sample_text_1,
10962 "other.rs": sample_text_2,
10963 "lib.rs": sample_text_3,
10964 }),
10965 )
10966 .await;
10967 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10968 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10969 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10970 let multi_buffer_editor = cx.new_view(|cx| {
10971 Editor::new(
10972 EditorMode::Full,
10973 multi_buffer,
10974 Some(project.clone()),
10975 true,
10976 cx,
10977 )
10978 });
10979 let multibuffer_item_id = workspace
10980 .update(cx, |workspace, cx| {
10981 assert!(
10982 workspace.active_item(cx).is_none(),
10983 "active item should be None before the first item is added"
10984 );
10985 workspace.add_item_to_active_pane(
10986 Box::new(multi_buffer_editor.clone()),
10987 None,
10988 true,
10989 cx,
10990 );
10991 let active_item = workspace
10992 .active_item(cx)
10993 .expect("should have an active item after adding the multi buffer");
10994 assert!(
10995 !active_item.is_singleton(cx),
10996 "A multi buffer was expected to active after adding"
10997 );
10998 active_item.item_id()
10999 })
11000 .unwrap();
11001 cx.executor().run_until_parked();
11002
11003 multi_buffer_editor.update(cx, |editor, cx| {
11004 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11005 editor.open_excerpts(&OpenExcerpts, cx);
11006 });
11007 cx.executor().run_until_parked();
11008 let first_item_id = workspace
11009 .update(cx, |workspace, cx| {
11010 let active_item = workspace
11011 .active_item(cx)
11012 .expect("should have an active item after navigating into the 1st buffer");
11013 let first_item_id = active_item.item_id();
11014 assert_ne!(
11015 first_item_id, multibuffer_item_id,
11016 "Should navigate into the 1st buffer and activate it"
11017 );
11018 assert!(
11019 active_item.is_singleton(cx),
11020 "New active item should be a singleton buffer"
11021 );
11022 assert_eq!(
11023 active_item
11024 .act_as::<Editor>(cx)
11025 .expect("should have navigated into an editor for the 1st buffer")
11026 .read(cx)
11027 .text(cx),
11028 sample_text_1
11029 );
11030
11031 workspace
11032 .go_back(workspace.active_pane().downgrade(), cx)
11033 .detach_and_log_err(cx);
11034
11035 first_item_id
11036 })
11037 .unwrap();
11038 cx.executor().run_until_parked();
11039 workspace
11040 .update(cx, |workspace, cx| {
11041 let active_item = workspace
11042 .active_item(cx)
11043 .expect("should have an active item after navigating back");
11044 assert_eq!(
11045 active_item.item_id(),
11046 multibuffer_item_id,
11047 "Should navigate back to the multi buffer"
11048 );
11049 assert!(!active_item.is_singleton(cx));
11050 })
11051 .unwrap();
11052
11053 multi_buffer_editor.update(cx, |editor, cx| {
11054 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11055 s.select_ranges(Some(39..40))
11056 });
11057 editor.open_excerpts(&OpenExcerpts, cx);
11058 });
11059 cx.executor().run_until_parked();
11060 let second_item_id = workspace
11061 .update(cx, |workspace, cx| {
11062 let active_item = workspace
11063 .active_item(cx)
11064 .expect("should have an active item after navigating into the 2nd buffer");
11065 let second_item_id = active_item.item_id();
11066 assert_ne!(
11067 second_item_id, multibuffer_item_id,
11068 "Should navigate away from the multibuffer"
11069 );
11070 assert_ne!(
11071 second_item_id, first_item_id,
11072 "Should navigate into the 2nd buffer and activate it"
11073 );
11074 assert!(
11075 active_item.is_singleton(cx),
11076 "New active item should be a singleton buffer"
11077 );
11078 assert_eq!(
11079 active_item
11080 .act_as::<Editor>(cx)
11081 .expect("should have navigated into an editor")
11082 .read(cx)
11083 .text(cx),
11084 sample_text_2
11085 );
11086
11087 workspace
11088 .go_back(workspace.active_pane().downgrade(), cx)
11089 .detach_and_log_err(cx);
11090
11091 second_item_id
11092 })
11093 .unwrap();
11094 cx.executor().run_until_parked();
11095 workspace
11096 .update(cx, |workspace, cx| {
11097 let active_item = workspace
11098 .active_item(cx)
11099 .expect("should have an active item after navigating back from the 2nd buffer");
11100 assert_eq!(
11101 active_item.item_id(),
11102 multibuffer_item_id,
11103 "Should navigate back from the 2nd buffer to the multi buffer"
11104 );
11105 assert!(!active_item.is_singleton(cx));
11106 })
11107 .unwrap();
11108
11109 multi_buffer_editor.update(cx, |editor, cx| {
11110 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11111 s.select_ranges(Some(60..70))
11112 });
11113 editor.open_excerpts(&OpenExcerpts, cx);
11114 });
11115 cx.executor().run_until_parked();
11116 workspace
11117 .update(cx, |workspace, cx| {
11118 let active_item = workspace
11119 .active_item(cx)
11120 .expect("should have an active item after navigating into the 3rd buffer");
11121 let third_item_id = active_item.item_id();
11122 assert_ne!(
11123 third_item_id, multibuffer_item_id,
11124 "Should navigate into the 3rd buffer and activate it"
11125 );
11126 assert_ne!(third_item_id, first_item_id);
11127 assert_ne!(third_item_id, second_item_id);
11128 assert!(
11129 active_item.is_singleton(cx),
11130 "New active item should be a singleton buffer"
11131 );
11132 assert_eq!(
11133 active_item
11134 .act_as::<Editor>(cx)
11135 .expect("should have navigated into an editor")
11136 .read(cx)
11137 .text(cx),
11138 sample_text_3
11139 );
11140
11141 workspace
11142 .go_back(workspace.active_pane().downgrade(), cx)
11143 .detach_and_log_err(cx);
11144 })
11145 .unwrap();
11146 cx.executor().run_until_parked();
11147 workspace
11148 .update(cx, |workspace, cx| {
11149 let active_item = workspace
11150 .active_item(cx)
11151 .expect("should have an active item after navigating back from the 3rd buffer");
11152 assert_eq!(
11153 active_item.item_id(),
11154 multibuffer_item_id,
11155 "Should navigate back from the 3rd buffer to the multi buffer"
11156 );
11157 assert!(!active_item.is_singleton(cx));
11158 })
11159 .unwrap();
11160}
11161
11162#[gpui::test]
11163async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11164 init_test(cx, |_| {});
11165
11166 let mut cx = EditorTestContext::new(cx).await;
11167
11168 let diff_base = r#"
11169 use some::mod;
11170
11171 const A: u32 = 42;
11172
11173 fn main() {
11174 println!("hello");
11175
11176 println!("world");
11177 }
11178 "#
11179 .unindent();
11180
11181 cx.set_state(
11182 &r#"
11183 use some::modified;
11184
11185 ˇ
11186 fn main() {
11187 println!("hello there");
11188
11189 println!("around the");
11190 println!("world");
11191 }
11192 "#
11193 .unindent(),
11194 );
11195
11196 cx.set_diff_base(Some(&diff_base));
11197 executor.run_until_parked();
11198
11199 cx.update_editor(|editor, cx| {
11200 editor.go_to_next_hunk(&GoToHunk, cx);
11201 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11202 });
11203 executor.run_until_parked();
11204 cx.assert_diff_hunks(
11205 r#"
11206 use some::modified;
11207
11208
11209 fn main() {
11210 - println!("hello");
11211 + println!("hello there");
11212
11213 println!("around the");
11214 println!("world");
11215 }
11216 "#
11217 .unindent(),
11218 );
11219
11220 cx.update_editor(|editor, cx| {
11221 for _ in 0..3 {
11222 editor.go_to_next_hunk(&GoToHunk, cx);
11223 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11224 }
11225 });
11226 executor.run_until_parked();
11227 cx.assert_editor_state(
11228 &r#"
11229 use some::modified;
11230
11231 ˇ
11232 fn main() {
11233 println!("hello there");
11234
11235 println!("around the");
11236 println!("world");
11237 }
11238 "#
11239 .unindent(),
11240 );
11241
11242 cx.assert_diff_hunks(
11243 r#"
11244 - use some::mod;
11245 + use some::modified;
11246
11247 - const A: u32 = 42;
11248
11249 fn main() {
11250 - println!("hello");
11251 + println!("hello there");
11252
11253 + println!("around the");
11254 println!("world");
11255 }
11256 "#
11257 .unindent(),
11258 );
11259
11260 cx.update_editor(|editor, cx| {
11261 editor.cancel(&Cancel, cx);
11262 });
11263
11264 cx.assert_diff_hunks(
11265 r#"
11266 use some::modified;
11267
11268
11269 fn main() {
11270 println!("hello there");
11271
11272 println!("around the");
11273 println!("world");
11274 }
11275 "#
11276 .unindent(),
11277 );
11278}
11279
11280#[gpui::test]
11281async fn test_diff_base_change_with_expanded_diff_hunks(
11282 executor: BackgroundExecutor,
11283 cx: &mut gpui::TestAppContext,
11284) {
11285 init_test(cx, |_| {});
11286
11287 let mut cx = EditorTestContext::new(cx).await;
11288
11289 let diff_base = r#"
11290 use some::mod1;
11291 use some::mod2;
11292
11293 const A: u32 = 42;
11294 const B: u32 = 42;
11295 const C: u32 = 42;
11296
11297 fn main() {
11298 println!("hello");
11299
11300 println!("world");
11301 }
11302 "#
11303 .unindent();
11304
11305 cx.set_state(
11306 &r#"
11307 use some::mod2;
11308
11309 const A: u32 = 42;
11310 const C: u32 = 42;
11311
11312 fn main(ˇ) {
11313 //println!("hello");
11314
11315 println!("world");
11316 //
11317 //
11318 }
11319 "#
11320 .unindent(),
11321 );
11322
11323 cx.set_diff_base(Some(&diff_base));
11324 executor.run_until_parked();
11325
11326 cx.update_editor(|editor, cx| {
11327 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11328 });
11329 executor.run_until_parked();
11330 cx.assert_diff_hunks(
11331 r#"
11332 - use some::mod1;
11333 use some::mod2;
11334
11335 const A: u32 = 42;
11336 - const B: u32 = 42;
11337 const C: u32 = 42;
11338
11339 fn main() {
11340 - println!("hello");
11341 + //println!("hello");
11342
11343 println!("world");
11344 + //
11345 + //
11346 }
11347 "#
11348 .unindent(),
11349 );
11350
11351 cx.set_diff_base(Some("new diff base!"));
11352 executor.run_until_parked();
11353 cx.assert_diff_hunks(
11354 r#"
11355 use some::mod2;
11356
11357 const A: u32 = 42;
11358 const C: u32 = 42;
11359
11360 fn main() {
11361 //println!("hello");
11362
11363 println!("world");
11364 //
11365 //
11366 }
11367 "#
11368 .unindent(),
11369 );
11370
11371 cx.update_editor(|editor, cx| {
11372 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11373 });
11374 executor.run_until_parked();
11375 cx.assert_diff_hunks(
11376 r#"
11377 - new diff base!
11378 + use some::mod2;
11379 +
11380 + const A: u32 = 42;
11381 + const C: u32 = 42;
11382 +
11383 + fn main() {
11384 + //println!("hello");
11385 +
11386 + println!("world");
11387 + //
11388 + //
11389 + }
11390 "#
11391 .unindent(),
11392 );
11393}
11394
11395#[gpui::test]
11396async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11397 init_test(cx, |_| {});
11398
11399 let mut cx = EditorTestContext::new(cx).await;
11400
11401 let diff_base = r#"
11402 use some::mod1;
11403 use some::mod2;
11404
11405 const A: u32 = 42;
11406 const B: u32 = 42;
11407 const C: u32 = 42;
11408
11409 fn main() {
11410 println!("hello");
11411
11412 println!("world");
11413 }
11414
11415 fn another() {
11416 println!("another");
11417 }
11418
11419 fn another2() {
11420 println!("another2");
11421 }
11422 "#
11423 .unindent();
11424
11425 cx.set_state(
11426 &r#"
11427 «use some::mod2;
11428
11429 const A: u32 = 42;
11430 const C: u32 = 42;
11431
11432 fn main() {
11433 //println!("hello");
11434
11435 println!("world");
11436 //
11437 //ˇ»
11438 }
11439
11440 fn another() {
11441 println!("another");
11442 println!("another");
11443 }
11444
11445 println!("another2");
11446 }
11447 "#
11448 .unindent(),
11449 );
11450
11451 cx.set_diff_base(Some(&diff_base));
11452 executor.run_until_parked();
11453
11454 cx.update_editor(|editor, cx| {
11455 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11456 });
11457 executor.run_until_parked();
11458
11459 cx.assert_diff_hunks(
11460 r#"
11461 - use some::mod1;
11462 use some::mod2;
11463
11464 const A: u32 = 42;
11465 - const B: u32 = 42;
11466 const C: u32 = 42;
11467
11468 fn main() {
11469 - println!("hello");
11470 + //println!("hello");
11471
11472 println!("world");
11473 + //
11474 + //
11475 }
11476
11477 fn another() {
11478 println!("another");
11479 + println!("another");
11480 }
11481
11482 - fn another2() {
11483 println!("another2");
11484 }
11485 "#
11486 .unindent(),
11487 );
11488
11489 // Fold across some of the diff hunks. They should no longer appear expanded.
11490 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11491 cx.executor().run_until_parked();
11492
11493 // Hunks are not shown if their position is within a fold
11494 cx.assert_diff_hunks(
11495 r#"
11496 use some::mod2;
11497
11498 const A: u32 = 42;
11499 const C: u32 = 42;
11500
11501 fn main() {
11502 //println!("hello");
11503
11504 println!("world");
11505 //
11506 //
11507 }
11508
11509 fn another() {
11510 println!("another");
11511 + println!("another");
11512 }
11513
11514 - fn another2() {
11515 println!("another2");
11516 }
11517 "#
11518 .unindent(),
11519 );
11520
11521 cx.update_editor(|editor, cx| {
11522 editor.select_all(&SelectAll, cx);
11523 editor.unfold_lines(&UnfoldLines, cx);
11524 });
11525 cx.executor().run_until_parked();
11526
11527 // The deletions reappear when unfolding.
11528 cx.assert_diff_hunks(
11529 r#"
11530 - use some::mod1;
11531 use some::mod2;
11532
11533 const A: u32 = 42;
11534 - const B: u32 = 42;
11535 const C: u32 = 42;
11536
11537 fn main() {
11538 - println!("hello");
11539 + //println!("hello");
11540
11541 println!("world");
11542 + //
11543 + //
11544 }
11545
11546 fn another() {
11547 println!("another");
11548 + println!("another");
11549 }
11550
11551 - fn another2() {
11552 println!("another2");
11553 }
11554 "#
11555 .unindent(),
11556 );
11557}
11558
11559#[gpui::test]
11560async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11561 init_test(cx, |_| {});
11562
11563 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11564 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11565 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11566 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11567 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11568 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11569
11570 let buffer_1 = cx.new_model(|cx| {
11571 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11572 buffer.set_diff_base(Some(file_1_old.into()), cx);
11573 buffer
11574 });
11575 let buffer_2 = cx.new_model(|cx| {
11576 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11577 buffer.set_diff_base(Some(file_2_old.into()), cx);
11578 buffer
11579 });
11580 let buffer_3 = cx.new_model(|cx| {
11581 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11582 buffer.set_diff_base(Some(file_3_old.into()), cx);
11583 buffer
11584 });
11585
11586 let multi_buffer = cx.new_model(|cx| {
11587 let mut multibuffer = MultiBuffer::new(ReadWrite);
11588 multibuffer.push_excerpts(
11589 buffer_1.clone(),
11590 [
11591 ExcerptRange {
11592 context: Point::new(0, 0)..Point::new(3, 0),
11593 primary: None,
11594 },
11595 ExcerptRange {
11596 context: Point::new(5, 0)..Point::new(7, 0),
11597 primary: None,
11598 },
11599 ExcerptRange {
11600 context: Point::new(9, 0)..Point::new(10, 3),
11601 primary: None,
11602 },
11603 ],
11604 cx,
11605 );
11606 multibuffer.push_excerpts(
11607 buffer_2.clone(),
11608 [
11609 ExcerptRange {
11610 context: Point::new(0, 0)..Point::new(3, 0),
11611 primary: None,
11612 },
11613 ExcerptRange {
11614 context: Point::new(5, 0)..Point::new(7, 0),
11615 primary: None,
11616 },
11617 ExcerptRange {
11618 context: Point::new(9, 0)..Point::new(10, 3),
11619 primary: None,
11620 },
11621 ],
11622 cx,
11623 );
11624 multibuffer.push_excerpts(
11625 buffer_3.clone(),
11626 [
11627 ExcerptRange {
11628 context: Point::new(0, 0)..Point::new(3, 0),
11629 primary: None,
11630 },
11631 ExcerptRange {
11632 context: Point::new(5, 0)..Point::new(7, 0),
11633 primary: None,
11634 },
11635 ExcerptRange {
11636 context: Point::new(9, 0)..Point::new(10, 3),
11637 primary: None,
11638 },
11639 ],
11640 cx,
11641 );
11642 multibuffer
11643 });
11644
11645 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11646 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11647 cx.run_until_parked();
11648
11649 cx.assert_editor_state(
11650 &"
11651 ˇaaa
11652 ccc
11653 ddd
11654
11655 ggg
11656 hhh
11657
11658
11659 lll
11660 mmm
11661 NNN
11662
11663 qqq
11664 rrr
11665
11666 uuu
11667 111
11668 222
11669 333
11670
11671 666
11672 777
11673
11674 000
11675 !!!"
11676 .unindent(),
11677 );
11678
11679 cx.update_editor(|editor, cx| {
11680 editor.select_all(&SelectAll, cx);
11681 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11682 });
11683 cx.executor().run_until_parked();
11684
11685 cx.assert_diff_hunks(
11686 "
11687 aaa
11688 - bbb
11689 ccc
11690 ddd
11691
11692 ggg
11693 hhh
11694
11695
11696 lll
11697 mmm
11698 - nnn
11699 + NNN
11700
11701 qqq
11702 rrr
11703
11704 uuu
11705 111
11706 222
11707 333
11708
11709 + 666
11710 777
11711
11712 000
11713 !!!"
11714 .unindent(),
11715 );
11716}
11717
11718#[gpui::test]
11719async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
11720 init_test(cx, |_| {});
11721
11722 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
11723 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
11724
11725 let buffer = cx.new_model(|cx| {
11726 let mut buffer = Buffer::local(text.to_string(), cx);
11727 buffer.set_diff_base(Some(base.into()), cx);
11728 buffer
11729 });
11730
11731 let multi_buffer = cx.new_model(|cx| {
11732 let mut multibuffer = MultiBuffer::new(ReadWrite);
11733 multibuffer.push_excerpts(
11734 buffer.clone(),
11735 [
11736 ExcerptRange {
11737 context: Point::new(0, 0)..Point::new(2, 0),
11738 primary: None,
11739 },
11740 ExcerptRange {
11741 context: Point::new(5, 0)..Point::new(7, 0),
11742 primary: None,
11743 },
11744 ],
11745 cx,
11746 );
11747 multibuffer
11748 });
11749
11750 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11751 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11752 cx.run_until_parked();
11753
11754 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
11755 cx.executor().run_until_parked();
11756
11757 cx.assert_diff_hunks(
11758 "
11759 aaa
11760 - bbb
11761 + BBB
11762
11763 - ddd
11764 - eee
11765 + EEE
11766 fff
11767 "
11768 .unindent(),
11769 );
11770}
11771
11772#[gpui::test]
11773async fn test_edits_around_expanded_insertion_hunks(
11774 executor: BackgroundExecutor,
11775 cx: &mut gpui::TestAppContext,
11776) {
11777 init_test(cx, |_| {});
11778
11779 let mut cx = EditorTestContext::new(cx).await;
11780
11781 let diff_base = r#"
11782 use some::mod1;
11783 use some::mod2;
11784
11785 const A: u32 = 42;
11786
11787 fn main() {
11788 println!("hello");
11789
11790 println!("world");
11791 }
11792 "#
11793 .unindent();
11794 executor.run_until_parked();
11795 cx.set_state(
11796 &r#"
11797 use some::mod1;
11798 use some::mod2;
11799
11800 const A: u32 = 42;
11801 const B: u32 = 42;
11802 const C: u32 = 42;
11803 ˇ
11804
11805 fn main() {
11806 println!("hello");
11807
11808 println!("world");
11809 }
11810 "#
11811 .unindent(),
11812 );
11813
11814 cx.set_diff_base(Some(&diff_base));
11815 executor.run_until_parked();
11816
11817 cx.update_editor(|editor, cx| {
11818 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11819 });
11820 executor.run_until_parked();
11821
11822 cx.assert_diff_hunks(
11823 r#"
11824 use some::mod1;
11825 use some::mod2;
11826
11827 const A: u32 = 42;
11828 + const B: u32 = 42;
11829 + const C: u32 = 42;
11830 +
11831
11832 fn main() {
11833 println!("hello");
11834
11835 println!("world");
11836 }
11837 "#
11838 .unindent(),
11839 );
11840
11841 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11842 executor.run_until_parked();
11843
11844 cx.assert_diff_hunks(
11845 r#"
11846 use some::mod1;
11847 use some::mod2;
11848
11849 const A: u32 = 42;
11850 + const B: u32 = 42;
11851 + const C: u32 = 42;
11852 + const D: u32 = 42;
11853 +
11854
11855 fn main() {
11856 println!("hello");
11857
11858 println!("world");
11859 }
11860 "#
11861 .unindent(),
11862 );
11863
11864 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11865 executor.run_until_parked();
11866
11867 cx.assert_diff_hunks(
11868 r#"
11869 use some::mod1;
11870 use some::mod2;
11871
11872 const A: u32 = 42;
11873 + const B: u32 = 42;
11874 + const C: u32 = 42;
11875 + const D: u32 = 42;
11876 + const E: u32 = 42;
11877 +
11878
11879 fn main() {
11880 println!("hello");
11881
11882 println!("world");
11883 }
11884 "#
11885 .unindent(),
11886 );
11887
11888 cx.update_editor(|editor, cx| {
11889 editor.delete_line(&DeleteLine, cx);
11890 });
11891 executor.run_until_parked();
11892
11893 cx.assert_diff_hunks(
11894 r#"
11895 use some::mod1;
11896 use some::mod2;
11897
11898 const A: u32 = 42;
11899 + const B: u32 = 42;
11900 + const C: u32 = 42;
11901 + const D: u32 = 42;
11902 + const E: u32 = 42;
11903
11904 fn main() {
11905 println!("hello");
11906
11907 println!("world");
11908 }
11909 "#
11910 .unindent(),
11911 );
11912
11913 cx.update_editor(|editor, cx| {
11914 editor.move_up(&MoveUp, cx);
11915 editor.delete_line(&DeleteLine, cx);
11916 editor.move_up(&MoveUp, cx);
11917 editor.delete_line(&DeleteLine, cx);
11918 editor.move_up(&MoveUp, cx);
11919 editor.delete_line(&DeleteLine, cx);
11920 });
11921 executor.run_until_parked();
11922 cx.assert_editor_state(
11923 &r#"
11924 use some::mod1;
11925 use some::mod2;
11926
11927 const A: u32 = 42;
11928 const B: u32 = 42;
11929 ˇ
11930 fn main() {
11931 println!("hello");
11932
11933 println!("world");
11934 }
11935 "#
11936 .unindent(),
11937 );
11938
11939 cx.assert_diff_hunks(
11940 r#"
11941 use some::mod1;
11942 use some::mod2;
11943
11944 const A: u32 = 42;
11945 + const B: u32 = 42;
11946
11947 fn main() {
11948 println!("hello");
11949
11950 println!("world");
11951 }
11952 "#
11953 .unindent(),
11954 );
11955
11956 cx.update_editor(|editor, cx| {
11957 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11958 editor.delete_line(&DeleteLine, cx);
11959 });
11960 executor.run_until_parked();
11961 cx.assert_diff_hunks(
11962 r#"
11963 use some::mod1;
11964 - use some::mod2;
11965 -
11966 - const A: u32 = 42;
11967
11968 fn main() {
11969 println!("hello");
11970
11971 println!("world");
11972 }
11973 "#
11974 .unindent(),
11975 );
11976}
11977
11978#[gpui::test]
11979async fn test_edits_around_expanded_deletion_hunks(
11980 executor: BackgroundExecutor,
11981 cx: &mut gpui::TestAppContext,
11982) {
11983 init_test(cx, |_| {});
11984
11985 let mut cx = EditorTestContext::new(cx).await;
11986
11987 let diff_base = r#"
11988 use some::mod1;
11989 use some::mod2;
11990
11991 const A: u32 = 42;
11992 const B: u32 = 42;
11993 const C: u32 = 42;
11994
11995
11996 fn main() {
11997 println!("hello");
11998
11999 println!("world");
12000 }
12001 "#
12002 .unindent();
12003 executor.run_until_parked();
12004 cx.set_state(
12005 &r#"
12006 use some::mod1;
12007 use some::mod2;
12008
12009 ˇconst B: u32 = 42;
12010 const C: u32 = 42;
12011
12012
12013 fn main() {
12014 println!("hello");
12015
12016 println!("world");
12017 }
12018 "#
12019 .unindent(),
12020 );
12021
12022 cx.set_diff_base(Some(&diff_base));
12023 executor.run_until_parked();
12024
12025 cx.update_editor(|editor, cx| {
12026 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12027 });
12028 executor.run_until_parked();
12029
12030 cx.assert_diff_hunks(
12031 r#"
12032 use some::mod1;
12033 use some::mod2;
12034
12035 - const A: u32 = 42;
12036 const B: u32 = 42;
12037 const C: u32 = 42;
12038
12039
12040 fn main() {
12041 println!("hello");
12042
12043 println!("world");
12044 }
12045 "#
12046 .unindent(),
12047 );
12048
12049 cx.update_editor(|editor, cx| {
12050 editor.delete_line(&DeleteLine, cx);
12051 });
12052 executor.run_until_parked();
12053 cx.assert_editor_state(
12054 &r#"
12055 use some::mod1;
12056 use some::mod2;
12057
12058 ˇconst C: u32 = 42;
12059
12060
12061 fn main() {
12062 println!("hello");
12063
12064 println!("world");
12065 }
12066 "#
12067 .unindent(),
12068 );
12069 cx.assert_diff_hunks(
12070 r#"
12071 use some::mod1;
12072 use some::mod2;
12073
12074 - const A: u32 = 42;
12075 - const B: u32 = 42;
12076 const C: u32 = 42;
12077
12078
12079 fn main() {
12080 println!("hello");
12081
12082 println!("world");
12083 }
12084 "#
12085 .unindent(),
12086 );
12087
12088 cx.update_editor(|editor, cx| {
12089 editor.delete_line(&DeleteLine, cx);
12090 });
12091 executor.run_until_parked();
12092 cx.assert_editor_state(
12093 &r#"
12094 use some::mod1;
12095 use some::mod2;
12096
12097 ˇ
12098
12099 fn main() {
12100 println!("hello");
12101
12102 println!("world");
12103 }
12104 "#
12105 .unindent(),
12106 );
12107 cx.assert_diff_hunks(
12108 r#"
12109 use some::mod1;
12110 use some::mod2;
12111
12112 - const A: u32 = 42;
12113 - const B: u32 = 42;
12114 - const C: u32 = 42;
12115
12116
12117 fn main() {
12118 println!("hello");
12119
12120 println!("world");
12121 }
12122 "#
12123 .unindent(),
12124 );
12125
12126 cx.update_editor(|editor, cx| {
12127 editor.handle_input("replacement", cx);
12128 });
12129 executor.run_until_parked();
12130 cx.assert_editor_state(
12131 &r#"
12132 use some::mod1;
12133 use some::mod2;
12134
12135 replacementˇ
12136
12137 fn main() {
12138 println!("hello");
12139
12140 println!("world");
12141 }
12142 "#
12143 .unindent(),
12144 );
12145 cx.assert_diff_hunks(
12146 r#"
12147 use some::mod1;
12148 use some::mod2;
12149
12150 - const A: u32 = 42;
12151 - const B: u32 = 42;
12152 - const C: u32 = 42;
12153 -
12154 + replacement
12155
12156 fn main() {
12157 println!("hello");
12158
12159 println!("world");
12160 }
12161 "#
12162 .unindent(),
12163 );
12164}
12165
12166#[gpui::test]
12167async fn test_edit_after_expanded_modification_hunk(
12168 executor: BackgroundExecutor,
12169 cx: &mut gpui::TestAppContext,
12170) {
12171 init_test(cx, |_| {});
12172
12173 let mut cx = EditorTestContext::new(cx).await;
12174
12175 let diff_base = r#"
12176 use some::mod1;
12177 use some::mod2;
12178
12179 const A: u32 = 42;
12180 const B: u32 = 42;
12181 const C: u32 = 42;
12182 const D: u32 = 42;
12183
12184
12185 fn main() {
12186 println!("hello");
12187
12188 println!("world");
12189 }"#
12190 .unindent();
12191
12192 cx.set_state(
12193 &r#"
12194 use some::mod1;
12195 use some::mod2;
12196
12197 const A: u32 = 42;
12198 const B: u32 = 42;
12199 const C: u32 = 43ˇ
12200 const D: u32 = 42;
12201
12202
12203 fn main() {
12204 println!("hello");
12205
12206 println!("world");
12207 }"#
12208 .unindent(),
12209 );
12210
12211 cx.set_diff_base(Some(&diff_base));
12212 executor.run_until_parked();
12213 cx.update_editor(|editor, cx| {
12214 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12215 });
12216 executor.run_until_parked();
12217
12218 cx.assert_diff_hunks(
12219 r#"
12220 use some::mod1;
12221 use some::mod2;
12222
12223 const A: u32 = 42;
12224 const B: u32 = 42;
12225 - const C: u32 = 42;
12226 + const C: u32 = 43
12227 const D: u32 = 42;
12228
12229
12230 fn main() {
12231 println!("hello");
12232
12233 println!("world");
12234 }"#
12235 .unindent(),
12236 );
12237
12238 cx.update_editor(|editor, cx| {
12239 editor.handle_input("\nnew_line\n", cx);
12240 });
12241 executor.run_until_parked();
12242
12243 cx.assert_diff_hunks(
12244 r#"
12245 use some::mod1;
12246 use some::mod2;
12247
12248 const A: u32 = 42;
12249 const B: u32 = 42;
12250 - const C: u32 = 42;
12251 + const C: u32 = 43
12252 + new_line
12253 +
12254 const D: u32 = 42;
12255
12256
12257 fn main() {
12258 println!("hello");
12259
12260 println!("world");
12261 }"#
12262 .unindent(),
12263 );
12264}
12265
12266async fn setup_indent_guides_editor(
12267 text: &str,
12268 cx: &mut gpui::TestAppContext,
12269) -> (BufferId, EditorTestContext) {
12270 init_test(cx, |_| {});
12271
12272 let mut cx = EditorTestContext::new(cx).await;
12273
12274 let buffer_id = cx.update_editor(|editor, cx| {
12275 editor.set_text(text, cx);
12276 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12277
12278 buffer_ids[0]
12279 });
12280
12281 (buffer_id, cx)
12282}
12283
12284fn assert_indent_guides(
12285 range: Range<u32>,
12286 expected: Vec<IndentGuide>,
12287 active_indices: Option<Vec<usize>>,
12288 cx: &mut EditorTestContext,
12289) {
12290 let indent_guides = cx.update_editor(|editor, cx| {
12291 let snapshot = editor.snapshot(cx).display_snapshot;
12292 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12293 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12294 true,
12295 &snapshot,
12296 cx,
12297 );
12298
12299 indent_guides.sort_by(|a, b| {
12300 a.depth.cmp(&b.depth).then(
12301 a.start_row
12302 .cmp(&b.start_row)
12303 .then(a.end_row.cmp(&b.end_row)),
12304 )
12305 });
12306 indent_guides
12307 });
12308
12309 if let Some(expected) = active_indices {
12310 let active_indices = cx.update_editor(|editor, cx| {
12311 let snapshot = editor.snapshot(cx).display_snapshot;
12312 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12313 });
12314
12315 assert_eq!(
12316 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12317 expected,
12318 "Active indent guide indices do not match"
12319 );
12320 }
12321
12322 let expected: Vec<_> = expected
12323 .into_iter()
12324 .map(|guide| MultiBufferIndentGuide {
12325 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12326 buffer: guide,
12327 })
12328 .collect();
12329
12330 assert_eq!(indent_guides, expected, "Indent guides do not match");
12331}
12332
12333fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12334 IndentGuide {
12335 buffer_id,
12336 start_row,
12337 end_row,
12338 depth,
12339 tab_size: 4,
12340 settings: IndentGuideSettings {
12341 enabled: true,
12342 line_width: 1,
12343 active_line_width: 1,
12344 ..Default::default()
12345 },
12346 }
12347}
12348
12349#[gpui::test]
12350async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12351 let (buffer_id, mut cx) = setup_indent_guides_editor(
12352 &"
12353 fn main() {
12354 let a = 1;
12355 }"
12356 .unindent(),
12357 cx,
12358 )
12359 .await;
12360
12361 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12362}
12363
12364#[gpui::test]
12365async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12366 let (buffer_id, mut cx) = setup_indent_guides_editor(
12367 &"
12368 fn main() {
12369 let a = 1;
12370 let b = 2;
12371 }"
12372 .unindent(),
12373 cx,
12374 )
12375 .await;
12376
12377 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12378}
12379
12380#[gpui::test]
12381async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12382 let (buffer_id, mut cx) = setup_indent_guides_editor(
12383 &"
12384 fn main() {
12385 let a = 1;
12386 if a == 3 {
12387 let b = 2;
12388 } else {
12389 let c = 3;
12390 }
12391 }"
12392 .unindent(),
12393 cx,
12394 )
12395 .await;
12396
12397 assert_indent_guides(
12398 0..8,
12399 vec![
12400 indent_guide(buffer_id, 1, 6, 0),
12401 indent_guide(buffer_id, 3, 3, 1),
12402 indent_guide(buffer_id, 5, 5, 1),
12403 ],
12404 None,
12405 &mut cx,
12406 );
12407}
12408
12409#[gpui::test]
12410async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12411 let (buffer_id, mut cx) = setup_indent_guides_editor(
12412 &"
12413 fn main() {
12414 let a = 1;
12415 let b = 2;
12416 let c = 3;
12417 }"
12418 .unindent(),
12419 cx,
12420 )
12421 .await;
12422
12423 assert_indent_guides(
12424 0..5,
12425 vec![
12426 indent_guide(buffer_id, 1, 3, 0),
12427 indent_guide(buffer_id, 2, 2, 1),
12428 ],
12429 None,
12430 &mut cx,
12431 );
12432}
12433
12434#[gpui::test]
12435async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12436 let (buffer_id, mut cx) = setup_indent_guides_editor(
12437 &"
12438 fn main() {
12439 let a = 1;
12440
12441 let c = 3;
12442 }"
12443 .unindent(),
12444 cx,
12445 )
12446 .await;
12447
12448 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12449}
12450
12451#[gpui::test]
12452async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12453 let (buffer_id, mut cx) = setup_indent_guides_editor(
12454 &"
12455 fn main() {
12456 let a = 1;
12457
12458 let c = 3;
12459
12460 if a == 3 {
12461 let b = 2;
12462 } else {
12463 let c = 3;
12464 }
12465 }"
12466 .unindent(),
12467 cx,
12468 )
12469 .await;
12470
12471 assert_indent_guides(
12472 0..11,
12473 vec![
12474 indent_guide(buffer_id, 1, 9, 0),
12475 indent_guide(buffer_id, 6, 6, 1),
12476 indent_guide(buffer_id, 8, 8, 1),
12477 ],
12478 None,
12479 &mut cx,
12480 );
12481}
12482
12483#[gpui::test]
12484async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12485 let (buffer_id, mut cx) = setup_indent_guides_editor(
12486 &"
12487 fn main() {
12488 let a = 1;
12489
12490 let c = 3;
12491
12492 if a == 3 {
12493 let b = 2;
12494 } else {
12495 let c = 3;
12496 }
12497 }"
12498 .unindent(),
12499 cx,
12500 )
12501 .await;
12502
12503 assert_indent_guides(
12504 1..11,
12505 vec![
12506 indent_guide(buffer_id, 1, 9, 0),
12507 indent_guide(buffer_id, 6, 6, 1),
12508 indent_guide(buffer_id, 8, 8, 1),
12509 ],
12510 None,
12511 &mut cx,
12512 );
12513}
12514
12515#[gpui::test]
12516async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12517 let (buffer_id, mut cx) = setup_indent_guides_editor(
12518 &"
12519 fn main() {
12520 let a = 1;
12521
12522 let c = 3;
12523
12524 if a == 3 {
12525 let b = 2;
12526 } else {
12527 let c = 3;
12528 }
12529 }"
12530 .unindent(),
12531 cx,
12532 )
12533 .await;
12534
12535 assert_indent_guides(
12536 1..10,
12537 vec![
12538 indent_guide(buffer_id, 1, 9, 0),
12539 indent_guide(buffer_id, 6, 6, 1),
12540 indent_guide(buffer_id, 8, 8, 1),
12541 ],
12542 None,
12543 &mut cx,
12544 );
12545}
12546
12547#[gpui::test]
12548async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12549 let (buffer_id, mut cx) = setup_indent_guides_editor(
12550 &"
12551 block1
12552 block2
12553 block3
12554 block4
12555 block2
12556 block1
12557 block1"
12558 .unindent(),
12559 cx,
12560 )
12561 .await;
12562
12563 assert_indent_guides(
12564 1..10,
12565 vec![
12566 indent_guide(buffer_id, 1, 4, 0),
12567 indent_guide(buffer_id, 2, 3, 1),
12568 indent_guide(buffer_id, 3, 3, 2),
12569 ],
12570 None,
12571 &mut cx,
12572 );
12573}
12574
12575#[gpui::test]
12576async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12577 let (buffer_id, mut cx) = setup_indent_guides_editor(
12578 &"
12579 block1
12580 block2
12581 block3
12582
12583 block1
12584 block1"
12585 .unindent(),
12586 cx,
12587 )
12588 .await;
12589
12590 assert_indent_guides(
12591 0..6,
12592 vec![
12593 indent_guide(buffer_id, 1, 2, 0),
12594 indent_guide(buffer_id, 2, 2, 1),
12595 ],
12596 None,
12597 &mut cx,
12598 );
12599}
12600
12601#[gpui::test]
12602async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12603 let (buffer_id, mut cx) = setup_indent_guides_editor(
12604 &"
12605 block1
12606
12607
12608
12609 block2
12610 "
12611 .unindent(),
12612 cx,
12613 )
12614 .await;
12615
12616 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12617}
12618
12619#[gpui::test]
12620async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12621 let (buffer_id, mut cx) = setup_indent_guides_editor(
12622 &"
12623 def a:
12624 \tb = 3
12625 \tif True:
12626 \t\tc = 4
12627 \t\td = 5
12628 \tprint(b)
12629 "
12630 .unindent(),
12631 cx,
12632 )
12633 .await;
12634
12635 assert_indent_guides(
12636 0..6,
12637 vec![
12638 indent_guide(buffer_id, 1, 6, 0),
12639 indent_guide(buffer_id, 3, 4, 1),
12640 ],
12641 None,
12642 &mut cx,
12643 );
12644}
12645
12646#[gpui::test]
12647async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12648 let (buffer_id, mut cx) = setup_indent_guides_editor(
12649 &"
12650 fn main() {
12651 let a = 1;
12652 }"
12653 .unindent(),
12654 cx,
12655 )
12656 .await;
12657
12658 cx.update_editor(|editor, cx| {
12659 editor.change_selections(None, cx, |s| {
12660 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12661 });
12662 });
12663
12664 assert_indent_guides(
12665 0..3,
12666 vec![indent_guide(buffer_id, 1, 1, 0)],
12667 Some(vec![0]),
12668 &mut cx,
12669 );
12670}
12671
12672#[gpui::test]
12673async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12674 let (buffer_id, mut cx) = setup_indent_guides_editor(
12675 &"
12676 fn main() {
12677 if 1 == 2 {
12678 let a = 1;
12679 }
12680 }"
12681 .unindent(),
12682 cx,
12683 )
12684 .await;
12685
12686 cx.update_editor(|editor, cx| {
12687 editor.change_selections(None, cx, |s| {
12688 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12689 });
12690 });
12691
12692 assert_indent_guides(
12693 0..4,
12694 vec![
12695 indent_guide(buffer_id, 1, 3, 0),
12696 indent_guide(buffer_id, 2, 2, 1),
12697 ],
12698 Some(vec![1]),
12699 &mut cx,
12700 );
12701
12702 cx.update_editor(|editor, cx| {
12703 editor.change_selections(None, cx, |s| {
12704 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12705 });
12706 });
12707
12708 assert_indent_guides(
12709 0..4,
12710 vec![
12711 indent_guide(buffer_id, 1, 3, 0),
12712 indent_guide(buffer_id, 2, 2, 1),
12713 ],
12714 Some(vec![1]),
12715 &mut cx,
12716 );
12717
12718 cx.update_editor(|editor, cx| {
12719 editor.change_selections(None, cx, |s| {
12720 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12721 });
12722 });
12723
12724 assert_indent_guides(
12725 0..4,
12726 vec![
12727 indent_guide(buffer_id, 1, 3, 0),
12728 indent_guide(buffer_id, 2, 2, 1),
12729 ],
12730 Some(vec![0]),
12731 &mut cx,
12732 );
12733}
12734
12735#[gpui::test]
12736async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12737 let (buffer_id, mut cx) = setup_indent_guides_editor(
12738 &"
12739 fn main() {
12740 let a = 1;
12741
12742 let b = 2;
12743 }"
12744 .unindent(),
12745 cx,
12746 )
12747 .await;
12748
12749 cx.update_editor(|editor, cx| {
12750 editor.change_selections(None, cx, |s| {
12751 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12752 });
12753 });
12754
12755 assert_indent_guides(
12756 0..5,
12757 vec![indent_guide(buffer_id, 1, 3, 0)],
12758 Some(vec![0]),
12759 &mut cx,
12760 );
12761}
12762
12763#[gpui::test]
12764async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12765 let (buffer_id, mut cx) = setup_indent_guides_editor(
12766 &"
12767 def m:
12768 a = 1
12769 pass"
12770 .unindent(),
12771 cx,
12772 )
12773 .await;
12774
12775 cx.update_editor(|editor, cx| {
12776 editor.change_selections(None, cx, |s| {
12777 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12778 });
12779 });
12780
12781 assert_indent_guides(
12782 0..3,
12783 vec![indent_guide(buffer_id, 1, 2, 0)],
12784 Some(vec![0]),
12785 &mut cx,
12786 );
12787}
12788
12789#[gpui::test]
12790fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12791 init_test(cx, |_| {});
12792
12793 let editor = cx.add_window(|cx| {
12794 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12795 build_editor(buffer, cx)
12796 });
12797
12798 let render_args = Arc::new(Mutex::new(None));
12799 let snapshot = editor
12800 .update(cx, |editor, cx| {
12801 let snapshot = editor.buffer().read(cx).snapshot(cx);
12802 let range =
12803 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12804
12805 struct RenderArgs {
12806 row: MultiBufferRow,
12807 folded: bool,
12808 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12809 }
12810
12811 let crease = Crease::new(
12812 range,
12813 FoldPlaceholder::test(),
12814 {
12815 let toggle_callback = render_args.clone();
12816 move |row, folded, callback, _cx| {
12817 *toggle_callback.lock() = Some(RenderArgs {
12818 row,
12819 folded,
12820 callback,
12821 });
12822 div()
12823 }
12824 },
12825 |_row, _folded, _cx| div(),
12826 );
12827
12828 editor.insert_creases(Some(crease), cx);
12829 let snapshot = editor.snapshot(cx);
12830 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12831 snapshot
12832 })
12833 .unwrap();
12834
12835 let render_args = render_args.lock().take().unwrap();
12836 assert_eq!(render_args.row, MultiBufferRow(1));
12837 assert!(!render_args.folded);
12838 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12839
12840 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12841 .unwrap();
12842 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12843 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12844
12845 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12846 .unwrap();
12847 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12848 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12849}
12850
12851#[gpui::test]
12852async fn test_input_text(cx: &mut gpui::TestAppContext) {
12853 init_test(cx, |_| {});
12854 let mut cx = EditorTestContext::new(cx).await;
12855
12856 cx.set_state(
12857 &r#"ˇone
12858 two
12859
12860 three
12861 fourˇ
12862 five
12863
12864 siˇx"#
12865 .unindent(),
12866 );
12867
12868 cx.dispatch_action(HandleInput(String::new()));
12869 cx.assert_editor_state(
12870 &r#"ˇone
12871 two
12872
12873 three
12874 fourˇ
12875 five
12876
12877 siˇx"#
12878 .unindent(),
12879 );
12880
12881 cx.dispatch_action(HandleInput("AAAA".to_string()));
12882 cx.assert_editor_state(
12883 &r#"AAAAˇone
12884 two
12885
12886 three
12887 fourAAAAˇ
12888 five
12889
12890 siAAAAˇx"#
12891 .unindent(),
12892 );
12893}
12894
12895#[gpui::test]
12896async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
12897 init_test(cx, |_| {});
12898
12899 let mut cx = EditorTestContext::new(cx).await;
12900 cx.set_state(
12901 r#"let foo = 1;
12902let foo = 2;
12903let foo = 3;
12904let fooˇ = 4;
12905let foo = 5;
12906let foo = 6;
12907let foo = 7;
12908let foo = 8;
12909let foo = 9;
12910let foo = 10;
12911let foo = 11;
12912let foo = 12;
12913let foo = 13;
12914let foo = 14;
12915let foo = 15;"#,
12916 );
12917
12918 cx.update_editor(|e, cx| {
12919 assert_eq!(
12920 e.next_scroll_position,
12921 NextScrollCursorCenterTopBottom::Center,
12922 "Default next scroll direction is center",
12923 );
12924
12925 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12926 assert_eq!(
12927 e.next_scroll_position,
12928 NextScrollCursorCenterTopBottom::Top,
12929 "After center, next scroll direction should be top",
12930 );
12931
12932 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12933 assert_eq!(
12934 e.next_scroll_position,
12935 NextScrollCursorCenterTopBottom::Bottom,
12936 "After top, next scroll direction should be bottom",
12937 );
12938
12939 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12940 assert_eq!(
12941 e.next_scroll_position,
12942 NextScrollCursorCenterTopBottom::Center,
12943 "After bottom, scrolling should start over",
12944 );
12945
12946 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12947 assert_eq!(
12948 e.next_scroll_position,
12949 NextScrollCursorCenterTopBottom::Top,
12950 "Scrolling continues if retriggered fast enough"
12951 );
12952 });
12953
12954 cx.executor()
12955 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
12956 cx.executor().run_until_parked();
12957 cx.update_editor(|e, _| {
12958 assert_eq!(
12959 e.next_scroll_position,
12960 NextScrollCursorCenterTopBottom::Center,
12961 "If scrolling is not triggered fast enough, it should reset"
12962 );
12963 });
12964}
12965
12966#[gpui::test]
12967async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
12968 init_test(cx, |_| {});
12969 let mut cx = EditorLspTestContext::new_rust(
12970 lsp::ServerCapabilities {
12971 definition_provider: Some(lsp::OneOf::Left(true)),
12972 references_provider: Some(lsp::OneOf::Left(true)),
12973 ..lsp::ServerCapabilities::default()
12974 },
12975 cx,
12976 )
12977 .await;
12978
12979 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
12980 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
12981 move |params, _| async move {
12982 if empty_go_to_definition {
12983 Ok(None)
12984 } else {
12985 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
12986 uri: params.text_document_position_params.text_document.uri,
12987 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
12988 })))
12989 }
12990 },
12991 );
12992 let references =
12993 cx.lsp
12994 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
12995 Ok(Some(vec![lsp::Location {
12996 uri: params.text_document_position.text_document.uri,
12997 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
12998 }]))
12999 });
13000 (go_to_definition, references)
13001 };
13002
13003 cx.set_state(
13004 &r#"fn one() {
13005 let mut a = ˇtwo();
13006 }
13007
13008 fn two() {}"#
13009 .unindent(),
13010 );
13011 set_up_lsp_handlers(false, &mut cx);
13012 let navigated = cx
13013 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13014 .await
13015 .expect("Failed to navigate to definition");
13016 assert_eq!(
13017 navigated,
13018 Navigated::Yes,
13019 "Should have navigated to definition from the GetDefinition response"
13020 );
13021 cx.assert_editor_state(
13022 &r#"fn one() {
13023 let mut a = two();
13024 }
13025
13026 fn «twoˇ»() {}"#
13027 .unindent(),
13028 );
13029
13030 let editors = cx.update_workspace(|workspace, cx| {
13031 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13032 });
13033 cx.update_editor(|_, test_editor_cx| {
13034 assert_eq!(
13035 editors.len(),
13036 1,
13037 "Initially, only one, test, editor should be open in the workspace"
13038 );
13039 assert_eq!(
13040 test_editor_cx.view(),
13041 editors.last().expect("Asserted len is 1")
13042 );
13043 });
13044
13045 set_up_lsp_handlers(true, &mut cx);
13046 let navigated = cx
13047 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13048 .await
13049 .expect("Failed to navigate to lookup references");
13050 assert_eq!(
13051 navigated,
13052 Navigated::Yes,
13053 "Should have navigated to references as a fallback after empty GoToDefinition response"
13054 );
13055 // We should not change the selections in the existing file,
13056 // if opening another milti buffer with the references
13057 cx.assert_editor_state(
13058 &r#"fn one() {
13059 let mut a = two();
13060 }
13061
13062 fn «twoˇ»() {}"#
13063 .unindent(),
13064 );
13065 let editors = cx.update_workspace(|workspace, cx| {
13066 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13067 });
13068 cx.update_editor(|_, test_editor_cx| {
13069 assert_eq!(
13070 editors.len(),
13071 2,
13072 "After falling back to references search, we open a new editor with the results"
13073 );
13074 let references_fallback_text = editors
13075 .into_iter()
13076 .find(|new_editor| new_editor != test_editor_cx.view())
13077 .expect("Should have one non-test editor now")
13078 .read(test_editor_cx)
13079 .text(test_editor_cx);
13080 assert_eq!(
13081 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13082 "Should use the range from the references response and not the GoToDefinition one"
13083 );
13084 });
13085}
13086
13087fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13088 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13089 point..point
13090}
13091
13092fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13093 let (text, ranges) = marked_text_ranges(marked_text, true);
13094 assert_eq!(view.text(cx), text);
13095 assert_eq!(
13096 view.selections.ranges(cx),
13097 ranges,
13098 "Assert selections are {}",
13099 marked_text
13100 );
13101}
13102
13103pub fn handle_signature_help_request(
13104 cx: &mut EditorLspTestContext,
13105 mocked_response: lsp::SignatureHelp,
13106) -> impl Future<Output = ()> {
13107 let mut request =
13108 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13109 let mocked_response = mocked_response.clone();
13110 async move { Ok(Some(mocked_response)) }
13111 });
13112
13113 async move {
13114 request.next().await;
13115 }
13116}
13117
13118/// Handle completion request passing a marked string specifying where the completion
13119/// should be triggered from using '|' character, what range should be replaced, and what completions
13120/// should be returned using '<' and '>' to delimit the range
13121pub fn handle_completion_request(
13122 cx: &mut EditorLspTestContext,
13123 marked_string: &str,
13124 completions: Vec<&'static str>,
13125 counter: Arc<AtomicUsize>,
13126) -> impl Future<Output = ()> {
13127 let complete_from_marker: TextRangeMarker = '|'.into();
13128 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13129 let (_, mut marked_ranges) = marked_text_ranges_by(
13130 marked_string,
13131 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13132 );
13133
13134 let complete_from_position =
13135 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13136 let replace_range =
13137 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13138
13139 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13140 let completions = completions.clone();
13141 counter.fetch_add(1, atomic::Ordering::Release);
13142 async move {
13143 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13144 assert_eq!(
13145 params.text_document_position.position,
13146 complete_from_position
13147 );
13148 Ok(Some(lsp::CompletionResponse::Array(
13149 completions
13150 .iter()
13151 .map(|completion_text| lsp::CompletionItem {
13152 label: completion_text.to_string(),
13153 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13154 range: replace_range,
13155 new_text: completion_text.to_string(),
13156 })),
13157 ..Default::default()
13158 })
13159 .collect(),
13160 )))
13161 }
13162 });
13163
13164 async move {
13165 request.next().await;
13166 }
13167}
13168
13169fn handle_resolve_completion_request(
13170 cx: &mut EditorLspTestContext,
13171 edits: Option<Vec<(&'static str, &'static str)>>,
13172) -> impl Future<Output = ()> {
13173 let edits = edits.map(|edits| {
13174 edits
13175 .iter()
13176 .map(|(marked_string, new_text)| {
13177 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13178 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13179 lsp::TextEdit::new(replace_range, new_text.to_string())
13180 })
13181 .collect::<Vec<_>>()
13182 });
13183
13184 let mut request =
13185 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13186 let edits = edits.clone();
13187 async move {
13188 Ok(lsp::CompletionItem {
13189 additional_text_edits: edits,
13190 ..Default::default()
13191 })
13192 }
13193 });
13194
13195 async move {
13196 request.next().await;
13197 }
13198}
13199
13200pub(crate) fn update_test_language_settings(
13201 cx: &mut TestAppContext,
13202 f: impl Fn(&mut AllLanguageSettingsContent),
13203) {
13204 cx.update(|cx| {
13205 SettingsStore::update_global(cx, |store, cx| {
13206 store.update_user_settings::<AllLanguageSettings>(cx, f);
13207 });
13208 });
13209}
13210
13211pub(crate) fn update_test_project_settings(
13212 cx: &mut TestAppContext,
13213 f: impl Fn(&mut ProjectSettings),
13214) {
13215 cx.update(|cx| {
13216 SettingsStore::update_global(cx, |store, cx| {
13217 store.update_user_settings::<ProjectSettings>(cx, f);
13218 });
13219 });
13220}
13221
13222pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13223 cx.update(|cx| {
13224 assets::Assets.load_test_fonts(cx);
13225 let store = SettingsStore::test(cx);
13226 cx.set_global(store);
13227 theme::init(theme::LoadThemes::JustBase, cx);
13228 release_channel::init(SemanticVersion::default(), cx);
13229 client::init_settings(cx);
13230 language::init(cx);
13231 Project::init_settings(cx);
13232 workspace::init_settings(cx);
13233 crate::init(cx);
13234 });
13235
13236 update_test_language_settings(cx, f);
13237}
13238
13239pub(crate) fn rust_lang() -> Arc<Language> {
13240 Arc::new(Language::new(
13241 LanguageConfig {
13242 name: "Rust".into(),
13243 matcher: LanguageMatcher {
13244 path_suffixes: vec!["rs".to_string()],
13245 ..Default::default()
13246 },
13247 ..Default::default()
13248 },
13249 Some(tree_sitter_rust::LANGUAGE.into()),
13250 ))
13251}
13252
13253#[track_caller]
13254fn assert_hunk_revert(
13255 not_reverted_text_with_selections: &str,
13256 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13257 expected_reverted_text_with_selections: &str,
13258 base_text: &str,
13259 cx: &mut EditorLspTestContext,
13260) {
13261 cx.set_state(not_reverted_text_with_selections);
13262 cx.update_editor(|editor, cx| {
13263 editor
13264 .buffer()
13265 .read(cx)
13266 .as_singleton()
13267 .unwrap()
13268 .update(cx, |buffer, cx| {
13269 buffer.set_diff_base(Some(base_text.into()), cx);
13270 });
13271 });
13272 cx.executor().run_until_parked();
13273
13274 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13275 let snapshot = editor.buffer().read(cx).snapshot(cx);
13276 let reverted_hunk_statuses = snapshot
13277 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13278 .map(|hunk| hunk_status(&hunk))
13279 .collect::<Vec<_>>();
13280
13281 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13282 reverted_hunk_statuses
13283 });
13284 cx.executor().run_until_parked();
13285 cx.assert_editor_state(expected_reverted_text_with_selections);
13286 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13287}