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(
7080 project.clone(),
7081 FormatTrigger::Manual,
7082 FormatTarget::Buffer,
7083 cx,
7084 )
7085 })
7086 .unwrap();
7087 fake_server
7088 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7089 assert_eq!(
7090 params.text_document.uri,
7091 lsp::Url::from_file_path("/file.rs").unwrap()
7092 );
7093 assert_eq!(params.options.tab_size, 4);
7094 Ok(Some(vec![lsp::TextEdit::new(
7095 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7096 ", ".to_string(),
7097 )]))
7098 })
7099 .next()
7100 .await;
7101 cx.executor().start_waiting();
7102 format.await;
7103 assert_eq!(
7104 editor.update(cx, |editor, cx| editor.text(cx)),
7105 "one, two\nthree\n"
7106 );
7107
7108 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7109 // Ensure we don't lock if formatting hangs.
7110 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7111 assert_eq!(
7112 params.text_document.uri,
7113 lsp::Url::from_file_path("/file.rs").unwrap()
7114 );
7115 futures::future::pending::<()>().await;
7116 unreachable!()
7117 });
7118 let format = editor
7119 .update(cx, |editor, cx| {
7120 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7121 })
7122 .unwrap();
7123 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7124 cx.executor().start_waiting();
7125 format.await;
7126 assert_eq!(
7127 editor.update(cx, |editor, cx| editor.text(cx)),
7128 "one\ntwo\nthree\n"
7129 );
7130}
7131
7132#[gpui::test]
7133async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7134 init_test(cx, |_| {});
7135
7136 let mut cx = EditorLspTestContext::new_rust(
7137 lsp::ServerCapabilities {
7138 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7139 ..Default::default()
7140 },
7141 cx,
7142 )
7143 .await;
7144
7145 cx.set_state(indoc! {"
7146 one.twoˇ
7147 "});
7148
7149 // The format request takes a long time. When it completes, it inserts
7150 // a newline and an indent before the `.`
7151 cx.lsp
7152 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7153 let executor = cx.background_executor().clone();
7154 async move {
7155 executor.timer(Duration::from_millis(100)).await;
7156 Ok(Some(vec![lsp::TextEdit {
7157 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7158 new_text: "\n ".into(),
7159 }]))
7160 }
7161 });
7162
7163 // Submit a format request.
7164 let format_1 = cx
7165 .update_editor(|editor, cx| editor.format(&Format, cx))
7166 .unwrap();
7167 cx.executor().run_until_parked();
7168
7169 // Submit a second format request.
7170 let format_2 = cx
7171 .update_editor(|editor, cx| editor.format(&Format, cx))
7172 .unwrap();
7173 cx.executor().run_until_parked();
7174
7175 // Wait for both format requests to complete
7176 cx.executor().advance_clock(Duration::from_millis(200));
7177 cx.executor().start_waiting();
7178 format_1.await.unwrap();
7179 cx.executor().start_waiting();
7180 format_2.await.unwrap();
7181
7182 // The formatting edits only happens once.
7183 cx.assert_editor_state(indoc! {"
7184 one
7185 .twoˇ
7186 "});
7187}
7188
7189#[gpui::test]
7190async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7191 init_test(cx, |settings| {
7192 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7193 });
7194
7195 let mut cx = EditorLspTestContext::new_rust(
7196 lsp::ServerCapabilities {
7197 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7198 ..Default::default()
7199 },
7200 cx,
7201 )
7202 .await;
7203
7204 // Set up a buffer white some trailing whitespace and no trailing newline.
7205 cx.set_state(
7206 &[
7207 "one ", //
7208 "twoˇ", //
7209 "three ", //
7210 "four", //
7211 ]
7212 .join("\n"),
7213 );
7214
7215 // Submit a format request.
7216 let format = cx
7217 .update_editor(|editor, cx| editor.format(&Format, cx))
7218 .unwrap();
7219
7220 // Record which buffer changes have been sent to the language server
7221 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7222 cx.lsp
7223 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7224 let buffer_changes = buffer_changes.clone();
7225 move |params, _| {
7226 buffer_changes.lock().extend(
7227 params
7228 .content_changes
7229 .into_iter()
7230 .map(|e| (e.range.unwrap(), e.text)),
7231 );
7232 }
7233 });
7234
7235 // Handle formatting requests to the language server.
7236 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7237 let buffer_changes = buffer_changes.clone();
7238 move |_, _| {
7239 // When formatting is requested, trailing whitespace has already been stripped,
7240 // and the trailing newline has already been added.
7241 assert_eq!(
7242 &buffer_changes.lock()[1..],
7243 &[
7244 (
7245 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7246 "".into()
7247 ),
7248 (
7249 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7250 "".into()
7251 ),
7252 (
7253 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7254 "\n".into()
7255 ),
7256 ]
7257 );
7258
7259 // Insert blank lines between each line of the buffer.
7260 async move {
7261 Ok(Some(vec![
7262 lsp::TextEdit {
7263 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7264 new_text: "\n".into(),
7265 },
7266 lsp::TextEdit {
7267 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7268 new_text: "\n".into(),
7269 },
7270 ]))
7271 }
7272 }
7273 });
7274
7275 // After formatting the buffer, the trailing whitespace is stripped,
7276 // a newline is appended, and the edits provided by the language server
7277 // have been applied.
7278 format.await.unwrap();
7279 cx.assert_editor_state(
7280 &[
7281 "one", //
7282 "", //
7283 "twoˇ", //
7284 "", //
7285 "three", //
7286 "four", //
7287 "", //
7288 ]
7289 .join("\n"),
7290 );
7291
7292 // Undoing the formatting undoes the trailing whitespace removal, the
7293 // trailing newline, and the LSP edits.
7294 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7295 cx.assert_editor_state(
7296 &[
7297 "one ", //
7298 "twoˇ", //
7299 "three ", //
7300 "four", //
7301 ]
7302 .join("\n"),
7303 );
7304}
7305
7306#[gpui::test]
7307async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7308 cx: &mut gpui::TestAppContext,
7309) {
7310 init_test(cx, |_| {});
7311
7312 cx.update(|cx| {
7313 cx.update_global::<SettingsStore, _>(|settings, cx| {
7314 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7315 settings.auto_signature_help = Some(true);
7316 });
7317 });
7318 });
7319
7320 let mut cx = EditorLspTestContext::new_rust(
7321 lsp::ServerCapabilities {
7322 signature_help_provider: Some(lsp::SignatureHelpOptions {
7323 ..Default::default()
7324 }),
7325 ..Default::default()
7326 },
7327 cx,
7328 )
7329 .await;
7330
7331 let language = Language::new(
7332 LanguageConfig {
7333 name: "Rust".into(),
7334 brackets: BracketPairConfig {
7335 pairs: vec![
7336 BracketPair {
7337 start: "{".to_string(),
7338 end: "}".to_string(),
7339 close: true,
7340 surround: true,
7341 newline: true,
7342 },
7343 BracketPair {
7344 start: "(".to_string(),
7345 end: ")".to_string(),
7346 close: true,
7347 surround: true,
7348 newline: true,
7349 },
7350 BracketPair {
7351 start: "/*".to_string(),
7352 end: " */".to_string(),
7353 close: true,
7354 surround: true,
7355 newline: true,
7356 },
7357 BracketPair {
7358 start: "[".to_string(),
7359 end: "]".to_string(),
7360 close: false,
7361 surround: false,
7362 newline: true,
7363 },
7364 BracketPair {
7365 start: "\"".to_string(),
7366 end: "\"".to_string(),
7367 close: true,
7368 surround: true,
7369 newline: false,
7370 },
7371 BracketPair {
7372 start: "<".to_string(),
7373 end: ">".to_string(),
7374 close: false,
7375 surround: true,
7376 newline: true,
7377 },
7378 ],
7379 ..Default::default()
7380 },
7381 autoclose_before: "})]".to_string(),
7382 ..Default::default()
7383 },
7384 Some(tree_sitter_rust::LANGUAGE.into()),
7385 );
7386 let language = Arc::new(language);
7387
7388 cx.language_registry().add(language.clone());
7389 cx.update_buffer(|buffer, cx| {
7390 buffer.set_language(Some(language), cx);
7391 });
7392
7393 cx.set_state(
7394 &r#"
7395 fn main() {
7396 sampleˇ
7397 }
7398 "#
7399 .unindent(),
7400 );
7401
7402 cx.update_editor(|view, cx| {
7403 view.handle_input("(", cx);
7404 });
7405 cx.assert_editor_state(
7406 &"
7407 fn main() {
7408 sample(ˇ)
7409 }
7410 "
7411 .unindent(),
7412 );
7413
7414 let mocked_response = lsp::SignatureHelp {
7415 signatures: vec![lsp::SignatureInformation {
7416 label: "fn sample(param1: u8, param2: u8)".to_string(),
7417 documentation: None,
7418 parameters: Some(vec![
7419 lsp::ParameterInformation {
7420 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7421 documentation: None,
7422 },
7423 lsp::ParameterInformation {
7424 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7425 documentation: None,
7426 },
7427 ]),
7428 active_parameter: None,
7429 }],
7430 active_signature: Some(0),
7431 active_parameter: Some(0),
7432 };
7433 handle_signature_help_request(&mut cx, mocked_response).await;
7434
7435 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7436 .await;
7437
7438 cx.editor(|editor, _| {
7439 let signature_help_state = editor.signature_help_state.popover().cloned();
7440 assert!(signature_help_state.is_some());
7441 let ParsedMarkdown {
7442 text, highlights, ..
7443 } = signature_help_state.unwrap().parsed_content;
7444 assert_eq!(text, "param1: u8, param2: u8");
7445 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7446 });
7447}
7448
7449#[gpui::test]
7450async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7451 init_test(cx, |_| {});
7452
7453 cx.update(|cx| {
7454 cx.update_global::<SettingsStore, _>(|settings, cx| {
7455 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7456 settings.auto_signature_help = Some(false);
7457 settings.show_signature_help_after_edits = Some(false);
7458 });
7459 });
7460 });
7461
7462 let mut cx = EditorLspTestContext::new_rust(
7463 lsp::ServerCapabilities {
7464 signature_help_provider: Some(lsp::SignatureHelpOptions {
7465 ..Default::default()
7466 }),
7467 ..Default::default()
7468 },
7469 cx,
7470 )
7471 .await;
7472
7473 let language = Language::new(
7474 LanguageConfig {
7475 name: "Rust".into(),
7476 brackets: BracketPairConfig {
7477 pairs: vec![
7478 BracketPair {
7479 start: "{".to_string(),
7480 end: "}".to_string(),
7481 close: true,
7482 surround: true,
7483 newline: true,
7484 },
7485 BracketPair {
7486 start: "(".to_string(),
7487 end: ")".to_string(),
7488 close: true,
7489 surround: true,
7490 newline: true,
7491 },
7492 BracketPair {
7493 start: "/*".to_string(),
7494 end: " */".to_string(),
7495 close: true,
7496 surround: true,
7497 newline: true,
7498 },
7499 BracketPair {
7500 start: "[".to_string(),
7501 end: "]".to_string(),
7502 close: false,
7503 surround: false,
7504 newline: true,
7505 },
7506 BracketPair {
7507 start: "\"".to_string(),
7508 end: "\"".to_string(),
7509 close: true,
7510 surround: true,
7511 newline: false,
7512 },
7513 BracketPair {
7514 start: "<".to_string(),
7515 end: ">".to_string(),
7516 close: false,
7517 surround: true,
7518 newline: true,
7519 },
7520 ],
7521 ..Default::default()
7522 },
7523 autoclose_before: "})]".to_string(),
7524 ..Default::default()
7525 },
7526 Some(tree_sitter_rust::LANGUAGE.into()),
7527 );
7528 let language = Arc::new(language);
7529
7530 cx.language_registry().add(language.clone());
7531 cx.update_buffer(|buffer, cx| {
7532 buffer.set_language(Some(language), cx);
7533 });
7534
7535 // Ensure that signature_help is not called when no signature help is enabled.
7536 cx.set_state(
7537 &r#"
7538 fn main() {
7539 sampleˇ
7540 }
7541 "#
7542 .unindent(),
7543 );
7544 cx.update_editor(|view, cx| {
7545 view.handle_input("(", cx);
7546 });
7547 cx.assert_editor_state(
7548 &"
7549 fn main() {
7550 sample(ˇ)
7551 }
7552 "
7553 .unindent(),
7554 );
7555 cx.editor(|editor, _| {
7556 assert!(editor.signature_help_state.task().is_none());
7557 });
7558
7559 let mocked_response = lsp::SignatureHelp {
7560 signatures: vec![lsp::SignatureInformation {
7561 label: "fn sample(param1: u8, param2: u8)".to_string(),
7562 documentation: None,
7563 parameters: Some(vec![
7564 lsp::ParameterInformation {
7565 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7566 documentation: None,
7567 },
7568 lsp::ParameterInformation {
7569 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7570 documentation: None,
7571 },
7572 ]),
7573 active_parameter: None,
7574 }],
7575 active_signature: Some(0),
7576 active_parameter: Some(0),
7577 };
7578
7579 // Ensure that signature_help is called when enabled afte edits
7580 cx.update(|cx| {
7581 cx.update_global::<SettingsStore, _>(|settings, cx| {
7582 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7583 settings.auto_signature_help = Some(false);
7584 settings.show_signature_help_after_edits = Some(true);
7585 });
7586 });
7587 });
7588 cx.set_state(
7589 &r#"
7590 fn main() {
7591 sampleˇ
7592 }
7593 "#
7594 .unindent(),
7595 );
7596 cx.update_editor(|view, cx| {
7597 view.handle_input("(", cx);
7598 });
7599 cx.assert_editor_state(
7600 &"
7601 fn main() {
7602 sample(ˇ)
7603 }
7604 "
7605 .unindent(),
7606 );
7607 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7608 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7609 .await;
7610 cx.update_editor(|editor, _| {
7611 let signature_help_state = editor.signature_help_state.popover().cloned();
7612 assert!(signature_help_state.is_some());
7613 let ParsedMarkdown {
7614 text, highlights, ..
7615 } = signature_help_state.unwrap().parsed_content;
7616 assert_eq!(text, "param1: u8, param2: u8");
7617 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7618 editor.signature_help_state = SignatureHelpState::default();
7619 });
7620
7621 // Ensure that signature_help is called when auto signature help override is enabled
7622 cx.update(|cx| {
7623 cx.update_global::<SettingsStore, _>(|settings, cx| {
7624 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7625 settings.auto_signature_help = Some(true);
7626 settings.show_signature_help_after_edits = Some(false);
7627 });
7628 });
7629 });
7630 cx.set_state(
7631 &r#"
7632 fn main() {
7633 sampleˇ
7634 }
7635 "#
7636 .unindent(),
7637 );
7638 cx.update_editor(|view, cx| {
7639 view.handle_input("(", cx);
7640 });
7641 cx.assert_editor_state(
7642 &"
7643 fn main() {
7644 sample(ˇ)
7645 }
7646 "
7647 .unindent(),
7648 );
7649 handle_signature_help_request(&mut cx, mocked_response).await;
7650 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7651 .await;
7652 cx.editor(|editor, _| {
7653 let signature_help_state = editor.signature_help_state.popover().cloned();
7654 assert!(signature_help_state.is_some());
7655 let ParsedMarkdown {
7656 text, highlights, ..
7657 } = signature_help_state.unwrap().parsed_content;
7658 assert_eq!(text, "param1: u8, param2: u8");
7659 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7660 });
7661}
7662
7663#[gpui::test]
7664async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7665 init_test(cx, |_| {});
7666 cx.update(|cx| {
7667 cx.update_global::<SettingsStore, _>(|settings, cx| {
7668 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7669 settings.auto_signature_help = Some(true);
7670 });
7671 });
7672 });
7673
7674 let mut cx = EditorLspTestContext::new_rust(
7675 lsp::ServerCapabilities {
7676 signature_help_provider: Some(lsp::SignatureHelpOptions {
7677 ..Default::default()
7678 }),
7679 ..Default::default()
7680 },
7681 cx,
7682 )
7683 .await;
7684
7685 // A test that directly calls `show_signature_help`
7686 cx.update_editor(|editor, cx| {
7687 editor.show_signature_help(&ShowSignatureHelp, cx);
7688 });
7689
7690 let mocked_response = lsp::SignatureHelp {
7691 signatures: vec![lsp::SignatureInformation {
7692 label: "fn sample(param1: u8, param2: u8)".to_string(),
7693 documentation: None,
7694 parameters: Some(vec![
7695 lsp::ParameterInformation {
7696 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7697 documentation: None,
7698 },
7699 lsp::ParameterInformation {
7700 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7701 documentation: None,
7702 },
7703 ]),
7704 active_parameter: None,
7705 }],
7706 active_signature: Some(0),
7707 active_parameter: Some(0),
7708 };
7709 handle_signature_help_request(&mut cx, mocked_response).await;
7710
7711 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7712 .await;
7713
7714 cx.editor(|editor, _| {
7715 let signature_help_state = editor.signature_help_state.popover().cloned();
7716 assert!(signature_help_state.is_some());
7717 let ParsedMarkdown {
7718 text, highlights, ..
7719 } = signature_help_state.unwrap().parsed_content;
7720 assert_eq!(text, "param1: u8, param2: u8");
7721 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7722 });
7723
7724 // When exiting outside from inside the brackets, `signature_help` is closed.
7725 cx.set_state(indoc! {"
7726 fn main() {
7727 sample(ˇ);
7728 }
7729
7730 fn sample(param1: u8, param2: u8) {}
7731 "});
7732
7733 cx.update_editor(|editor, cx| {
7734 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7735 });
7736
7737 let mocked_response = lsp::SignatureHelp {
7738 signatures: Vec::new(),
7739 active_signature: None,
7740 active_parameter: None,
7741 };
7742 handle_signature_help_request(&mut cx, mocked_response).await;
7743
7744 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7745 .await;
7746
7747 cx.editor(|editor, _| {
7748 assert!(!editor.signature_help_state.is_shown());
7749 });
7750
7751 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7752 cx.set_state(indoc! {"
7753 fn main() {
7754 sample(ˇ);
7755 }
7756
7757 fn sample(param1: u8, param2: u8) {}
7758 "});
7759
7760 let mocked_response = lsp::SignatureHelp {
7761 signatures: vec![lsp::SignatureInformation {
7762 label: "fn sample(param1: u8, param2: u8)".to_string(),
7763 documentation: None,
7764 parameters: Some(vec![
7765 lsp::ParameterInformation {
7766 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7767 documentation: None,
7768 },
7769 lsp::ParameterInformation {
7770 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7771 documentation: None,
7772 },
7773 ]),
7774 active_parameter: None,
7775 }],
7776 active_signature: Some(0),
7777 active_parameter: Some(0),
7778 };
7779 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7780 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7781 .await;
7782 cx.editor(|editor, _| {
7783 assert!(editor.signature_help_state.is_shown());
7784 });
7785
7786 // Restore the popover with more parameter input
7787 cx.set_state(indoc! {"
7788 fn main() {
7789 sample(param1, param2ˇ);
7790 }
7791
7792 fn sample(param1: u8, param2: u8) {}
7793 "});
7794
7795 let mocked_response = lsp::SignatureHelp {
7796 signatures: vec![lsp::SignatureInformation {
7797 label: "fn sample(param1: u8, param2: u8)".to_string(),
7798 documentation: None,
7799 parameters: Some(vec![
7800 lsp::ParameterInformation {
7801 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7802 documentation: None,
7803 },
7804 lsp::ParameterInformation {
7805 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7806 documentation: None,
7807 },
7808 ]),
7809 active_parameter: None,
7810 }],
7811 active_signature: Some(0),
7812 active_parameter: Some(1),
7813 };
7814 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7815 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7816 .await;
7817
7818 // When selecting a range, the popover is gone.
7819 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7820 cx.update_editor(|editor, cx| {
7821 editor.change_selections(None, cx, |s| {
7822 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7823 })
7824 });
7825 cx.assert_editor_state(indoc! {"
7826 fn main() {
7827 sample(param1, «ˇparam2»);
7828 }
7829
7830 fn sample(param1: u8, param2: u8) {}
7831 "});
7832 cx.editor(|editor, _| {
7833 assert!(!editor.signature_help_state.is_shown());
7834 });
7835
7836 // When unselecting again, the popover is back if within the brackets.
7837 cx.update_editor(|editor, cx| {
7838 editor.change_selections(None, cx, |s| {
7839 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7840 })
7841 });
7842 cx.assert_editor_state(indoc! {"
7843 fn main() {
7844 sample(param1, ˇparam2);
7845 }
7846
7847 fn sample(param1: u8, param2: u8) {}
7848 "});
7849 handle_signature_help_request(&mut cx, mocked_response).await;
7850 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7851 .await;
7852 cx.editor(|editor, _| {
7853 assert!(editor.signature_help_state.is_shown());
7854 });
7855
7856 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7857 cx.update_editor(|editor, cx| {
7858 editor.change_selections(None, cx, |s| {
7859 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7860 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7861 })
7862 });
7863 cx.assert_editor_state(indoc! {"
7864 fn main() {
7865 sample(param1, ˇparam2);
7866 }
7867
7868 fn sample(param1: u8, param2: u8) {}
7869 "});
7870
7871 let mocked_response = lsp::SignatureHelp {
7872 signatures: vec![lsp::SignatureInformation {
7873 label: "fn sample(param1: u8, param2: u8)".to_string(),
7874 documentation: None,
7875 parameters: Some(vec![
7876 lsp::ParameterInformation {
7877 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7878 documentation: None,
7879 },
7880 lsp::ParameterInformation {
7881 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7882 documentation: None,
7883 },
7884 ]),
7885 active_parameter: None,
7886 }],
7887 active_signature: Some(0),
7888 active_parameter: Some(1),
7889 };
7890 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7891 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7892 .await;
7893 cx.update_editor(|editor, cx| {
7894 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7895 });
7896 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7897 .await;
7898 cx.update_editor(|editor, cx| {
7899 editor.change_selections(None, cx, |s| {
7900 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7901 })
7902 });
7903 cx.assert_editor_state(indoc! {"
7904 fn main() {
7905 sample(param1, «ˇparam2»);
7906 }
7907
7908 fn sample(param1: u8, param2: u8) {}
7909 "});
7910 cx.update_editor(|editor, cx| {
7911 editor.change_selections(None, cx, |s| {
7912 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7913 })
7914 });
7915 cx.assert_editor_state(indoc! {"
7916 fn main() {
7917 sample(param1, ˇparam2);
7918 }
7919
7920 fn sample(param1: u8, param2: u8) {}
7921 "});
7922 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7923 .await;
7924}
7925
7926#[gpui::test]
7927async fn test_completion(cx: &mut gpui::TestAppContext) {
7928 init_test(cx, |_| {});
7929
7930 let mut cx = EditorLspTestContext::new_rust(
7931 lsp::ServerCapabilities {
7932 completion_provider: Some(lsp::CompletionOptions {
7933 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7934 resolve_provider: Some(true),
7935 ..Default::default()
7936 }),
7937 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7938 ..Default::default()
7939 },
7940 cx,
7941 )
7942 .await;
7943 let counter = Arc::new(AtomicUsize::new(0));
7944
7945 cx.set_state(indoc! {"
7946 oneˇ
7947 two
7948 three
7949 "});
7950 cx.simulate_keystroke(".");
7951 handle_completion_request(
7952 &mut cx,
7953 indoc! {"
7954 one.|<>
7955 two
7956 three
7957 "},
7958 vec!["first_completion", "second_completion"],
7959 counter.clone(),
7960 )
7961 .await;
7962 cx.condition(|editor, _| editor.context_menu_visible())
7963 .await;
7964 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7965
7966 let _handler = handle_signature_help_request(
7967 &mut cx,
7968 lsp::SignatureHelp {
7969 signatures: vec![lsp::SignatureInformation {
7970 label: "test signature".to_string(),
7971 documentation: None,
7972 parameters: Some(vec![lsp::ParameterInformation {
7973 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7974 documentation: None,
7975 }]),
7976 active_parameter: None,
7977 }],
7978 active_signature: None,
7979 active_parameter: None,
7980 },
7981 );
7982 cx.update_editor(|editor, cx| {
7983 assert!(
7984 !editor.signature_help_state.is_shown(),
7985 "No signature help was called for"
7986 );
7987 editor.show_signature_help(&ShowSignatureHelp, cx);
7988 });
7989 cx.run_until_parked();
7990 cx.update_editor(|editor, _| {
7991 assert!(
7992 !editor.signature_help_state.is_shown(),
7993 "No signature help should be shown when completions menu is open"
7994 );
7995 });
7996
7997 let apply_additional_edits = cx.update_editor(|editor, cx| {
7998 editor.context_menu_next(&Default::default(), cx);
7999 editor
8000 .confirm_completion(&ConfirmCompletion::default(), cx)
8001 .unwrap()
8002 });
8003 cx.assert_editor_state(indoc! {"
8004 one.second_completionˇ
8005 two
8006 three
8007 "});
8008
8009 handle_resolve_completion_request(
8010 &mut cx,
8011 Some(vec![
8012 (
8013 //This overlaps with the primary completion edit which is
8014 //misbehavior from the LSP spec, test that we filter it out
8015 indoc! {"
8016 one.second_ˇcompletion
8017 two
8018 threeˇ
8019 "},
8020 "overlapping additional edit",
8021 ),
8022 (
8023 indoc! {"
8024 one.second_completion
8025 two
8026 threeˇ
8027 "},
8028 "\nadditional edit",
8029 ),
8030 ]),
8031 )
8032 .await;
8033 apply_additional_edits.await.unwrap();
8034 cx.assert_editor_state(indoc! {"
8035 one.second_completionˇ
8036 two
8037 three
8038 additional edit
8039 "});
8040
8041 cx.set_state(indoc! {"
8042 one.second_completion
8043 twoˇ
8044 threeˇ
8045 additional edit
8046 "});
8047 cx.simulate_keystroke(" ");
8048 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8049 cx.simulate_keystroke("s");
8050 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8051
8052 cx.assert_editor_state(indoc! {"
8053 one.second_completion
8054 two sˇ
8055 three sˇ
8056 additional edit
8057 "});
8058 handle_completion_request(
8059 &mut cx,
8060 indoc! {"
8061 one.second_completion
8062 two s
8063 three <s|>
8064 additional edit
8065 "},
8066 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8067 counter.clone(),
8068 )
8069 .await;
8070 cx.condition(|editor, _| editor.context_menu_visible())
8071 .await;
8072 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8073
8074 cx.simulate_keystroke("i");
8075
8076 handle_completion_request(
8077 &mut cx,
8078 indoc! {"
8079 one.second_completion
8080 two si
8081 three <si|>
8082 additional edit
8083 "},
8084 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8085 counter.clone(),
8086 )
8087 .await;
8088 cx.condition(|editor, _| editor.context_menu_visible())
8089 .await;
8090 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8091
8092 let apply_additional_edits = cx.update_editor(|editor, cx| {
8093 editor
8094 .confirm_completion(&ConfirmCompletion::default(), cx)
8095 .unwrap()
8096 });
8097 cx.assert_editor_state(indoc! {"
8098 one.second_completion
8099 two sixth_completionˇ
8100 three sixth_completionˇ
8101 additional edit
8102 "});
8103
8104 handle_resolve_completion_request(&mut cx, None).await;
8105 apply_additional_edits.await.unwrap();
8106
8107 cx.update(|cx| {
8108 cx.update_global::<SettingsStore, _>(|settings, cx| {
8109 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8110 settings.show_completions_on_input = Some(false);
8111 });
8112 })
8113 });
8114 cx.set_state("editorˇ");
8115 cx.simulate_keystroke(".");
8116 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8117 cx.simulate_keystroke("c");
8118 cx.simulate_keystroke("l");
8119 cx.simulate_keystroke("o");
8120 cx.assert_editor_state("editor.cloˇ");
8121 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8122 cx.update_editor(|editor, cx| {
8123 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8124 });
8125 handle_completion_request(
8126 &mut cx,
8127 "editor.<clo|>",
8128 vec!["close", "clobber"],
8129 counter.clone(),
8130 )
8131 .await;
8132 cx.condition(|editor, _| editor.context_menu_visible())
8133 .await;
8134 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8135
8136 let apply_additional_edits = cx.update_editor(|editor, cx| {
8137 editor
8138 .confirm_completion(&ConfirmCompletion::default(), cx)
8139 .unwrap()
8140 });
8141 cx.assert_editor_state("editor.closeˇ");
8142 handle_resolve_completion_request(&mut cx, None).await;
8143 apply_additional_edits.await.unwrap();
8144}
8145
8146#[gpui::test]
8147async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8148 init_test(cx, |_| {});
8149 let mut cx = EditorLspTestContext::new_rust(
8150 lsp::ServerCapabilities {
8151 completion_provider: Some(lsp::CompletionOptions {
8152 trigger_characters: Some(vec![".".to_string()]),
8153 ..Default::default()
8154 }),
8155 ..Default::default()
8156 },
8157 cx,
8158 )
8159 .await;
8160 cx.lsp
8161 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8162 Ok(Some(lsp::CompletionResponse::Array(vec![
8163 lsp::CompletionItem {
8164 label: "first".into(),
8165 ..Default::default()
8166 },
8167 lsp::CompletionItem {
8168 label: "last".into(),
8169 ..Default::default()
8170 },
8171 ])))
8172 });
8173 cx.set_state("variableˇ");
8174 cx.simulate_keystroke(".");
8175 cx.executor().run_until_parked();
8176
8177 cx.update_editor(|editor, _| {
8178 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8179 assert_eq!(
8180 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8181 &["first", "last"]
8182 );
8183 } else {
8184 panic!("expected completion menu to be open");
8185 }
8186 });
8187
8188 cx.update_editor(|editor, cx| {
8189 editor.move_page_down(&MovePageDown::default(), cx);
8190 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8191 assert!(
8192 menu.selected_item == 1,
8193 "expected PageDown to select the last item from the context menu"
8194 );
8195 } else {
8196 panic!("expected completion menu to stay open after PageDown");
8197 }
8198 });
8199
8200 cx.update_editor(|editor, cx| {
8201 editor.move_page_up(&MovePageUp::default(), cx);
8202 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8203 assert!(
8204 menu.selected_item == 0,
8205 "expected PageUp to select the first item from the context menu"
8206 );
8207 } else {
8208 panic!("expected completion menu to stay open after PageUp");
8209 }
8210 });
8211}
8212
8213#[gpui::test]
8214async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8215 init_test(cx, |_| {});
8216
8217 let mut cx = EditorLspTestContext::new_rust(
8218 lsp::ServerCapabilities {
8219 completion_provider: Some(lsp::CompletionOptions {
8220 trigger_characters: Some(vec![".".to_string()]),
8221 resolve_provider: Some(true),
8222 ..Default::default()
8223 }),
8224 ..Default::default()
8225 },
8226 cx,
8227 )
8228 .await;
8229
8230 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8231 cx.simulate_keystroke(".");
8232 let completion_item = lsp::CompletionItem {
8233 label: "Some".into(),
8234 kind: Some(lsp::CompletionItemKind::SNIPPET),
8235 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8236 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8237 kind: lsp::MarkupKind::Markdown,
8238 value: "```rust\nSome(2)\n```".to_string(),
8239 })),
8240 deprecated: Some(false),
8241 sort_text: Some("Some".to_string()),
8242 filter_text: Some("Some".to_string()),
8243 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8244 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8245 range: lsp::Range {
8246 start: lsp::Position {
8247 line: 0,
8248 character: 22,
8249 },
8250 end: lsp::Position {
8251 line: 0,
8252 character: 22,
8253 },
8254 },
8255 new_text: "Some(2)".to_string(),
8256 })),
8257 additional_text_edits: Some(vec![lsp::TextEdit {
8258 range: lsp::Range {
8259 start: lsp::Position {
8260 line: 0,
8261 character: 20,
8262 },
8263 end: lsp::Position {
8264 line: 0,
8265 character: 22,
8266 },
8267 },
8268 new_text: "".to_string(),
8269 }]),
8270 ..Default::default()
8271 };
8272
8273 let closure_completion_item = completion_item.clone();
8274 let counter = Arc::new(AtomicUsize::new(0));
8275 let counter_clone = counter.clone();
8276 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8277 let task_completion_item = closure_completion_item.clone();
8278 counter_clone.fetch_add(1, atomic::Ordering::Release);
8279 async move {
8280 Ok(Some(lsp::CompletionResponse::Array(vec![
8281 task_completion_item,
8282 ])))
8283 }
8284 });
8285
8286 cx.condition(|editor, _| editor.context_menu_visible())
8287 .await;
8288 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8289 assert!(request.next().await.is_some());
8290 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8291
8292 cx.simulate_keystroke("S");
8293 cx.simulate_keystroke("o");
8294 cx.simulate_keystroke("m");
8295 cx.condition(|editor, _| editor.context_menu_visible())
8296 .await;
8297 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8298 assert!(request.next().await.is_some());
8299 assert!(request.next().await.is_some());
8300 assert!(request.next().await.is_some());
8301 request.close();
8302 assert!(request.next().await.is_none());
8303 assert_eq!(
8304 counter.load(atomic::Ordering::Acquire),
8305 4,
8306 "With the completions menu open, only one LSP request should happen per input"
8307 );
8308}
8309
8310#[gpui::test]
8311async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8312 init_test(cx, |_| {});
8313 let mut cx = EditorTestContext::new(cx).await;
8314 let language = Arc::new(Language::new(
8315 LanguageConfig {
8316 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8317 ..Default::default()
8318 },
8319 Some(tree_sitter_rust::LANGUAGE.into()),
8320 ));
8321 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8322
8323 // If multiple selections intersect a line, the line is only toggled once.
8324 cx.set_state(indoc! {"
8325 fn a() {
8326 «//b();
8327 ˇ»// «c();
8328 //ˇ» d();
8329 }
8330 "});
8331
8332 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8333
8334 cx.assert_editor_state(indoc! {"
8335 fn a() {
8336 «b();
8337 c();
8338 ˇ» d();
8339 }
8340 "});
8341
8342 // The comment prefix is inserted at the same column for every line in a
8343 // selection.
8344 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8345
8346 cx.assert_editor_state(indoc! {"
8347 fn a() {
8348 // «b();
8349 // c();
8350 ˇ»// d();
8351 }
8352 "});
8353
8354 // If a selection ends at the beginning of a line, that line is not toggled.
8355 cx.set_selections_state(indoc! {"
8356 fn a() {
8357 // b();
8358 «// c();
8359 ˇ» // d();
8360 }
8361 "});
8362
8363 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8364
8365 cx.assert_editor_state(indoc! {"
8366 fn a() {
8367 // b();
8368 «c();
8369 ˇ» // d();
8370 }
8371 "});
8372
8373 // If a selection span a single line and is empty, the line is toggled.
8374 cx.set_state(indoc! {"
8375 fn a() {
8376 a();
8377 b();
8378 ˇ
8379 }
8380 "});
8381
8382 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8383
8384 cx.assert_editor_state(indoc! {"
8385 fn a() {
8386 a();
8387 b();
8388 //•ˇ
8389 }
8390 "});
8391
8392 // If a selection span multiple lines, empty lines are not toggled.
8393 cx.set_state(indoc! {"
8394 fn a() {
8395 «a();
8396
8397 c();ˇ»
8398 }
8399 "});
8400
8401 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8402
8403 cx.assert_editor_state(indoc! {"
8404 fn a() {
8405 // «a();
8406
8407 // c();ˇ»
8408 }
8409 "});
8410
8411 // If a selection includes multiple comment prefixes, all lines are uncommented.
8412 cx.set_state(indoc! {"
8413 fn a() {
8414 «// a();
8415 /// b();
8416 //! c();ˇ»
8417 }
8418 "});
8419
8420 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8421
8422 cx.assert_editor_state(indoc! {"
8423 fn a() {
8424 «a();
8425 b();
8426 c();ˇ»
8427 }
8428 "});
8429}
8430
8431#[gpui::test]
8432async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8433 init_test(cx, |_| {});
8434
8435 let language = Arc::new(Language::new(
8436 LanguageConfig {
8437 line_comments: vec!["// ".into()],
8438 ..Default::default()
8439 },
8440 Some(tree_sitter_rust::LANGUAGE.into()),
8441 ));
8442
8443 let mut cx = EditorTestContext::new(cx).await;
8444
8445 cx.language_registry().add(language.clone());
8446 cx.update_buffer(|buffer, cx| {
8447 buffer.set_language(Some(language), cx);
8448 });
8449
8450 let toggle_comments = &ToggleComments {
8451 advance_downwards: true,
8452 };
8453
8454 // Single cursor on one line -> advance
8455 // Cursor moves horizontally 3 characters as well on non-blank line
8456 cx.set_state(indoc!(
8457 "fn a() {
8458 ˇdog();
8459 cat();
8460 }"
8461 ));
8462 cx.update_editor(|editor, cx| {
8463 editor.toggle_comments(toggle_comments, cx);
8464 });
8465 cx.assert_editor_state(indoc!(
8466 "fn a() {
8467 // dog();
8468 catˇ();
8469 }"
8470 ));
8471
8472 // Single selection on one line -> don't advance
8473 cx.set_state(indoc!(
8474 "fn a() {
8475 «dog()ˇ»;
8476 cat();
8477 }"
8478 ));
8479 cx.update_editor(|editor, cx| {
8480 editor.toggle_comments(toggle_comments, cx);
8481 });
8482 cx.assert_editor_state(indoc!(
8483 "fn a() {
8484 // «dog()ˇ»;
8485 cat();
8486 }"
8487 ));
8488
8489 // Multiple cursors on one line -> advance
8490 cx.set_state(indoc!(
8491 "fn a() {
8492 ˇdˇog();
8493 cat();
8494 }"
8495 ));
8496 cx.update_editor(|editor, cx| {
8497 editor.toggle_comments(toggle_comments, cx);
8498 });
8499 cx.assert_editor_state(indoc!(
8500 "fn a() {
8501 // dog();
8502 catˇ(ˇ);
8503 }"
8504 ));
8505
8506 // Multiple cursors on one line, with selection -> don't advance
8507 cx.set_state(indoc!(
8508 "fn a() {
8509 ˇdˇog«()ˇ»;
8510 cat();
8511 }"
8512 ));
8513 cx.update_editor(|editor, cx| {
8514 editor.toggle_comments(toggle_comments, cx);
8515 });
8516 cx.assert_editor_state(indoc!(
8517 "fn a() {
8518 // ˇdˇog«()ˇ»;
8519 cat();
8520 }"
8521 ));
8522
8523 // Single cursor on one line -> advance
8524 // Cursor moves to column 0 on blank line
8525 cx.set_state(indoc!(
8526 "fn a() {
8527 ˇdog();
8528
8529 cat();
8530 }"
8531 ));
8532 cx.update_editor(|editor, cx| {
8533 editor.toggle_comments(toggle_comments, cx);
8534 });
8535 cx.assert_editor_state(indoc!(
8536 "fn a() {
8537 // dog();
8538 ˇ
8539 cat();
8540 }"
8541 ));
8542
8543 // Single cursor on one line -> advance
8544 // Cursor starts and ends at column 0
8545 cx.set_state(indoc!(
8546 "fn a() {
8547 ˇ dog();
8548 cat();
8549 }"
8550 ));
8551 cx.update_editor(|editor, cx| {
8552 editor.toggle_comments(toggle_comments, cx);
8553 });
8554 cx.assert_editor_state(indoc!(
8555 "fn a() {
8556 // dog();
8557 ˇ cat();
8558 }"
8559 ));
8560}
8561
8562#[gpui::test]
8563async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8564 init_test(cx, |_| {});
8565
8566 let mut cx = EditorTestContext::new(cx).await;
8567
8568 let html_language = Arc::new(
8569 Language::new(
8570 LanguageConfig {
8571 name: "HTML".into(),
8572 block_comment: Some(("<!-- ".into(), " -->".into())),
8573 ..Default::default()
8574 },
8575 Some(tree_sitter_html::language()),
8576 )
8577 .with_injection_query(
8578 r#"
8579 (script_element
8580 (raw_text) @content
8581 (#set! "language" "javascript"))
8582 "#,
8583 )
8584 .unwrap(),
8585 );
8586
8587 let javascript_language = Arc::new(Language::new(
8588 LanguageConfig {
8589 name: "JavaScript".into(),
8590 line_comments: vec!["// ".into()],
8591 ..Default::default()
8592 },
8593 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8594 ));
8595
8596 cx.language_registry().add(html_language.clone());
8597 cx.language_registry().add(javascript_language.clone());
8598 cx.update_buffer(|buffer, cx| {
8599 buffer.set_language(Some(html_language), cx);
8600 });
8601
8602 // Toggle comments for empty selections
8603 cx.set_state(
8604 &r#"
8605 <p>A</p>ˇ
8606 <p>B</p>ˇ
8607 <p>C</p>ˇ
8608 "#
8609 .unindent(),
8610 );
8611 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8612 cx.assert_editor_state(
8613 &r#"
8614 <!-- <p>A</p>ˇ -->
8615 <!-- <p>B</p>ˇ -->
8616 <!-- <p>C</p>ˇ -->
8617 "#
8618 .unindent(),
8619 );
8620 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8621 cx.assert_editor_state(
8622 &r#"
8623 <p>A</p>ˇ
8624 <p>B</p>ˇ
8625 <p>C</p>ˇ
8626 "#
8627 .unindent(),
8628 );
8629
8630 // Toggle comments for mixture of empty and non-empty selections, where
8631 // multiple selections occupy a given line.
8632 cx.set_state(
8633 &r#"
8634 <p>A«</p>
8635 <p>ˇ»B</p>ˇ
8636 <p>C«</p>
8637 <p>ˇ»D</p>ˇ
8638 "#
8639 .unindent(),
8640 );
8641
8642 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8643 cx.assert_editor_state(
8644 &r#"
8645 <!-- <p>A«</p>
8646 <p>ˇ»B</p>ˇ -->
8647 <!-- <p>C«</p>
8648 <p>ˇ»D</p>ˇ -->
8649 "#
8650 .unindent(),
8651 );
8652 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8653 cx.assert_editor_state(
8654 &r#"
8655 <p>A«</p>
8656 <p>ˇ»B</p>ˇ
8657 <p>C«</p>
8658 <p>ˇ»D</p>ˇ
8659 "#
8660 .unindent(),
8661 );
8662
8663 // Toggle comments when different languages are active for different
8664 // selections.
8665 cx.set_state(
8666 &r#"
8667 ˇ<script>
8668 ˇvar x = new Y();
8669 ˇ</script>
8670 "#
8671 .unindent(),
8672 );
8673 cx.executor().run_until_parked();
8674 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8675 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8676 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8677 cx.assert_editor_state(
8678 &r#"
8679 <!-- ˇ<script> -->
8680 // ˇvar x = new Y();
8681 // ˇ</script>
8682 "#
8683 .unindent(),
8684 );
8685}
8686
8687#[gpui::test]
8688fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8689 init_test(cx, |_| {});
8690
8691 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8692 let multibuffer = cx.new_model(|cx| {
8693 let mut multibuffer = MultiBuffer::new(ReadWrite);
8694 multibuffer.push_excerpts(
8695 buffer.clone(),
8696 [
8697 ExcerptRange {
8698 context: Point::new(0, 0)..Point::new(0, 4),
8699 primary: None,
8700 },
8701 ExcerptRange {
8702 context: Point::new(1, 0)..Point::new(1, 4),
8703 primary: None,
8704 },
8705 ],
8706 cx,
8707 );
8708 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8709 multibuffer
8710 });
8711
8712 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8713 view.update(cx, |view, cx| {
8714 assert_eq!(view.text(cx), "aaaa\nbbbb");
8715 view.change_selections(None, cx, |s| {
8716 s.select_ranges([
8717 Point::new(0, 0)..Point::new(0, 0),
8718 Point::new(1, 0)..Point::new(1, 0),
8719 ])
8720 });
8721
8722 view.handle_input("X", cx);
8723 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8724 assert_eq!(
8725 view.selections.ranges(cx),
8726 [
8727 Point::new(0, 1)..Point::new(0, 1),
8728 Point::new(1, 1)..Point::new(1, 1),
8729 ]
8730 );
8731
8732 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8733 view.change_selections(None, cx, |s| {
8734 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8735 });
8736 view.backspace(&Default::default(), cx);
8737 assert_eq!(view.text(cx), "Xa\nbbb");
8738 assert_eq!(
8739 view.selections.ranges(cx),
8740 [Point::new(1, 0)..Point::new(1, 0)]
8741 );
8742
8743 view.change_selections(None, cx, |s| {
8744 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8745 });
8746 view.backspace(&Default::default(), cx);
8747 assert_eq!(view.text(cx), "X\nbb");
8748 assert_eq!(
8749 view.selections.ranges(cx),
8750 [Point::new(0, 1)..Point::new(0, 1)]
8751 );
8752 });
8753}
8754
8755#[gpui::test]
8756fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8757 init_test(cx, |_| {});
8758
8759 let markers = vec![('[', ']').into(), ('(', ')').into()];
8760 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8761 indoc! {"
8762 [aaaa
8763 (bbbb]
8764 cccc)",
8765 },
8766 markers.clone(),
8767 );
8768 let excerpt_ranges = markers.into_iter().map(|marker| {
8769 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8770 ExcerptRange {
8771 context,
8772 primary: None,
8773 }
8774 });
8775 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8776 let multibuffer = cx.new_model(|cx| {
8777 let mut multibuffer = MultiBuffer::new(ReadWrite);
8778 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8779 multibuffer
8780 });
8781
8782 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8783 view.update(cx, |view, cx| {
8784 let (expected_text, selection_ranges) = marked_text_ranges(
8785 indoc! {"
8786 aaaa
8787 bˇbbb
8788 bˇbbˇb
8789 cccc"
8790 },
8791 true,
8792 );
8793 assert_eq!(view.text(cx), expected_text);
8794 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8795
8796 view.handle_input("X", cx);
8797
8798 let (expected_text, expected_selections) = marked_text_ranges(
8799 indoc! {"
8800 aaaa
8801 bXˇbbXb
8802 bXˇbbXˇb
8803 cccc"
8804 },
8805 false,
8806 );
8807 assert_eq!(view.text(cx), expected_text);
8808 assert_eq!(view.selections.ranges(cx), expected_selections);
8809
8810 view.newline(&Newline, cx);
8811 let (expected_text, expected_selections) = marked_text_ranges(
8812 indoc! {"
8813 aaaa
8814 bX
8815 ˇbbX
8816 b
8817 bX
8818 ˇbbX
8819 ˇb
8820 cccc"
8821 },
8822 false,
8823 );
8824 assert_eq!(view.text(cx), expected_text);
8825 assert_eq!(view.selections.ranges(cx), expected_selections);
8826 });
8827}
8828
8829#[gpui::test]
8830fn test_refresh_selections(cx: &mut TestAppContext) {
8831 init_test(cx, |_| {});
8832
8833 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8834 let mut excerpt1_id = None;
8835 let multibuffer = cx.new_model(|cx| {
8836 let mut multibuffer = MultiBuffer::new(ReadWrite);
8837 excerpt1_id = multibuffer
8838 .push_excerpts(
8839 buffer.clone(),
8840 [
8841 ExcerptRange {
8842 context: Point::new(0, 0)..Point::new(1, 4),
8843 primary: None,
8844 },
8845 ExcerptRange {
8846 context: Point::new(1, 0)..Point::new(2, 4),
8847 primary: None,
8848 },
8849 ],
8850 cx,
8851 )
8852 .into_iter()
8853 .next();
8854 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8855 multibuffer
8856 });
8857
8858 let editor = cx.add_window(|cx| {
8859 let mut editor = build_editor(multibuffer.clone(), cx);
8860 let snapshot = editor.snapshot(cx);
8861 editor.change_selections(None, cx, |s| {
8862 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8863 });
8864 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8865 assert_eq!(
8866 editor.selections.ranges(cx),
8867 [
8868 Point::new(1, 3)..Point::new(1, 3),
8869 Point::new(2, 1)..Point::new(2, 1),
8870 ]
8871 );
8872 editor
8873 });
8874
8875 // Refreshing selections is a no-op when excerpts haven't changed.
8876 _ = editor.update(cx, |editor, cx| {
8877 editor.change_selections(None, cx, |s| s.refresh());
8878 assert_eq!(
8879 editor.selections.ranges(cx),
8880 [
8881 Point::new(1, 3)..Point::new(1, 3),
8882 Point::new(2, 1)..Point::new(2, 1),
8883 ]
8884 );
8885 });
8886
8887 multibuffer.update(cx, |multibuffer, cx| {
8888 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8889 });
8890 _ = editor.update(cx, |editor, cx| {
8891 // Removing an excerpt causes the first selection to become degenerate.
8892 assert_eq!(
8893 editor.selections.ranges(cx),
8894 [
8895 Point::new(0, 0)..Point::new(0, 0),
8896 Point::new(0, 1)..Point::new(0, 1)
8897 ]
8898 );
8899
8900 // Refreshing selections will relocate the first selection to the original buffer
8901 // location.
8902 editor.change_selections(None, cx, |s| s.refresh());
8903 assert_eq!(
8904 editor.selections.ranges(cx),
8905 [
8906 Point::new(0, 1)..Point::new(0, 1),
8907 Point::new(0, 3)..Point::new(0, 3)
8908 ]
8909 );
8910 assert!(editor.selections.pending_anchor().is_some());
8911 });
8912}
8913
8914#[gpui::test]
8915fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8916 init_test(cx, |_| {});
8917
8918 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8919 let mut excerpt1_id = None;
8920 let multibuffer = cx.new_model(|cx| {
8921 let mut multibuffer = MultiBuffer::new(ReadWrite);
8922 excerpt1_id = multibuffer
8923 .push_excerpts(
8924 buffer.clone(),
8925 [
8926 ExcerptRange {
8927 context: Point::new(0, 0)..Point::new(1, 4),
8928 primary: None,
8929 },
8930 ExcerptRange {
8931 context: Point::new(1, 0)..Point::new(2, 4),
8932 primary: None,
8933 },
8934 ],
8935 cx,
8936 )
8937 .into_iter()
8938 .next();
8939 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8940 multibuffer
8941 });
8942
8943 let editor = cx.add_window(|cx| {
8944 let mut editor = build_editor(multibuffer.clone(), cx);
8945 let snapshot = editor.snapshot(cx);
8946 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8947 assert_eq!(
8948 editor.selections.ranges(cx),
8949 [Point::new(1, 3)..Point::new(1, 3)]
8950 );
8951 editor
8952 });
8953
8954 multibuffer.update(cx, |multibuffer, cx| {
8955 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8956 });
8957 _ = editor.update(cx, |editor, cx| {
8958 assert_eq!(
8959 editor.selections.ranges(cx),
8960 [Point::new(0, 0)..Point::new(0, 0)]
8961 );
8962
8963 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8964 editor.change_selections(None, cx, |s| s.refresh());
8965 assert_eq!(
8966 editor.selections.ranges(cx),
8967 [Point::new(0, 3)..Point::new(0, 3)]
8968 );
8969 assert!(editor.selections.pending_anchor().is_some());
8970 });
8971}
8972
8973#[gpui::test]
8974async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8975 init_test(cx, |_| {});
8976
8977 let language = Arc::new(
8978 Language::new(
8979 LanguageConfig {
8980 brackets: BracketPairConfig {
8981 pairs: vec![
8982 BracketPair {
8983 start: "{".to_string(),
8984 end: "}".to_string(),
8985 close: true,
8986 surround: true,
8987 newline: true,
8988 },
8989 BracketPair {
8990 start: "/* ".to_string(),
8991 end: " */".to_string(),
8992 close: true,
8993 surround: true,
8994 newline: true,
8995 },
8996 ],
8997 ..Default::default()
8998 },
8999 ..Default::default()
9000 },
9001 Some(tree_sitter_rust::LANGUAGE.into()),
9002 )
9003 .with_indents_query("")
9004 .unwrap(),
9005 );
9006
9007 let text = concat!(
9008 "{ }\n", //
9009 " x\n", //
9010 " /* */\n", //
9011 "x\n", //
9012 "{{} }\n", //
9013 );
9014
9015 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9016 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9017 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9018 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9019 .await;
9020
9021 view.update(cx, |view, cx| {
9022 view.change_selections(None, cx, |s| {
9023 s.select_display_ranges([
9024 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9025 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9026 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9027 ])
9028 });
9029 view.newline(&Newline, cx);
9030
9031 assert_eq!(
9032 view.buffer().read(cx).read(cx).text(),
9033 concat!(
9034 "{ \n", // Suppress rustfmt
9035 "\n", //
9036 "}\n", //
9037 " x\n", //
9038 " /* \n", //
9039 " \n", //
9040 " */\n", //
9041 "x\n", //
9042 "{{} \n", //
9043 "}\n", //
9044 )
9045 );
9046 });
9047}
9048
9049#[gpui::test]
9050fn test_highlighted_ranges(cx: &mut TestAppContext) {
9051 init_test(cx, |_| {});
9052
9053 let editor = cx.add_window(|cx| {
9054 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9055 build_editor(buffer.clone(), cx)
9056 });
9057
9058 _ = editor.update(cx, |editor, cx| {
9059 struct Type1;
9060 struct Type2;
9061
9062 let buffer = editor.buffer.read(cx).snapshot(cx);
9063
9064 let anchor_range =
9065 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9066
9067 editor.highlight_background::<Type1>(
9068 &[
9069 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9070 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9071 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9072 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9073 ],
9074 |_| Hsla::red(),
9075 cx,
9076 );
9077 editor.highlight_background::<Type2>(
9078 &[
9079 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9080 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9081 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9082 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9083 ],
9084 |_| Hsla::green(),
9085 cx,
9086 );
9087
9088 let snapshot = editor.snapshot(cx);
9089 let mut highlighted_ranges = editor.background_highlights_in_range(
9090 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9091 &snapshot,
9092 cx.theme().colors(),
9093 );
9094 // Enforce a consistent ordering based on color without relying on the ordering of the
9095 // highlight's `TypeId` which is non-executor.
9096 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9097 assert_eq!(
9098 highlighted_ranges,
9099 &[
9100 (
9101 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9102 Hsla::red(),
9103 ),
9104 (
9105 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9106 Hsla::red(),
9107 ),
9108 (
9109 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9110 Hsla::green(),
9111 ),
9112 (
9113 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9114 Hsla::green(),
9115 ),
9116 ]
9117 );
9118 assert_eq!(
9119 editor.background_highlights_in_range(
9120 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9121 &snapshot,
9122 cx.theme().colors(),
9123 ),
9124 &[(
9125 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9126 Hsla::red(),
9127 )]
9128 );
9129 });
9130}
9131
9132#[gpui::test]
9133async fn test_following(cx: &mut gpui::TestAppContext) {
9134 init_test(cx, |_| {});
9135
9136 let fs = FakeFs::new(cx.executor());
9137 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9138
9139 let buffer = project.update(cx, |project, cx| {
9140 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9141 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9142 });
9143 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9144 let follower = cx.update(|cx| {
9145 cx.open_window(
9146 WindowOptions {
9147 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9148 gpui::Point::new(px(0.), px(0.)),
9149 gpui::Point::new(px(10.), px(80.)),
9150 ))),
9151 ..Default::default()
9152 },
9153 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9154 )
9155 .unwrap()
9156 });
9157
9158 let is_still_following = Rc::new(RefCell::new(true));
9159 let follower_edit_event_count = Rc::new(RefCell::new(0));
9160 let pending_update = Rc::new(RefCell::new(None));
9161 _ = follower.update(cx, {
9162 let update = pending_update.clone();
9163 let is_still_following = is_still_following.clone();
9164 let follower_edit_event_count = follower_edit_event_count.clone();
9165 |_, cx| {
9166 cx.subscribe(
9167 &leader.root_view(cx).unwrap(),
9168 move |_, leader, event, cx| {
9169 leader
9170 .read(cx)
9171 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9172 },
9173 )
9174 .detach();
9175
9176 cx.subscribe(
9177 &follower.root_view(cx).unwrap(),
9178 move |_, _, event: &EditorEvent, _cx| {
9179 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9180 *is_still_following.borrow_mut() = false;
9181 }
9182
9183 if let EditorEvent::BufferEdited = event {
9184 *follower_edit_event_count.borrow_mut() += 1;
9185 }
9186 },
9187 )
9188 .detach();
9189 }
9190 });
9191
9192 // Update the selections only
9193 _ = leader.update(cx, |leader, cx| {
9194 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9195 });
9196 follower
9197 .update(cx, |follower, cx| {
9198 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9199 })
9200 .unwrap()
9201 .await
9202 .unwrap();
9203 _ = follower.update(cx, |follower, cx| {
9204 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9205 });
9206 assert!(*is_still_following.borrow());
9207 assert_eq!(*follower_edit_event_count.borrow(), 0);
9208
9209 // Update the scroll position only
9210 _ = leader.update(cx, |leader, cx| {
9211 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9212 });
9213 follower
9214 .update(cx, |follower, cx| {
9215 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9216 })
9217 .unwrap()
9218 .await
9219 .unwrap();
9220 assert_eq!(
9221 follower
9222 .update(cx, |follower, cx| follower.scroll_position(cx))
9223 .unwrap(),
9224 gpui::Point::new(1.5, 3.5)
9225 );
9226 assert!(*is_still_following.borrow());
9227 assert_eq!(*follower_edit_event_count.borrow(), 0);
9228
9229 // Update the selections and scroll position. The follower's scroll position is updated
9230 // via autoscroll, not via the leader's exact scroll position.
9231 _ = leader.update(cx, |leader, cx| {
9232 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9233 leader.request_autoscroll(Autoscroll::newest(), cx);
9234 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9235 });
9236 follower
9237 .update(cx, |follower, cx| {
9238 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9239 })
9240 .unwrap()
9241 .await
9242 .unwrap();
9243 _ = follower.update(cx, |follower, cx| {
9244 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9245 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9246 });
9247 assert!(*is_still_following.borrow());
9248
9249 // Creating a pending selection that precedes another selection
9250 _ = leader.update(cx, |leader, cx| {
9251 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9252 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9253 });
9254 follower
9255 .update(cx, |follower, cx| {
9256 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9257 })
9258 .unwrap()
9259 .await
9260 .unwrap();
9261 _ = follower.update(cx, |follower, cx| {
9262 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9263 });
9264 assert!(*is_still_following.borrow());
9265
9266 // Extend the pending selection so that it surrounds another selection
9267 _ = leader.update(cx, |leader, cx| {
9268 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9269 });
9270 follower
9271 .update(cx, |follower, cx| {
9272 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9273 })
9274 .unwrap()
9275 .await
9276 .unwrap();
9277 _ = follower.update(cx, |follower, cx| {
9278 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9279 });
9280
9281 // Scrolling locally breaks the follow
9282 _ = follower.update(cx, |follower, cx| {
9283 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9284 follower.set_scroll_anchor(
9285 ScrollAnchor {
9286 anchor: top_anchor,
9287 offset: gpui::Point::new(0.0, 0.5),
9288 },
9289 cx,
9290 );
9291 });
9292 assert!(!(*is_still_following.borrow()));
9293}
9294
9295#[gpui::test]
9296async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9297 init_test(cx, |_| {});
9298
9299 let fs = FakeFs::new(cx.executor());
9300 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9301 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9302 let pane = workspace
9303 .update(cx, |workspace, _| workspace.active_pane().clone())
9304 .unwrap();
9305
9306 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9307
9308 let leader = pane.update(cx, |_, cx| {
9309 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9310 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9311 });
9312
9313 // Start following the editor when it has no excerpts.
9314 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9315 let follower_1 = cx
9316 .update_window(*workspace.deref(), |_, cx| {
9317 Editor::from_state_proto(
9318 workspace.root_view(cx).unwrap(),
9319 ViewId {
9320 creator: Default::default(),
9321 id: 0,
9322 },
9323 &mut state_message,
9324 cx,
9325 )
9326 })
9327 .unwrap()
9328 .unwrap()
9329 .await
9330 .unwrap();
9331
9332 let update_message = Rc::new(RefCell::new(None));
9333 follower_1.update(cx, {
9334 let update = update_message.clone();
9335 |_, cx| {
9336 cx.subscribe(&leader, move |_, leader, event, cx| {
9337 leader
9338 .read(cx)
9339 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9340 })
9341 .detach();
9342 }
9343 });
9344
9345 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9346 (
9347 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9348 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9349 )
9350 });
9351
9352 // Insert some excerpts.
9353 leader.update(cx, |leader, cx| {
9354 leader.buffer.update(cx, |multibuffer, cx| {
9355 let excerpt_ids = multibuffer.push_excerpts(
9356 buffer_1.clone(),
9357 [
9358 ExcerptRange {
9359 context: 1..6,
9360 primary: None,
9361 },
9362 ExcerptRange {
9363 context: 12..15,
9364 primary: None,
9365 },
9366 ExcerptRange {
9367 context: 0..3,
9368 primary: None,
9369 },
9370 ],
9371 cx,
9372 );
9373 multibuffer.insert_excerpts_after(
9374 excerpt_ids[0],
9375 buffer_2.clone(),
9376 [
9377 ExcerptRange {
9378 context: 8..12,
9379 primary: None,
9380 },
9381 ExcerptRange {
9382 context: 0..6,
9383 primary: None,
9384 },
9385 ],
9386 cx,
9387 );
9388 });
9389 });
9390
9391 // Apply the update of adding the excerpts.
9392 follower_1
9393 .update(cx, |follower, cx| {
9394 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9395 })
9396 .await
9397 .unwrap();
9398 assert_eq!(
9399 follower_1.update(cx, |editor, cx| editor.text(cx)),
9400 leader.update(cx, |editor, cx| editor.text(cx))
9401 );
9402 update_message.borrow_mut().take();
9403
9404 // Start following separately after it already has excerpts.
9405 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9406 let follower_2 = cx
9407 .update_window(*workspace.deref(), |_, cx| {
9408 Editor::from_state_proto(
9409 workspace.root_view(cx).unwrap().clone(),
9410 ViewId {
9411 creator: Default::default(),
9412 id: 0,
9413 },
9414 &mut state_message,
9415 cx,
9416 )
9417 })
9418 .unwrap()
9419 .unwrap()
9420 .await
9421 .unwrap();
9422 assert_eq!(
9423 follower_2.update(cx, |editor, cx| editor.text(cx)),
9424 leader.update(cx, |editor, cx| editor.text(cx))
9425 );
9426
9427 // Remove some excerpts.
9428 leader.update(cx, |leader, cx| {
9429 leader.buffer.update(cx, |multibuffer, cx| {
9430 let excerpt_ids = multibuffer.excerpt_ids();
9431 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9432 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9433 });
9434 });
9435
9436 // Apply the update of removing the excerpts.
9437 follower_1
9438 .update(cx, |follower, cx| {
9439 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9440 })
9441 .await
9442 .unwrap();
9443 follower_2
9444 .update(cx, |follower, cx| {
9445 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9446 })
9447 .await
9448 .unwrap();
9449 update_message.borrow_mut().take();
9450 assert_eq!(
9451 follower_1.update(cx, |editor, cx| editor.text(cx)),
9452 leader.update(cx, |editor, cx| editor.text(cx))
9453 );
9454}
9455
9456#[gpui::test]
9457async fn go_to_prev_overlapping_diagnostic(
9458 executor: BackgroundExecutor,
9459 cx: &mut gpui::TestAppContext,
9460) {
9461 init_test(cx, |_| {});
9462
9463 let mut cx = EditorTestContext::new(cx).await;
9464 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9465
9466 cx.set_state(indoc! {"
9467 ˇfn func(abc def: i32) -> u32 {
9468 }
9469 "});
9470
9471 cx.update(|cx| {
9472 project.update(cx, |project, cx| {
9473 project
9474 .update_diagnostics(
9475 LanguageServerId(0),
9476 lsp::PublishDiagnosticsParams {
9477 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9478 version: None,
9479 diagnostics: vec![
9480 lsp::Diagnostic {
9481 range: lsp::Range::new(
9482 lsp::Position::new(0, 11),
9483 lsp::Position::new(0, 12),
9484 ),
9485 severity: Some(lsp::DiagnosticSeverity::ERROR),
9486 ..Default::default()
9487 },
9488 lsp::Diagnostic {
9489 range: lsp::Range::new(
9490 lsp::Position::new(0, 12),
9491 lsp::Position::new(0, 15),
9492 ),
9493 severity: Some(lsp::DiagnosticSeverity::ERROR),
9494 ..Default::default()
9495 },
9496 lsp::Diagnostic {
9497 range: lsp::Range::new(
9498 lsp::Position::new(0, 25),
9499 lsp::Position::new(0, 28),
9500 ),
9501 severity: Some(lsp::DiagnosticSeverity::ERROR),
9502 ..Default::default()
9503 },
9504 ],
9505 },
9506 &[],
9507 cx,
9508 )
9509 .unwrap()
9510 });
9511 });
9512
9513 executor.run_until_parked();
9514
9515 cx.update_editor(|editor, cx| {
9516 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9517 });
9518
9519 cx.assert_editor_state(indoc! {"
9520 fn func(abc def: i32) -> ˇu32 {
9521 }
9522 "});
9523
9524 cx.update_editor(|editor, cx| {
9525 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9526 });
9527
9528 cx.assert_editor_state(indoc! {"
9529 fn func(abc ˇdef: i32) -> u32 {
9530 }
9531 "});
9532
9533 cx.update_editor(|editor, cx| {
9534 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9535 });
9536
9537 cx.assert_editor_state(indoc! {"
9538 fn func(abcˇ def: i32) -> u32 {
9539 }
9540 "});
9541
9542 cx.update_editor(|editor, cx| {
9543 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9544 });
9545
9546 cx.assert_editor_state(indoc! {"
9547 fn func(abc def: i32) -> ˇu32 {
9548 }
9549 "});
9550}
9551
9552#[gpui::test]
9553async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9554 init_test(cx, |_| {});
9555
9556 let mut cx = EditorTestContext::new(cx).await;
9557
9558 cx.set_state(indoc! {"
9559 fn func(abˇc def: i32) -> u32 {
9560 }
9561 "});
9562 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9563
9564 cx.update(|cx| {
9565 project.update(cx, |project, cx| {
9566 project.update_diagnostics(
9567 LanguageServerId(0),
9568 lsp::PublishDiagnosticsParams {
9569 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9570 version: None,
9571 diagnostics: vec![lsp::Diagnostic {
9572 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9573 severity: Some(lsp::DiagnosticSeverity::ERROR),
9574 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9575 ..Default::default()
9576 }],
9577 },
9578 &[],
9579 cx,
9580 )
9581 })
9582 }).unwrap();
9583 cx.run_until_parked();
9584 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9585 cx.run_until_parked();
9586 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9587}
9588
9589#[gpui::test]
9590async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9591 init_test(cx, |_| {});
9592
9593 let mut cx = EditorTestContext::new(cx).await;
9594
9595 let diff_base = r#"
9596 use some::mod;
9597
9598 const A: u32 = 42;
9599
9600 fn main() {
9601 println!("hello");
9602
9603 println!("world");
9604 }
9605 "#
9606 .unindent();
9607
9608 // Edits are modified, removed, modified, added
9609 cx.set_state(
9610 &r#"
9611 use some::modified;
9612
9613 ˇ
9614 fn main() {
9615 println!("hello there");
9616
9617 println!("around the");
9618 println!("world");
9619 }
9620 "#
9621 .unindent(),
9622 );
9623
9624 cx.set_diff_base(Some(&diff_base));
9625 executor.run_until_parked();
9626
9627 cx.update_editor(|editor, cx| {
9628 //Wrap around the bottom of the buffer
9629 for _ in 0..3 {
9630 editor.go_to_next_hunk(&GoToHunk, cx);
9631 }
9632 });
9633
9634 cx.assert_editor_state(
9635 &r#"
9636 ˇuse some::modified;
9637
9638
9639 fn main() {
9640 println!("hello there");
9641
9642 println!("around the");
9643 println!("world");
9644 }
9645 "#
9646 .unindent(),
9647 );
9648
9649 cx.update_editor(|editor, cx| {
9650 //Wrap around the top of the buffer
9651 for _ in 0..2 {
9652 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9653 }
9654 });
9655
9656 cx.assert_editor_state(
9657 &r#"
9658 use some::modified;
9659
9660
9661 fn main() {
9662 ˇ println!("hello there");
9663
9664 println!("around the");
9665 println!("world");
9666 }
9667 "#
9668 .unindent(),
9669 );
9670
9671 cx.update_editor(|editor, cx| {
9672 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9673 });
9674
9675 cx.assert_editor_state(
9676 &r#"
9677 use some::modified;
9678
9679 ˇ
9680 fn main() {
9681 println!("hello there");
9682
9683 println!("around the");
9684 println!("world");
9685 }
9686 "#
9687 .unindent(),
9688 );
9689
9690 cx.update_editor(|editor, cx| {
9691 for _ in 0..3 {
9692 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9693 }
9694 });
9695
9696 cx.assert_editor_state(
9697 &r#"
9698 use some::modified;
9699
9700
9701 fn main() {
9702 ˇ println!("hello there");
9703
9704 println!("around the");
9705 println!("world");
9706 }
9707 "#
9708 .unindent(),
9709 );
9710
9711 cx.update_editor(|editor, cx| {
9712 editor.fold(&Fold, cx);
9713
9714 //Make sure that the fold only gets one hunk
9715 for _ in 0..4 {
9716 editor.go_to_next_hunk(&GoToHunk, cx);
9717 }
9718 });
9719
9720 cx.assert_editor_state(
9721 &r#"
9722 ˇuse some::modified;
9723
9724
9725 fn main() {
9726 println!("hello there");
9727
9728 println!("around the");
9729 println!("world");
9730 }
9731 "#
9732 .unindent(),
9733 );
9734}
9735
9736#[test]
9737fn test_split_words() {
9738 fn split(text: &str) -> Vec<&str> {
9739 split_words(text).collect()
9740 }
9741
9742 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9743 assert_eq!(split("hello_world"), &["hello_", "world"]);
9744 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9745 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9746 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9747 assert_eq!(split("helloworld"), &["helloworld"]);
9748
9749 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9750}
9751
9752#[gpui::test]
9753async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9754 init_test(cx, |_| {});
9755
9756 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9757 let mut assert = |before, after| {
9758 let _state_context = cx.set_state(before);
9759 cx.update_editor(|editor, cx| {
9760 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9761 });
9762 cx.assert_editor_state(after);
9763 };
9764
9765 // Outside bracket jumps to outside of matching bracket
9766 assert("console.logˇ(var);", "console.log(var)ˇ;");
9767 assert("console.log(var)ˇ;", "console.logˇ(var);");
9768
9769 // Inside bracket jumps to inside of matching bracket
9770 assert("console.log(ˇvar);", "console.log(varˇ);");
9771 assert("console.log(varˇ);", "console.log(ˇvar);");
9772
9773 // When outside a bracket and inside, favor jumping to the inside bracket
9774 assert(
9775 "console.log('foo', [1, 2, 3]ˇ);",
9776 "console.log(ˇ'foo', [1, 2, 3]);",
9777 );
9778 assert(
9779 "console.log(ˇ'foo', [1, 2, 3]);",
9780 "console.log('foo', [1, 2, 3]ˇ);",
9781 );
9782
9783 // Bias forward if two options are equally likely
9784 assert(
9785 "let result = curried_fun()ˇ();",
9786 "let result = curried_fun()()ˇ;",
9787 );
9788
9789 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9790 assert(
9791 indoc! {"
9792 function test() {
9793 console.log('test')ˇ
9794 }"},
9795 indoc! {"
9796 function test() {
9797 console.logˇ('test')
9798 }"},
9799 );
9800}
9801
9802#[gpui::test]
9803async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9804 init_test(cx, |_| {});
9805
9806 let fs = FakeFs::new(cx.executor());
9807 fs.insert_tree(
9808 "/a",
9809 json!({
9810 "main.rs": "fn main() { let a = 5; }",
9811 "other.rs": "// Test file",
9812 }),
9813 )
9814 .await;
9815 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9816
9817 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9818 language_registry.add(Arc::new(Language::new(
9819 LanguageConfig {
9820 name: "Rust".into(),
9821 matcher: LanguageMatcher {
9822 path_suffixes: vec!["rs".to_string()],
9823 ..Default::default()
9824 },
9825 brackets: BracketPairConfig {
9826 pairs: vec![BracketPair {
9827 start: "{".to_string(),
9828 end: "}".to_string(),
9829 close: true,
9830 surround: true,
9831 newline: true,
9832 }],
9833 disabled_scopes_by_bracket_ix: Vec::new(),
9834 },
9835 ..Default::default()
9836 },
9837 Some(tree_sitter_rust::LANGUAGE.into()),
9838 )));
9839 let mut fake_servers = language_registry.register_fake_lsp(
9840 "Rust",
9841 FakeLspAdapter {
9842 capabilities: lsp::ServerCapabilities {
9843 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9844 first_trigger_character: "{".to_string(),
9845 more_trigger_character: None,
9846 }),
9847 ..Default::default()
9848 },
9849 ..Default::default()
9850 },
9851 );
9852
9853 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9854
9855 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9856
9857 let worktree_id = workspace
9858 .update(cx, |workspace, cx| {
9859 workspace.project().update(cx, |project, cx| {
9860 project.worktrees(cx).next().unwrap().read(cx).id()
9861 })
9862 })
9863 .unwrap();
9864
9865 let buffer = project
9866 .update(cx, |project, cx| {
9867 project.open_local_buffer("/a/main.rs", cx)
9868 })
9869 .await
9870 .unwrap();
9871 cx.executor().run_until_parked();
9872 cx.executor().start_waiting();
9873 let fake_server = fake_servers.next().await.unwrap();
9874 let editor_handle = workspace
9875 .update(cx, |workspace, cx| {
9876 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9877 })
9878 .unwrap()
9879 .await
9880 .unwrap()
9881 .downcast::<Editor>()
9882 .unwrap();
9883
9884 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9885 assert_eq!(
9886 params.text_document_position.text_document.uri,
9887 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9888 );
9889 assert_eq!(
9890 params.text_document_position.position,
9891 lsp::Position::new(0, 21),
9892 );
9893
9894 Ok(Some(vec![lsp::TextEdit {
9895 new_text: "]".to_string(),
9896 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9897 }]))
9898 });
9899
9900 editor_handle.update(cx, |editor, cx| {
9901 editor.focus(cx);
9902 editor.change_selections(None, cx, |s| {
9903 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9904 });
9905 editor.handle_input("{", cx);
9906 });
9907
9908 cx.executor().run_until_parked();
9909
9910 buffer.update(cx, |buffer, _| {
9911 assert_eq!(
9912 buffer.text(),
9913 "fn main() { let a = {5}; }",
9914 "No extra braces from on type formatting should appear in the buffer"
9915 )
9916 });
9917}
9918
9919#[gpui::test]
9920async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9921 init_test(cx, |_| {});
9922
9923 let fs = FakeFs::new(cx.executor());
9924 fs.insert_tree(
9925 "/a",
9926 json!({
9927 "main.rs": "fn main() { let a = 5; }",
9928 "other.rs": "// Test file",
9929 }),
9930 )
9931 .await;
9932
9933 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9934
9935 let server_restarts = Arc::new(AtomicUsize::new(0));
9936 let closure_restarts = Arc::clone(&server_restarts);
9937 let language_server_name = "test language server";
9938 let language_name: LanguageName = "Rust".into();
9939
9940 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9941 language_registry.add(Arc::new(Language::new(
9942 LanguageConfig {
9943 name: language_name.clone(),
9944 matcher: LanguageMatcher {
9945 path_suffixes: vec!["rs".to_string()],
9946 ..Default::default()
9947 },
9948 ..Default::default()
9949 },
9950 Some(tree_sitter_rust::LANGUAGE.into()),
9951 )));
9952 let mut fake_servers = language_registry.register_fake_lsp(
9953 "Rust",
9954 FakeLspAdapter {
9955 name: language_server_name,
9956 initialization_options: Some(json!({
9957 "testOptionValue": true
9958 })),
9959 initializer: Some(Box::new(move |fake_server| {
9960 let task_restarts = Arc::clone(&closure_restarts);
9961 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9962 task_restarts.fetch_add(1, atomic::Ordering::Release);
9963 futures::future::ready(Ok(()))
9964 });
9965 })),
9966 ..Default::default()
9967 },
9968 );
9969
9970 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9971 let _buffer = project
9972 .update(cx, |project, cx| {
9973 project.open_local_buffer("/a/main.rs", cx)
9974 })
9975 .await
9976 .unwrap();
9977 let _fake_server = fake_servers.next().await.unwrap();
9978 update_test_language_settings(cx, |language_settings| {
9979 language_settings.languages.insert(
9980 language_name.clone(),
9981 LanguageSettingsContent {
9982 tab_size: NonZeroU32::new(8),
9983 ..Default::default()
9984 },
9985 );
9986 });
9987 cx.executor().run_until_parked();
9988 assert_eq!(
9989 server_restarts.load(atomic::Ordering::Acquire),
9990 0,
9991 "Should not restart LSP server on an unrelated change"
9992 );
9993
9994 update_test_project_settings(cx, |project_settings| {
9995 project_settings.lsp.insert(
9996 "Some other server name".into(),
9997 LspSettings {
9998 binary: None,
9999 settings: None,
10000 initialization_options: Some(json!({
10001 "some other init value": false
10002 })),
10003 },
10004 );
10005 });
10006 cx.executor().run_until_parked();
10007 assert_eq!(
10008 server_restarts.load(atomic::Ordering::Acquire),
10009 0,
10010 "Should not restart LSP server on an unrelated LSP settings change"
10011 );
10012
10013 update_test_project_settings(cx, |project_settings| {
10014 project_settings.lsp.insert(
10015 language_server_name.into(),
10016 LspSettings {
10017 binary: None,
10018 settings: None,
10019 initialization_options: Some(json!({
10020 "anotherInitValue": false
10021 })),
10022 },
10023 );
10024 });
10025 cx.executor().run_until_parked();
10026 assert_eq!(
10027 server_restarts.load(atomic::Ordering::Acquire),
10028 1,
10029 "Should restart LSP server on a related LSP settings change"
10030 );
10031
10032 update_test_project_settings(cx, |project_settings| {
10033 project_settings.lsp.insert(
10034 language_server_name.into(),
10035 LspSettings {
10036 binary: None,
10037 settings: None,
10038 initialization_options: Some(json!({
10039 "anotherInitValue": false
10040 })),
10041 },
10042 );
10043 });
10044 cx.executor().run_until_parked();
10045 assert_eq!(
10046 server_restarts.load(atomic::Ordering::Acquire),
10047 1,
10048 "Should not restart LSP server on a related LSP settings change that is the same"
10049 );
10050
10051 update_test_project_settings(cx, |project_settings| {
10052 project_settings.lsp.insert(
10053 language_server_name.into(),
10054 LspSettings {
10055 binary: None,
10056 settings: None,
10057 initialization_options: None,
10058 },
10059 );
10060 });
10061 cx.executor().run_until_parked();
10062 assert_eq!(
10063 server_restarts.load(atomic::Ordering::Acquire),
10064 2,
10065 "Should restart LSP server on another related LSP settings change"
10066 );
10067}
10068
10069#[gpui::test]
10070async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10071 init_test(cx, |_| {});
10072
10073 let mut cx = EditorLspTestContext::new_rust(
10074 lsp::ServerCapabilities {
10075 completion_provider: Some(lsp::CompletionOptions {
10076 trigger_characters: Some(vec![".".to_string()]),
10077 resolve_provider: Some(true),
10078 ..Default::default()
10079 }),
10080 ..Default::default()
10081 },
10082 cx,
10083 )
10084 .await;
10085
10086 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10087 cx.simulate_keystroke(".");
10088 let completion_item = lsp::CompletionItem {
10089 label: "some".into(),
10090 kind: Some(lsp::CompletionItemKind::SNIPPET),
10091 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10092 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10093 kind: lsp::MarkupKind::Markdown,
10094 value: "```rust\nSome(2)\n```".to_string(),
10095 })),
10096 deprecated: Some(false),
10097 sort_text: Some("fffffff2".to_string()),
10098 filter_text: Some("some".to_string()),
10099 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10100 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10101 range: lsp::Range {
10102 start: lsp::Position {
10103 line: 0,
10104 character: 22,
10105 },
10106 end: lsp::Position {
10107 line: 0,
10108 character: 22,
10109 },
10110 },
10111 new_text: "Some(2)".to_string(),
10112 })),
10113 additional_text_edits: Some(vec![lsp::TextEdit {
10114 range: lsp::Range {
10115 start: lsp::Position {
10116 line: 0,
10117 character: 20,
10118 },
10119 end: lsp::Position {
10120 line: 0,
10121 character: 22,
10122 },
10123 },
10124 new_text: "".to_string(),
10125 }]),
10126 ..Default::default()
10127 };
10128
10129 let closure_completion_item = completion_item.clone();
10130 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10131 let task_completion_item = closure_completion_item.clone();
10132 async move {
10133 Ok(Some(lsp::CompletionResponse::Array(vec![
10134 task_completion_item,
10135 ])))
10136 }
10137 });
10138
10139 request.next().await;
10140
10141 cx.condition(|editor, _| editor.context_menu_visible())
10142 .await;
10143 let apply_additional_edits = cx.update_editor(|editor, cx| {
10144 editor
10145 .confirm_completion(&ConfirmCompletion::default(), cx)
10146 .unwrap()
10147 });
10148 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10149
10150 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10151 let task_completion_item = completion_item.clone();
10152 async move { Ok(task_completion_item) }
10153 })
10154 .next()
10155 .await
10156 .unwrap();
10157 apply_additional_edits.await.unwrap();
10158 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10159}
10160
10161#[gpui::test]
10162async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10163 init_test(cx, |_| {});
10164
10165 let mut cx = EditorLspTestContext::new(
10166 Language::new(
10167 LanguageConfig {
10168 matcher: LanguageMatcher {
10169 path_suffixes: vec!["jsx".into()],
10170 ..Default::default()
10171 },
10172 overrides: [(
10173 "element".into(),
10174 LanguageConfigOverride {
10175 word_characters: Override::Set(['-'].into_iter().collect()),
10176 ..Default::default()
10177 },
10178 )]
10179 .into_iter()
10180 .collect(),
10181 ..Default::default()
10182 },
10183 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10184 )
10185 .with_override_query("(jsx_self_closing_element) @element")
10186 .unwrap(),
10187 lsp::ServerCapabilities {
10188 completion_provider: Some(lsp::CompletionOptions {
10189 trigger_characters: Some(vec![":".to_string()]),
10190 ..Default::default()
10191 }),
10192 ..Default::default()
10193 },
10194 cx,
10195 )
10196 .await;
10197
10198 cx.lsp
10199 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10200 Ok(Some(lsp::CompletionResponse::Array(vec![
10201 lsp::CompletionItem {
10202 label: "bg-blue".into(),
10203 ..Default::default()
10204 },
10205 lsp::CompletionItem {
10206 label: "bg-red".into(),
10207 ..Default::default()
10208 },
10209 lsp::CompletionItem {
10210 label: "bg-yellow".into(),
10211 ..Default::default()
10212 },
10213 ])))
10214 });
10215
10216 cx.set_state(r#"<p class="bgˇ" />"#);
10217
10218 // Trigger completion when typing a dash, because the dash is an extra
10219 // word character in the 'element' scope, which contains the cursor.
10220 cx.simulate_keystroke("-");
10221 cx.executor().run_until_parked();
10222 cx.update_editor(|editor, _| {
10223 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10224 assert_eq!(
10225 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10226 &["bg-red", "bg-blue", "bg-yellow"]
10227 );
10228 } else {
10229 panic!("expected completion menu to be open");
10230 }
10231 });
10232
10233 cx.simulate_keystroke("l");
10234 cx.executor().run_until_parked();
10235 cx.update_editor(|editor, _| {
10236 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10237 assert_eq!(
10238 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10239 &["bg-blue", "bg-yellow"]
10240 );
10241 } else {
10242 panic!("expected completion menu to be open");
10243 }
10244 });
10245
10246 // When filtering completions, consider the character after the '-' to
10247 // be the start of a subword.
10248 cx.set_state(r#"<p class="yelˇ" />"#);
10249 cx.simulate_keystroke("l");
10250 cx.executor().run_until_parked();
10251 cx.update_editor(|editor, _| {
10252 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10253 assert_eq!(
10254 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10255 &["bg-yellow"]
10256 );
10257 } else {
10258 panic!("expected completion menu to be open");
10259 }
10260 });
10261}
10262
10263#[gpui::test]
10264async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10265 init_test(cx, |settings| {
10266 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10267 FormatterList(vec![Formatter::Prettier].into()),
10268 ))
10269 });
10270
10271 let fs = FakeFs::new(cx.executor());
10272 fs.insert_file("/file.ts", Default::default()).await;
10273
10274 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10275 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10276
10277 language_registry.add(Arc::new(Language::new(
10278 LanguageConfig {
10279 name: "TypeScript".into(),
10280 matcher: LanguageMatcher {
10281 path_suffixes: vec!["ts".to_string()],
10282 ..Default::default()
10283 },
10284 ..Default::default()
10285 },
10286 Some(tree_sitter_rust::LANGUAGE.into()),
10287 )));
10288 update_test_language_settings(cx, |settings| {
10289 settings.defaults.prettier = Some(PrettierSettings {
10290 allowed: true,
10291 ..PrettierSettings::default()
10292 });
10293 });
10294
10295 let test_plugin = "test_plugin";
10296 let _ = language_registry.register_fake_lsp(
10297 "TypeScript",
10298 FakeLspAdapter {
10299 prettier_plugins: vec![test_plugin],
10300 ..Default::default()
10301 },
10302 );
10303
10304 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10305 let buffer = project
10306 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10307 .await
10308 .unwrap();
10309
10310 let buffer_text = "one\ntwo\nthree\n";
10311 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10312 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10313 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10314
10315 editor
10316 .update(cx, |editor, cx| {
10317 editor.perform_format(
10318 project.clone(),
10319 FormatTrigger::Manual,
10320 FormatTarget::Buffer,
10321 cx,
10322 )
10323 })
10324 .unwrap()
10325 .await;
10326 assert_eq!(
10327 editor.update(cx, |editor, cx| editor.text(cx)),
10328 buffer_text.to_string() + prettier_format_suffix,
10329 "Test prettier formatting was not applied to the original buffer text",
10330 );
10331
10332 update_test_language_settings(cx, |settings| {
10333 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10334 });
10335 let format = editor.update(cx, |editor, cx| {
10336 editor.perform_format(
10337 project.clone(),
10338 FormatTrigger::Manual,
10339 FormatTarget::Buffer,
10340 cx,
10341 )
10342 });
10343 format.await.unwrap();
10344 assert_eq!(
10345 editor.update(cx, |editor, cx| editor.text(cx)),
10346 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10347 "Autoformatting (via test prettier) was not applied to the original buffer text",
10348 );
10349}
10350
10351#[gpui::test]
10352async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10353 init_test(cx, |_| {});
10354 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10355 let base_text = indoc! {r#"struct Row;
10356struct Row1;
10357struct Row2;
10358
10359struct Row4;
10360struct Row5;
10361struct Row6;
10362
10363struct Row8;
10364struct Row9;
10365struct Row10;"#};
10366
10367 // When addition hunks are not adjacent to carets, no hunk revert is performed
10368 assert_hunk_revert(
10369 indoc! {r#"struct Row;
10370 struct Row1;
10371 struct Row1.1;
10372 struct Row1.2;
10373 struct Row2;ˇ
10374
10375 struct Row4;
10376 struct Row5;
10377 struct Row6;
10378
10379 struct Row8;
10380 ˇstruct Row9;
10381 struct Row9.1;
10382 struct Row9.2;
10383 struct Row9.3;
10384 struct Row10;"#},
10385 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10386 indoc! {r#"struct Row;
10387 struct Row1;
10388 struct Row1.1;
10389 struct Row1.2;
10390 struct Row2;ˇ
10391
10392 struct Row4;
10393 struct Row5;
10394 struct Row6;
10395
10396 struct Row8;
10397 ˇstruct Row9;
10398 struct Row9.1;
10399 struct Row9.2;
10400 struct Row9.3;
10401 struct Row10;"#},
10402 base_text,
10403 &mut cx,
10404 );
10405 // Same for selections
10406 assert_hunk_revert(
10407 indoc! {r#"struct Row;
10408 struct Row1;
10409 struct Row2;
10410 struct Row2.1;
10411 struct Row2.2;
10412 «ˇ
10413 struct Row4;
10414 struct» Row5;
10415 «struct Row6;
10416 ˇ»
10417 struct Row9.1;
10418 struct Row9.2;
10419 struct Row9.3;
10420 struct Row8;
10421 struct Row9;
10422 struct Row10;"#},
10423 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10424 indoc! {r#"struct Row;
10425 struct Row1;
10426 struct Row2;
10427 struct Row2.1;
10428 struct Row2.2;
10429 «ˇ
10430 struct Row4;
10431 struct» Row5;
10432 «struct Row6;
10433 ˇ»
10434 struct Row9.1;
10435 struct Row9.2;
10436 struct Row9.3;
10437 struct Row8;
10438 struct Row9;
10439 struct Row10;"#},
10440 base_text,
10441 &mut cx,
10442 );
10443
10444 // When carets and selections intersect the addition hunks, those are reverted.
10445 // Adjacent carets got merged.
10446 assert_hunk_revert(
10447 indoc! {r#"struct Row;
10448 ˇ// something on the top
10449 struct Row1;
10450 struct Row2;
10451 struct Roˇw3.1;
10452 struct Row2.2;
10453 struct Row2.3;ˇ
10454
10455 struct Row4;
10456 struct ˇRow5.1;
10457 struct Row5.2;
10458 struct «Rowˇ»5.3;
10459 struct Row5;
10460 struct Row6;
10461 ˇ
10462 struct Row9.1;
10463 struct «Rowˇ»9.2;
10464 struct «ˇRow»9.3;
10465 struct Row8;
10466 struct Row9;
10467 «ˇ// something on bottom»
10468 struct Row10;"#},
10469 vec![
10470 DiffHunkStatus::Added,
10471 DiffHunkStatus::Added,
10472 DiffHunkStatus::Added,
10473 DiffHunkStatus::Added,
10474 DiffHunkStatus::Added,
10475 ],
10476 indoc! {r#"struct Row;
10477 ˇstruct Row1;
10478 struct Row2;
10479 ˇ
10480 struct Row4;
10481 ˇstruct Row5;
10482 struct Row6;
10483 ˇ
10484 ˇstruct Row8;
10485 struct Row9;
10486 ˇstruct Row10;"#},
10487 base_text,
10488 &mut cx,
10489 );
10490}
10491
10492#[gpui::test]
10493async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10494 init_test(cx, |_| {});
10495 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10496 let base_text = indoc! {r#"struct Row;
10497struct Row1;
10498struct Row2;
10499
10500struct Row4;
10501struct Row5;
10502struct Row6;
10503
10504struct Row8;
10505struct Row9;
10506struct Row10;"#};
10507
10508 // Modification hunks behave the same as the addition ones.
10509 assert_hunk_revert(
10510 indoc! {r#"struct Row;
10511 struct Row1;
10512 struct Row33;
10513 ˇ
10514 struct Row4;
10515 struct Row5;
10516 struct Row6;
10517 ˇ
10518 struct Row99;
10519 struct Row9;
10520 struct Row10;"#},
10521 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
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 base_text,
10534 &mut cx,
10535 );
10536 assert_hunk_revert(
10537 indoc! {r#"struct Row;
10538 struct Row1;
10539 struct Row33;
10540 «ˇ
10541 struct Row4;
10542 struct» Row5;
10543 «struct Row6;
10544 ˇ»
10545 struct Row99;
10546 struct Row9;
10547 struct Row10;"#},
10548 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10549 indoc! {r#"struct Row;
10550 struct Row1;
10551 struct Row33;
10552 «ˇ
10553 struct Row4;
10554 struct» Row5;
10555 «struct Row6;
10556 ˇ»
10557 struct Row99;
10558 struct Row9;
10559 struct Row10;"#},
10560 base_text,
10561 &mut cx,
10562 );
10563
10564 assert_hunk_revert(
10565 indoc! {r#"ˇstruct Row1.1;
10566 struct Row1;
10567 «ˇstr»uct Row22;
10568
10569 struct ˇRow44;
10570 struct Row5;
10571 struct «Rˇ»ow66;ˇ
10572
10573 «struˇ»ct Row88;
10574 struct Row9;
10575 struct Row1011;ˇ"#},
10576 vec![
10577 DiffHunkStatus::Modified,
10578 DiffHunkStatus::Modified,
10579 DiffHunkStatus::Modified,
10580 DiffHunkStatus::Modified,
10581 DiffHunkStatus::Modified,
10582 DiffHunkStatus::Modified,
10583 ],
10584 indoc! {r#"struct Row;
10585 ˇstruct Row1;
10586 struct Row2;
10587 ˇ
10588 struct Row4;
10589 ˇstruct Row5;
10590 struct Row6;
10591 ˇ
10592 struct Row8;
10593 ˇstruct Row9;
10594 struct Row10;ˇ"#},
10595 base_text,
10596 &mut cx,
10597 );
10598}
10599
10600#[gpui::test]
10601async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10602 init_test(cx, |_| {});
10603 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10604 let base_text = indoc! {r#"struct Row;
10605struct Row1;
10606struct Row2;
10607
10608struct Row4;
10609struct Row5;
10610struct Row6;
10611
10612struct Row8;
10613struct Row9;
10614struct Row10;"#};
10615
10616 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10617 assert_hunk_revert(
10618 indoc! {r#"struct Row;
10619 struct Row2;
10620
10621 ˇstruct Row4;
10622 struct Row5;
10623 struct Row6;
10624 ˇ
10625 struct Row8;
10626 struct Row10;"#},
10627 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10628 indoc! {r#"struct Row;
10629 struct Row2;
10630
10631 ˇstruct Row4;
10632 struct Row5;
10633 struct Row6;
10634 ˇ
10635 struct Row8;
10636 struct Row10;"#},
10637 base_text,
10638 &mut cx,
10639 );
10640 assert_hunk_revert(
10641 indoc! {r#"struct Row;
10642 struct Row2;
10643
10644 «ˇstruct Row4;
10645 struct» Row5;
10646 «struct Row6;
10647 ˇ»
10648 struct Row8;
10649 struct Row10;"#},
10650 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
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 base_text,
10661 &mut cx,
10662 );
10663
10664 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10665 assert_hunk_revert(
10666 indoc! {r#"struct Row;
10667 ˇstruct Row2;
10668
10669 struct Row4;
10670 struct Row5;
10671 struct Row6;
10672
10673 struct Row8;ˇ
10674 struct Row10;"#},
10675 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10676 indoc! {r#"struct Row;
10677 struct Row1;
10678 ˇstruct Row2;
10679
10680 struct Row4;
10681 struct Row5;
10682 struct Row6;
10683
10684 struct Row8;ˇ
10685 struct Row9;
10686 struct Row10;"#},
10687 base_text,
10688 &mut cx,
10689 );
10690 assert_hunk_revert(
10691 indoc! {r#"struct Row;
10692 struct Row2«ˇ;
10693 struct Row4;
10694 struct» Row5;
10695 «struct Row6;
10696
10697 struct Row8;ˇ»
10698 struct Row10;"#},
10699 vec![
10700 DiffHunkStatus::Removed,
10701 DiffHunkStatus::Removed,
10702 DiffHunkStatus::Removed,
10703 ],
10704 indoc! {r#"struct Row;
10705 struct Row1;
10706 struct Row2«ˇ;
10707
10708 struct Row4;
10709 struct» Row5;
10710 «struct Row6;
10711
10712 struct Row8;ˇ»
10713 struct Row9;
10714 struct Row10;"#},
10715 base_text,
10716 &mut cx,
10717 );
10718}
10719
10720#[gpui::test]
10721async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10722 init_test(cx, |_| {});
10723
10724 let cols = 4;
10725 let rows = 10;
10726 let sample_text_1 = sample_text(rows, cols, 'a');
10727 assert_eq!(
10728 sample_text_1,
10729 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10730 );
10731 let sample_text_2 = sample_text(rows, cols, 'l');
10732 assert_eq!(
10733 sample_text_2,
10734 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10735 );
10736 let sample_text_3 = sample_text(rows, cols, 'v');
10737 assert_eq!(
10738 sample_text_3,
10739 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10740 );
10741
10742 fn diff_every_buffer_row(
10743 buffer: &Model<Buffer>,
10744 sample_text: String,
10745 cols: usize,
10746 cx: &mut gpui::TestAppContext,
10747 ) {
10748 // revert first character in each row, creating one large diff hunk per buffer
10749 let is_first_char = |offset: usize| offset % cols == 0;
10750 buffer.update(cx, |buffer, cx| {
10751 buffer.set_text(
10752 sample_text
10753 .chars()
10754 .enumerate()
10755 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10756 .collect::<String>(),
10757 cx,
10758 );
10759 buffer.set_diff_base(Some(sample_text), cx);
10760 });
10761 cx.executor().run_until_parked();
10762 }
10763
10764 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10765 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10766
10767 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10768 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10769
10770 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10771 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10772
10773 let multibuffer = cx.new_model(|cx| {
10774 let mut multibuffer = MultiBuffer::new(ReadWrite);
10775 multibuffer.push_excerpts(
10776 buffer_1.clone(),
10777 [
10778 ExcerptRange {
10779 context: Point::new(0, 0)..Point::new(3, 0),
10780 primary: None,
10781 },
10782 ExcerptRange {
10783 context: Point::new(5, 0)..Point::new(7, 0),
10784 primary: None,
10785 },
10786 ExcerptRange {
10787 context: Point::new(9, 0)..Point::new(10, 4),
10788 primary: None,
10789 },
10790 ],
10791 cx,
10792 );
10793 multibuffer.push_excerpts(
10794 buffer_2.clone(),
10795 [
10796 ExcerptRange {
10797 context: Point::new(0, 0)..Point::new(3, 0),
10798 primary: None,
10799 },
10800 ExcerptRange {
10801 context: Point::new(5, 0)..Point::new(7, 0),
10802 primary: None,
10803 },
10804 ExcerptRange {
10805 context: Point::new(9, 0)..Point::new(10, 4),
10806 primary: None,
10807 },
10808 ],
10809 cx,
10810 );
10811 multibuffer.push_excerpts(
10812 buffer_3.clone(),
10813 [
10814 ExcerptRange {
10815 context: Point::new(0, 0)..Point::new(3, 0),
10816 primary: None,
10817 },
10818 ExcerptRange {
10819 context: Point::new(5, 0)..Point::new(7, 0),
10820 primary: None,
10821 },
10822 ExcerptRange {
10823 context: Point::new(9, 0)..Point::new(10, 4),
10824 primary: None,
10825 },
10826 ],
10827 cx,
10828 );
10829 multibuffer
10830 });
10831
10832 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10833 editor.update(cx, |editor, cx| {
10834 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");
10835 editor.select_all(&SelectAll, cx);
10836 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10837 });
10838 cx.executor().run_until_parked();
10839 // When all ranges are selected, all buffer hunks are reverted.
10840 editor.update(cx, |editor, cx| {
10841 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");
10842 });
10843 buffer_1.update(cx, |buffer, _| {
10844 assert_eq!(buffer.text(), sample_text_1);
10845 });
10846 buffer_2.update(cx, |buffer, _| {
10847 assert_eq!(buffer.text(), sample_text_2);
10848 });
10849 buffer_3.update(cx, |buffer, _| {
10850 assert_eq!(buffer.text(), sample_text_3);
10851 });
10852
10853 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10854 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10855 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10856 editor.update(cx, |editor, cx| {
10857 editor.change_selections(None, cx, |s| {
10858 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10859 });
10860 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10861 });
10862 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10863 // but not affect buffer_2 and its related excerpts.
10864 editor.update(cx, |editor, cx| {
10865 assert_eq!(
10866 editor.text(cx),
10867 "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"
10868 );
10869 });
10870 buffer_1.update(cx, |buffer, _| {
10871 assert_eq!(buffer.text(), sample_text_1);
10872 });
10873 buffer_2.update(cx, |buffer, _| {
10874 assert_eq!(
10875 buffer.text(),
10876 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10877 );
10878 });
10879 buffer_3.update(cx, |buffer, _| {
10880 assert_eq!(
10881 buffer.text(),
10882 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10883 );
10884 });
10885}
10886
10887#[gpui::test]
10888async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10889 init_test(cx, |_| {});
10890
10891 let cols = 4;
10892 let rows = 10;
10893 let sample_text_1 = sample_text(rows, cols, 'a');
10894 assert_eq!(
10895 sample_text_1,
10896 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10897 );
10898 let sample_text_2 = sample_text(rows, cols, 'l');
10899 assert_eq!(
10900 sample_text_2,
10901 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10902 );
10903 let sample_text_3 = sample_text(rows, cols, 'v');
10904 assert_eq!(
10905 sample_text_3,
10906 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10907 );
10908
10909 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10910 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10911 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10912
10913 let multi_buffer = cx.new_model(|cx| {
10914 let mut multibuffer = MultiBuffer::new(ReadWrite);
10915 multibuffer.push_excerpts(
10916 buffer_1.clone(),
10917 [
10918 ExcerptRange {
10919 context: Point::new(0, 0)..Point::new(3, 0),
10920 primary: None,
10921 },
10922 ExcerptRange {
10923 context: Point::new(5, 0)..Point::new(7, 0),
10924 primary: None,
10925 },
10926 ExcerptRange {
10927 context: Point::new(9, 0)..Point::new(10, 4),
10928 primary: None,
10929 },
10930 ],
10931 cx,
10932 );
10933 multibuffer.push_excerpts(
10934 buffer_2.clone(),
10935 [
10936 ExcerptRange {
10937 context: Point::new(0, 0)..Point::new(3, 0),
10938 primary: None,
10939 },
10940 ExcerptRange {
10941 context: Point::new(5, 0)..Point::new(7, 0),
10942 primary: None,
10943 },
10944 ExcerptRange {
10945 context: Point::new(9, 0)..Point::new(10, 4),
10946 primary: None,
10947 },
10948 ],
10949 cx,
10950 );
10951 multibuffer.push_excerpts(
10952 buffer_3.clone(),
10953 [
10954 ExcerptRange {
10955 context: Point::new(0, 0)..Point::new(3, 0),
10956 primary: None,
10957 },
10958 ExcerptRange {
10959 context: Point::new(5, 0)..Point::new(7, 0),
10960 primary: None,
10961 },
10962 ExcerptRange {
10963 context: Point::new(9, 0)..Point::new(10, 4),
10964 primary: None,
10965 },
10966 ],
10967 cx,
10968 );
10969 multibuffer
10970 });
10971
10972 let fs = FakeFs::new(cx.executor());
10973 fs.insert_tree(
10974 "/a",
10975 json!({
10976 "main.rs": sample_text_1,
10977 "other.rs": sample_text_2,
10978 "lib.rs": sample_text_3,
10979 }),
10980 )
10981 .await;
10982 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10983 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10984 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10985 let multi_buffer_editor = cx.new_view(|cx| {
10986 Editor::new(
10987 EditorMode::Full,
10988 multi_buffer,
10989 Some(project.clone()),
10990 true,
10991 cx,
10992 )
10993 });
10994 let multibuffer_item_id = workspace
10995 .update(cx, |workspace, cx| {
10996 assert!(
10997 workspace.active_item(cx).is_none(),
10998 "active item should be None before the first item is added"
10999 );
11000 workspace.add_item_to_active_pane(
11001 Box::new(multi_buffer_editor.clone()),
11002 None,
11003 true,
11004 cx,
11005 );
11006 let active_item = workspace
11007 .active_item(cx)
11008 .expect("should have an active item after adding the multi buffer");
11009 assert!(
11010 !active_item.is_singleton(cx),
11011 "A multi buffer was expected to active after adding"
11012 );
11013 active_item.item_id()
11014 })
11015 .unwrap();
11016 cx.executor().run_until_parked();
11017
11018 multi_buffer_editor.update(cx, |editor, cx| {
11019 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11020 editor.open_excerpts(&OpenExcerpts, cx);
11021 });
11022 cx.executor().run_until_parked();
11023 let first_item_id = workspace
11024 .update(cx, |workspace, cx| {
11025 let active_item = workspace
11026 .active_item(cx)
11027 .expect("should have an active item after navigating into the 1st buffer");
11028 let first_item_id = active_item.item_id();
11029 assert_ne!(
11030 first_item_id, multibuffer_item_id,
11031 "Should navigate into the 1st buffer and activate it"
11032 );
11033 assert!(
11034 active_item.is_singleton(cx),
11035 "New active item should be a singleton buffer"
11036 );
11037 assert_eq!(
11038 active_item
11039 .act_as::<Editor>(cx)
11040 .expect("should have navigated into an editor for the 1st buffer")
11041 .read(cx)
11042 .text(cx),
11043 sample_text_1
11044 );
11045
11046 workspace
11047 .go_back(workspace.active_pane().downgrade(), cx)
11048 .detach_and_log_err(cx);
11049
11050 first_item_id
11051 })
11052 .unwrap();
11053 cx.executor().run_until_parked();
11054 workspace
11055 .update(cx, |workspace, cx| {
11056 let active_item = workspace
11057 .active_item(cx)
11058 .expect("should have an active item after navigating back");
11059 assert_eq!(
11060 active_item.item_id(),
11061 multibuffer_item_id,
11062 "Should navigate back to the multi buffer"
11063 );
11064 assert!(!active_item.is_singleton(cx));
11065 })
11066 .unwrap();
11067
11068 multi_buffer_editor.update(cx, |editor, cx| {
11069 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11070 s.select_ranges(Some(39..40))
11071 });
11072 editor.open_excerpts(&OpenExcerpts, cx);
11073 });
11074 cx.executor().run_until_parked();
11075 let second_item_id = workspace
11076 .update(cx, |workspace, cx| {
11077 let active_item = workspace
11078 .active_item(cx)
11079 .expect("should have an active item after navigating into the 2nd buffer");
11080 let second_item_id = active_item.item_id();
11081 assert_ne!(
11082 second_item_id, multibuffer_item_id,
11083 "Should navigate away from the multibuffer"
11084 );
11085 assert_ne!(
11086 second_item_id, first_item_id,
11087 "Should navigate into the 2nd buffer and activate it"
11088 );
11089 assert!(
11090 active_item.is_singleton(cx),
11091 "New active item should be a singleton buffer"
11092 );
11093 assert_eq!(
11094 active_item
11095 .act_as::<Editor>(cx)
11096 .expect("should have navigated into an editor")
11097 .read(cx)
11098 .text(cx),
11099 sample_text_2
11100 );
11101
11102 workspace
11103 .go_back(workspace.active_pane().downgrade(), cx)
11104 .detach_and_log_err(cx);
11105
11106 second_item_id
11107 })
11108 .unwrap();
11109 cx.executor().run_until_parked();
11110 workspace
11111 .update(cx, |workspace, cx| {
11112 let active_item = workspace
11113 .active_item(cx)
11114 .expect("should have an active item after navigating back from the 2nd buffer");
11115 assert_eq!(
11116 active_item.item_id(),
11117 multibuffer_item_id,
11118 "Should navigate back from the 2nd buffer to the multi buffer"
11119 );
11120 assert!(!active_item.is_singleton(cx));
11121 })
11122 .unwrap();
11123
11124 multi_buffer_editor.update(cx, |editor, cx| {
11125 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11126 s.select_ranges(Some(60..70))
11127 });
11128 editor.open_excerpts(&OpenExcerpts, cx);
11129 });
11130 cx.executor().run_until_parked();
11131 workspace
11132 .update(cx, |workspace, cx| {
11133 let active_item = workspace
11134 .active_item(cx)
11135 .expect("should have an active item after navigating into the 3rd buffer");
11136 let third_item_id = active_item.item_id();
11137 assert_ne!(
11138 third_item_id, multibuffer_item_id,
11139 "Should navigate into the 3rd buffer and activate it"
11140 );
11141 assert_ne!(third_item_id, first_item_id);
11142 assert_ne!(third_item_id, second_item_id);
11143 assert!(
11144 active_item.is_singleton(cx),
11145 "New active item should be a singleton buffer"
11146 );
11147 assert_eq!(
11148 active_item
11149 .act_as::<Editor>(cx)
11150 .expect("should have navigated into an editor")
11151 .read(cx)
11152 .text(cx),
11153 sample_text_3
11154 );
11155
11156 workspace
11157 .go_back(workspace.active_pane().downgrade(), cx)
11158 .detach_and_log_err(cx);
11159 })
11160 .unwrap();
11161 cx.executor().run_until_parked();
11162 workspace
11163 .update(cx, |workspace, cx| {
11164 let active_item = workspace
11165 .active_item(cx)
11166 .expect("should have an active item after navigating back from the 3rd buffer");
11167 assert_eq!(
11168 active_item.item_id(),
11169 multibuffer_item_id,
11170 "Should navigate back from the 3rd buffer to the multi buffer"
11171 );
11172 assert!(!active_item.is_singleton(cx));
11173 })
11174 .unwrap();
11175}
11176
11177#[gpui::test]
11178async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11179 init_test(cx, |_| {});
11180
11181 let mut cx = EditorTestContext::new(cx).await;
11182
11183 let diff_base = r#"
11184 use some::mod;
11185
11186 const A: u32 = 42;
11187
11188 fn main() {
11189 println!("hello");
11190
11191 println!("world");
11192 }
11193 "#
11194 .unindent();
11195
11196 cx.set_state(
11197 &r#"
11198 use some::modified;
11199
11200 ˇ
11201 fn main() {
11202 println!("hello there");
11203
11204 println!("around the");
11205 println!("world");
11206 }
11207 "#
11208 .unindent(),
11209 );
11210
11211 cx.set_diff_base(Some(&diff_base));
11212 executor.run_until_parked();
11213
11214 cx.update_editor(|editor, cx| {
11215 editor.go_to_next_hunk(&GoToHunk, cx);
11216 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11217 });
11218 executor.run_until_parked();
11219 cx.assert_diff_hunks(
11220 r#"
11221 use some::modified;
11222
11223
11224 fn main() {
11225 - println!("hello");
11226 + println!("hello there");
11227
11228 println!("around the");
11229 println!("world");
11230 }
11231 "#
11232 .unindent(),
11233 );
11234
11235 cx.update_editor(|editor, cx| {
11236 for _ in 0..3 {
11237 editor.go_to_next_hunk(&GoToHunk, cx);
11238 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11239 }
11240 });
11241 executor.run_until_parked();
11242 cx.assert_editor_state(
11243 &r#"
11244 use some::modified;
11245
11246 ˇ
11247 fn main() {
11248 println!("hello there");
11249
11250 println!("around the");
11251 println!("world");
11252 }
11253 "#
11254 .unindent(),
11255 );
11256
11257 cx.assert_diff_hunks(
11258 r#"
11259 - use some::mod;
11260 + use some::modified;
11261
11262 - const A: u32 = 42;
11263
11264 fn main() {
11265 - println!("hello");
11266 + println!("hello there");
11267
11268 + println!("around the");
11269 println!("world");
11270 }
11271 "#
11272 .unindent(),
11273 );
11274
11275 cx.update_editor(|editor, cx| {
11276 editor.cancel(&Cancel, cx);
11277 });
11278
11279 cx.assert_diff_hunks(
11280 r#"
11281 use some::modified;
11282
11283
11284 fn main() {
11285 println!("hello there");
11286
11287 println!("around the");
11288 println!("world");
11289 }
11290 "#
11291 .unindent(),
11292 );
11293}
11294
11295#[gpui::test]
11296async fn test_diff_base_change_with_expanded_diff_hunks(
11297 executor: BackgroundExecutor,
11298 cx: &mut gpui::TestAppContext,
11299) {
11300 init_test(cx, |_| {});
11301
11302 let mut cx = EditorTestContext::new(cx).await;
11303
11304 let diff_base = r#"
11305 use some::mod1;
11306 use some::mod2;
11307
11308 const A: u32 = 42;
11309 const B: u32 = 42;
11310 const C: u32 = 42;
11311
11312 fn main() {
11313 println!("hello");
11314
11315 println!("world");
11316 }
11317 "#
11318 .unindent();
11319
11320 cx.set_state(
11321 &r#"
11322 use some::mod2;
11323
11324 const A: u32 = 42;
11325 const C: u32 = 42;
11326
11327 fn main(ˇ) {
11328 //println!("hello");
11329
11330 println!("world");
11331 //
11332 //
11333 }
11334 "#
11335 .unindent(),
11336 );
11337
11338 cx.set_diff_base(Some(&diff_base));
11339 executor.run_until_parked();
11340
11341 cx.update_editor(|editor, cx| {
11342 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11343 });
11344 executor.run_until_parked();
11345 cx.assert_diff_hunks(
11346 r#"
11347 - use some::mod1;
11348 use some::mod2;
11349
11350 const A: u32 = 42;
11351 - const B: u32 = 42;
11352 const C: u32 = 42;
11353
11354 fn main() {
11355 - println!("hello");
11356 + //println!("hello");
11357
11358 println!("world");
11359 + //
11360 + //
11361 }
11362 "#
11363 .unindent(),
11364 );
11365
11366 cx.set_diff_base(Some("new diff base!"));
11367 executor.run_until_parked();
11368 cx.assert_diff_hunks(
11369 r#"
11370 use some::mod2;
11371
11372 const A: u32 = 42;
11373 const C: u32 = 42;
11374
11375 fn main() {
11376 //println!("hello");
11377
11378 println!("world");
11379 //
11380 //
11381 }
11382 "#
11383 .unindent(),
11384 );
11385
11386 cx.update_editor(|editor, cx| {
11387 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11388 });
11389 executor.run_until_parked();
11390 cx.assert_diff_hunks(
11391 r#"
11392 - new diff base!
11393 + use some::mod2;
11394 +
11395 + const A: u32 = 42;
11396 + const C: u32 = 42;
11397 +
11398 + fn main() {
11399 + //println!("hello");
11400 +
11401 + println!("world");
11402 + //
11403 + //
11404 + }
11405 "#
11406 .unindent(),
11407 );
11408}
11409
11410#[gpui::test]
11411async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11412 init_test(cx, |_| {});
11413
11414 let mut cx = EditorTestContext::new(cx).await;
11415
11416 let diff_base = r#"
11417 use some::mod1;
11418 use some::mod2;
11419
11420 const A: u32 = 42;
11421 const B: u32 = 42;
11422 const C: u32 = 42;
11423
11424 fn main() {
11425 println!("hello");
11426
11427 println!("world");
11428 }
11429
11430 fn another() {
11431 println!("another");
11432 }
11433
11434 fn another2() {
11435 println!("another2");
11436 }
11437 "#
11438 .unindent();
11439
11440 cx.set_state(
11441 &r#"
11442 «use some::mod2;
11443
11444 const A: u32 = 42;
11445 const C: u32 = 42;
11446
11447 fn main() {
11448 //println!("hello");
11449
11450 println!("world");
11451 //
11452 //ˇ»
11453 }
11454
11455 fn another() {
11456 println!("another");
11457 println!("another");
11458 }
11459
11460 println!("another2");
11461 }
11462 "#
11463 .unindent(),
11464 );
11465
11466 cx.set_diff_base(Some(&diff_base));
11467 executor.run_until_parked();
11468
11469 cx.update_editor(|editor, cx| {
11470 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11471 });
11472 executor.run_until_parked();
11473
11474 cx.assert_diff_hunks(
11475 r#"
11476 - use some::mod1;
11477 use some::mod2;
11478
11479 const A: u32 = 42;
11480 - const B: u32 = 42;
11481 const C: u32 = 42;
11482
11483 fn main() {
11484 - println!("hello");
11485 + //println!("hello");
11486
11487 println!("world");
11488 + //
11489 + //
11490 }
11491
11492 fn another() {
11493 println!("another");
11494 + println!("another");
11495 }
11496
11497 - fn another2() {
11498 println!("another2");
11499 }
11500 "#
11501 .unindent(),
11502 );
11503
11504 // Fold across some of the diff hunks. They should no longer appear expanded.
11505 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11506 cx.executor().run_until_parked();
11507
11508 // Hunks are not shown if their position is within a fold
11509 cx.assert_diff_hunks(
11510 r#"
11511 use some::mod2;
11512
11513 const A: u32 = 42;
11514 const C: u32 = 42;
11515
11516 fn main() {
11517 //println!("hello");
11518
11519 println!("world");
11520 //
11521 //
11522 }
11523
11524 fn another() {
11525 println!("another");
11526 + println!("another");
11527 }
11528
11529 - fn another2() {
11530 println!("another2");
11531 }
11532 "#
11533 .unindent(),
11534 );
11535
11536 cx.update_editor(|editor, cx| {
11537 editor.select_all(&SelectAll, cx);
11538 editor.unfold_lines(&UnfoldLines, cx);
11539 });
11540 cx.executor().run_until_parked();
11541
11542 // The deletions reappear when unfolding.
11543 cx.assert_diff_hunks(
11544 r#"
11545 - use some::mod1;
11546 use some::mod2;
11547
11548 const A: u32 = 42;
11549 - const B: u32 = 42;
11550 const C: u32 = 42;
11551
11552 fn main() {
11553 - println!("hello");
11554 + //println!("hello");
11555
11556 println!("world");
11557 + //
11558 + //
11559 }
11560
11561 fn another() {
11562 println!("another");
11563 + println!("another");
11564 }
11565
11566 - fn another2() {
11567 println!("another2");
11568 }
11569 "#
11570 .unindent(),
11571 );
11572}
11573
11574#[gpui::test]
11575async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11576 init_test(cx, |_| {});
11577
11578 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11579 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11580 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11581 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11582 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11583 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11584
11585 let buffer_1 = cx.new_model(|cx| {
11586 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11587 buffer.set_diff_base(Some(file_1_old.into()), cx);
11588 buffer
11589 });
11590 let buffer_2 = cx.new_model(|cx| {
11591 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11592 buffer.set_diff_base(Some(file_2_old.into()), cx);
11593 buffer
11594 });
11595 let buffer_3 = cx.new_model(|cx| {
11596 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11597 buffer.set_diff_base(Some(file_3_old.into()), cx);
11598 buffer
11599 });
11600
11601 let multi_buffer = cx.new_model(|cx| {
11602 let mut multibuffer = MultiBuffer::new(ReadWrite);
11603 multibuffer.push_excerpts(
11604 buffer_1.clone(),
11605 [
11606 ExcerptRange {
11607 context: Point::new(0, 0)..Point::new(3, 0),
11608 primary: None,
11609 },
11610 ExcerptRange {
11611 context: Point::new(5, 0)..Point::new(7, 0),
11612 primary: None,
11613 },
11614 ExcerptRange {
11615 context: Point::new(9, 0)..Point::new(10, 3),
11616 primary: None,
11617 },
11618 ],
11619 cx,
11620 );
11621 multibuffer.push_excerpts(
11622 buffer_2.clone(),
11623 [
11624 ExcerptRange {
11625 context: Point::new(0, 0)..Point::new(3, 0),
11626 primary: None,
11627 },
11628 ExcerptRange {
11629 context: Point::new(5, 0)..Point::new(7, 0),
11630 primary: None,
11631 },
11632 ExcerptRange {
11633 context: Point::new(9, 0)..Point::new(10, 3),
11634 primary: None,
11635 },
11636 ],
11637 cx,
11638 );
11639 multibuffer.push_excerpts(
11640 buffer_3.clone(),
11641 [
11642 ExcerptRange {
11643 context: Point::new(0, 0)..Point::new(3, 0),
11644 primary: None,
11645 },
11646 ExcerptRange {
11647 context: Point::new(5, 0)..Point::new(7, 0),
11648 primary: None,
11649 },
11650 ExcerptRange {
11651 context: Point::new(9, 0)..Point::new(10, 3),
11652 primary: None,
11653 },
11654 ],
11655 cx,
11656 );
11657 multibuffer
11658 });
11659
11660 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11661 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11662 cx.run_until_parked();
11663
11664 cx.assert_editor_state(
11665 &"
11666 ˇaaa
11667 ccc
11668 ddd
11669
11670 ggg
11671 hhh
11672
11673
11674 lll
11675 mmm
11676 NNN
11677
11678 qqq
11679 rrr
11680
11681 uuu
11682 111
11683 222
11684 333
11685
11686 666
11687 777
11688
11689 000
11690 !!!"
11691 .unindent(),
11692 );
11693
11694 cx.update_editor(|editor, cx| {
11695 editor.select_all(&SelectAll, cx);
11696 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11697 });
11698 cx.executor().run_until_parked();
11699
11700 cx.assert_diff_hunks(
11701 "
11702 aaa
11703 - bbb
11704 ccc
11705 ddd
11706
11707 ggg
11708 hhh
11709
11710
11711 lll
11712 mmm
11713 - nnn
11714 + NNN
11715
11716 qqq
11717 rrr
11718
11719 uuu
11720 111
11721 222
11722 333
11723
11724 + 666
11725 777
11726
11727 000
11728 !!!"
11729 .unindent(),
11730 );
11731}
11732
11733#[gpui::test]
11734async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
11735 init_test(cx, |_| {});
11736
11737 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
11738 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
11739
11740 let buffer = cx.new_model(|cx| {
11741 let mut buffer = Buffer::local(text.to_string(), cx);
11742 buffer.set_diff_base(Some(base.into()), cx);
11743 buffer
11744 });
11745
11746 let multi_buffer = cx.new_model(|cx| {
11747 let mut multibuffer = MultiBuffer::new(ReadWrite);
11748 multibuffer.push_excerpts(
11749 buffer.clone(),
11750 [
11751 ExcerptRange {
11752 context: Point::new(0, 0)..Point::new(2, 0),
11753 primary: None,
11754 },
11755 ExcerptRange {
11756 context: Point::new(5, 0)..Point::new(7, 0),
11757 primary: None,
11758 },
11759 ],
11760 cx,
11761 );
11762 multibuffer
11763 });
11764
11765 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11766 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11767 cx.run_until_parked();
11768
11769 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
11770 cx.executor().run_until_parked();
11771
11772 cx.assert_diff_hunks(
11773 "
11774 aaa
11775 - bbb
11776 + BBB
11777
11778 - ddd
11779 - eee
11780 + EEE
11781 fff
11782 "
11783 .unindent(),
11784 );
11785}
11786
11787#[gpui::test]
11788async fn test_edits_around_expanded_insertion_hunks(
11789 executor: BackgroundExecutor,
11790 cx: &mut gpui::TestAppContext,
11791) {
11792 init_test(cx, |_| {});
11793
11794 let mut cx = EditorTestContext::new(cx).await;
11795
11796 let diff_base = r#"
11797 use some::mod1;
11798 use some::mod2;
11799
11800 const A: u32 = 42;
11801
11802 fn main() {
11803 println!("hello");
11804
11805 println!("world");
11806 }
11807 "#
11808 .unindent();
11809 executor.run_until_parked();
11810 cx.set_state(
11811 &r#"
11812 use some::mod1;
11813 use some::mod2;
11814
11815 const A: u32 = 42;
11816 const B: u32 = 42;
11817 const C: u32 = 42;
11818 ˇ
11819
11820 fn main() {
11821 println!("hello");
11822
11823 println!("world");
11824 }
11825 "#
11826 .unindent(),
11827 );
11828
11829 cx.set_diff_base(Some(&diff_base));
11830 executor.run_until_parked();
11831
11832 cx.update_editor(|editor, cx| {
11833 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11834 });
11835 executor.run_until_parked();
11836
11837 cx.assert_diff_hunks(
11838 r#"
11839 use some::mod1;
11840 use some::mod2;
11841
11842 const A: u32 = 42;
11843 + const B: u32 = 42;
11844 + const C: u32 = 42;
11845 +
11846
11847 fn main() {
11848 println!("hello");
11849
11850 println!("world");
11851 }
11852 "#
11853 .unindent(),
11854 );
11855
11856 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11857 executor.run_until_parked();
11858
11859 cx.assert_diff_hunks(
11860 r#"
11861 use some::mod1;
11862 use some::mod2;
11863
11864 const A: u32 = 42;
11865 + const B: u32 = 42;
11866 + const C: u32 = 42;
11867 + const D: u32 = 42;
11868 +
11869
11870 fn main() {
11871 println!("hello");
11872
11873 println!("world");
11874 }
11875 "#
11876 .unindent(),
11877 );
11878
11879 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11880 executor.run_until_parked();
11881
11882 cx.assert_diff_hunks(
11883 r#"
11884 use some::mod1;
11885 use some::mod2;
11886
11887 const A: u32 = 42;
11888 + const B: u32 = 42;
11889 + const C: u32 = 42;
11890 + const D: u32 = 42;
11891 + const E: u32 = 42;
11892 +
11893
11894 fn main() {
11895 println!("hello");
11896
11897 println!("world");
11898 }
11899 "#
11900 .unindent(),
11901 );
11902
11903 cx.update_editor(|editor, cx| {
11904 editor.delete_line(&DeleteLine, cx);
11905 });
11906 executor.run_until_parked();
11907
11908 cx.assert_diff_hunks(
11909 r#"
11910 use some::mod1;
11911 use some::mod2;
11912
11913 const A: u32 = 42;
11914 + const B: u32 = 42;
11915 + const C: u32 = 42;
11916 + const D: u32 = 42;
11917 + const E: u32 = 42;
11918
11919 fn main() {
11920 println!("hello");
11921
11922 println!("world");
11923 }
11924 "#
11925 .unindent(),
11926 );
11927
11928 cx.update_editor(|editor, cx| {
11929 editor.move_up(&MoveUp, cx);
11930 editor.delete_line(&DeleteLine, cx);
11931 editor.move_up(&MoveUp, cx);
11932 editor.delete_line(&DeleteLine, cx);
11933 editor.move_up(&MoveUp, cx);
11934 editor.delete_line(&DeleteLine, cx);
11935 });
11936 executor.run_until_parked();
11937 cx.assert_editor_state(
11938 &r#"
11939 use some::mod1;
11940 use some::mod2;
11941
11942 const A: u32 = 42;
11943 const B: u32 = 42;
11944 ˇ
11945 fn main() {
11946 println!("hello");
11947
11948 println!("world");
11949 }
11950 "#
11951 .unindent(),
11952 );
11953
11954 cx.assert_diff_hunks(
11955 r#"
11956 use some::mod1;
11957 use some::mod2;
11958
11959 const A: u32 = 42;
11960 + const B: u32 = 42;
11961
11962 fn main() {
11963 println!("hello");
11964
11965 println!("world");
11966 }
11967 "#
11968 .unindent(),
11969 );
11970
11971 cx.update_editor(|editor, cx| {
11972 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11973 editor.delete_line(&DeleteLine, cx);
11974 });
11975 executor.run_until_parked();
11976 cx.assert_diff_hunks(
11977 r#"
11978 use some::mod1;
11979 - use some::mod2;
11980 -
11981 - const A: u32 = 42;
11982
11983 fn main() {
11984 println!("hello");
11985
11986 println!("world");
11987 }
11988 "#
11989 .unindent(),
11990 );
11991}
11992
11993#[gpui::test]
11994async fn test_edits_around_expanded_deletion_hunks(
11995 executor: BackgroundExecutor,
11996 cx: &mut gpui::TestAppContext,
11997) {
11998 init_test(cx, |_| {});
11999
12000 let mut cx = EditorTestContext::new(cx).await;
12001
12002 let diff_base = r#"
12003 use some::mod1;
12004 use some::mod2;
12005
12006 const A: u32 = 42;
12007 const B: u32 = 42;
12008 const C: u32 = 42;
12009
12010
12011 fn main() {
12012 println!("hello");
12013
12014 println!("world");
12015 }
12016 "#
12017 .unindent();
12018 executor.run_until_parked();
12019 cx.set_state(
12020 &r#"
12021 use some::mod1;
12022 use some::mod2;
12023
12024 ˇconst B: u32 = 42;
12025 const C: u32 = 42;
12026
12027
12028 fn main() {
12029 println!("hello");
12030
12031 println!("world");
12032 }
12033 "#
12034 .unindent(),
12035 );
12036
12037 cx.set_diff_base(Some(&diff_base));
12038 executor.run_until_parked();
12039
12040 cx.update_editor(|editor, cx| {
12041 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12042 });
12043 executor.run_until_parked();
12044
12045 cx.assert_diff_hunks(
12046 r#"
12047 use some::mod1;
12048 use some::mod2;
12049
12050 - const A: u32 = 42;
12051 const B: u32 = 42;
12052 const C: u32 = 42;
12053
12054
12055 fn main() {
12056 println!("hello");
12057
12058 println!("world");
12059 }
12060 "#
12061 .unindent(),
12062 );
12063
12064 cx.update_editor(|editor, cx| {
12065 editor.delete_line(&DeleteLine, cx);
12066 });
12067 executor.run_until_parked();
12068 cx.assert_editor_state(
12069 &r#"
12070 use some::mod1;
12071 use some::mod2;
12072
12073 ˇconst C: u32 = 42;
12074
12075
12076 fn main() {
12077 println!("hello");
12078
12079 println!("world");
12080 }
12081 "#
12082 .unindent(),
12083 );
12084 cx.assert_diff_hunks(
12085 r#"
12086 use some::mod1;
12087 use some::mod2;
12088
12089 - const A: u32 = 42;
12090 - const B: u32 = 42;
12091 const C: u32 = 42;
12092
12093
12094 fn main() {
12095 println!("hello");
12096
12097 println!("world");
12098 }
12099 "#
12100 .unindent(),
12101 );
12102
12103 cx.update_editor(|editor, cx| {
12104 editor.delete_line(&DeleteLine, cx);
12105 });
12106 executor.run_until_parked();
12107 cx.assert_editor_state(
12108 &r#"
12109 use some::mod1;
12110 use some::mod2;
12111
12112 ˇ
12113
12114 fn main() {
12115 println!("hello");
12116
12117 println!("world");
12118 }
12119 "#
12120 .unindent(),
12121 );
12122 cx.assert_diff_hunks(
12123 r#"
12124 use some::mod1;
12125 use some::mod2;
12126
12127 - const A: u32 = 42;
12128 - const B: u32 = 42;
12129 - const C: u32 = 42;
12130
12131
12132 fn main() {
12133 println!("hello");
12134
12135 println!("world");
12136 }
12137 "#
12138 .unindent(),
12139 );
12140
12141 cx.update_editor(|editor, cx| {
12142 editor.handle_input("replacement", cx);
12143 });
12144 executor.run_until_parked();
12145 cx.assert_editor_state(
12146 &r#"
12147 use some::mod1;
12148 use some::mod2;
12149
12150 replacementˇ
12151
12152 fn main() {
12153 println!("hello");
12154
12155 println!("world");
12156 }
12157 "#
12158 .unindent(),
12159 );
12160 cx.assert_diff_hunks(
12161 r#"
12162 use some::mod1;
12163 use some::mod2;
12164
12165 - const A: u32 = 42;
12166 - const B: u32 = 42;
12167 - const C: u32 = 42;
12168 -
12169 + replacement
12170
12171 fn main() {
12172 println!("hello");
12173
12174 println!("world");
12175 }
12176 "#
12177 .unindent(),
12178 );
12179}
12180
12181#[gpui::test]
12182async fn test_edit_after_expanded_modification_hunk(
12183 executor: BackgroundExecutor,
12184 cx: &mut gpui::TestAppContext,
12185) {
12186 init_test(cx, |_| {});
12187
12188 let mut cx = EditorTestContext::new(cx).await;
12189
12190 let diff_base = r#"
12191 use some::mod1;
12192 use some::mod2;
12193
12194 const A: u32 = 42;
12195 const B: u32 = 42;
12196 const C: u32 = 42;
12197 const D: u32 = 42;
12198
12199
12200 fn main() {
12201 println!("hello");
12202
12203 println!("world");
12204 }"#
12205 .unindent();
12206
12207 cx.set_state(
12208 &r#"
12209 use some::mod1;
12210 use some::mod2;
12211
12212 const A: u32 = 42;
12213 const B: u32 = 42;
12214 const C: u32 = 43ˇ
12215 const D: u32 = 42;
12216
12217
12218 fn main() {
12219 println!("hello");
12220
12221 println!("world");
12222 }"#
12223 .unindent(),
12224 );
12225
12226 cx.set_diff_base(Some(&diff_base));
12227 executor.run_until_parked();
12228 cx.update_editor(|editor, cx| {
12229 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12230 });
12231 executor.run_until_parked();
12232
12233 cx.assert_diff_hunks(
12234 r#"
12235 use some::mod1;
12236 use some::mod2;
12237
12238 const A: u32 = 42;
12239 const B: u32 = 42;
12240 - const C: u32 = 42;
12241 + const C: u32 = 43
12242 const D: u32 = 42;
12243
12244
12245 fn main() {
12246 println!("hello");
12247
12248 println!("world");
12249 }"#
12250 .unindent(),
12251 );
12252
12253 cx.update_editor(|editor, cx| {
12254 editor.handle_input("\nnew_line\n", cx);
12255 });
12256 executor.run_until_parked();
12257
12258 cx.assert_diff_hunks(
12259 r#"
12260 use some::mod1;
12261 use some::mod2;
12262
12263 const A: u32 = 42;
12264 const B: u32 = 42;
12265 - const C: u32 = 42;
12266 + const C: u32 = 43
12267 + new_line
12268 +
12269 const D: u32 = 42;
12270
12271
12272 fn main() {
12273 println!("hello");
12274
12275 println!("world");
12276 }"#
12277 .unindent(),
12278 );
12279}
12280
12281async fn setup_indent_guides_editor(
12282 text: &str,
12283 cx: &mut gpui::TestAppContext,
12284) -> (BufferId, EditorTestContext) {
12285 init_test(cx, |_| {});
12286
12287 let mut cx = EditorTestContext::new(cx).await;
12288
12289 let buffer_id = cx.update_editor(|editor, cx| {
12290 editor.set_text(text, cx);
12291 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12292
12293 buffer_ids[0]
12294 });
12295
12296 (buffer_id, cx)
12297}
12298
12299fn assert_indent_guides(
12300 range: Range<u32>,
12301 expected: Vec<IndentGuide>,
12302 active_indices: Option<Vec<usize>>,
12303 cx: &mut EditorTestContext,
12304) {
12305 let indent_guides = cx.update_editor(|editor, cx| {
12306 let snapshot = editor.snapshot(cx).display_snapshot;
12307 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12308 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12309 true,
12310 &snapshot,
12311 cx,
12312 );
12313
12314 indent_guides.sort_by(|a, b| {
12315 a.depth.cmp(&b.depth).then(
12316 a.start_row
12317 .cmp(&b.start_row)
12318 .then(a.end_row.cmp(&b.end_row)),
12319 )
12320 });
12321 indent_guides
12322 });
12323
12324 if let Some(expected) = active_indices {
12325 let active_indices = cx.update_editor(|editor, cx| {
12326 let snapshot = editor.snapshot(cx).display_snapshot;
12327 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12328 });
12329
12330 assert_eq!(
12331 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12332 expected,
12333 "Active indent guide indices do not match"
12334 );
12335 }
12336
12337 let expected: Vec<_> = expected
12338 .into_iter()
12339 .map(|guide| MultiBufferIndentGuide {
12340 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12341 buffer: guide,
12342 })
12343 .collect();
12344
12345 assert_eq!(indent_guides, expected, "Indent guides do not match");
12346}
12347
12348fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12349 IndentGuide {
12350 buffer_id,
12351 start_row,
12352 end_row,
12353 depth,
12354 tab_size: 4,
12355 settings: IndentGuideSettings {
12356 enabled: true,
12357 line_width: 1,
12358 active_line_width: 1,
12359 ..Default::default()
12360 },
12361 }
12362}
12363
12364#[gpui::test]
12365async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12366 let (buffer_id, mut cx) = setup_indent_guides_editor(
12367 &"
12368 fn main() {
12369 let a = 1;
12370 }"
12371 .unindent(),
12372 cx,
12373 )
12374 .await;
12375
12376 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12377}
12378
12379#[gpui::test]
12380async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12381 let (buffer_id, mut cx) = setup_indent_guides_editor(
12382 &"
12383 fn main() {
12384 let a = 1;
12385 let b = 2;
12386 }"
12387 .unindent(),
12388 cx,
12389 )
12390 .await;
12391
12392 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12393}
12394
12395#[gpui::test]
12396async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12397 let (buffer_id, mut cx) = setup_indent_guides_editor(
12398 &"
12399 fn main() {
12400 let a = 1;
12401 if a == 3 {
12402 let b = 2;
12403 } else {
12404 let c = 3;
12405 }
12406 }"
12407 .unindent(),
12408 cx,
12409 )
12410 .await;
12411
12412 assert_indent_guides(
12413 0..8,
12414 vec![
12415 indent_guide(buffer_id, 1, 6, 0),
12416 indent_guide(buffer_id, 3, 3, 1),
12417 indent_guide(buffer_id, 5, 5, 1),
12418 ],
12419 None,
12420 &mut cx,
12421 );
12422}
12423
12424#[gpui::test]
12425async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12426 let (buffer_id, mut cx) = setup_indent_guides_editor(
12427 &"
12428 fn main() {
12429 let a = 1;
12430 let b = 2;
12431 let c = 3;
12432 }"
12433 .unindent(),
12434 cx,
12435 )
12436 .await;
12437
12438 assert_indent_guides(
12439 0..5,
12440 vec![
12441 indent_guide(buffer_id, 1, 3, 0),
12442 indent_guide(buffer_id, 2, 2, 1),
12443 ],
12444 None,
12445 &mut cx,
12446 );
12447}
12448
12449#[gpui::test]
12450async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12451 let (buffer_id, mut cx) = setup_indent_guides_editor(
12452 &"
12453 fn main() {
12454 let a = 1;
12455
12456 let c = 3;
12457 }"
12458 .unindent(),
12459 cx,
12460 )
12461 .await;
12462
12463 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12464}
12465
12466#[gpui::test]
12467async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12468 let (buffer_id, mut cx) = setup_indent_guides_editor(
12469 &"
12470 fn main() {
12471 let a = 1;
12472
12473 let c = 3;
12474
12475 if a == 3 {
12476 let b = 2;
12477 } else {
12478 let c = 3;
12479 }
12480 }"
12481 .unindent(),
12482 cx,
12483 )
12484 .await;
12485
12486 assert_indent_guides(
12487 0..11,
12488 vec![
12489 indent_guide(buffer_id, 1, 9, 0),
12490 indent_guide(buffer_id, 6, 6, 1),
12491 indent_guide(buffer_id, 8, 8, 1),
12492 ],
12493 None,
12494 &mut cx,
12495 );
12496}
12497
12498#[gpui::test]
12499async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12500 let (buffer_id, mut cx) = setup_indent_guides_editor(
12501 &"
12502 fn main() {
12503 let a = 1;
12504
12505 let c = 3;
12506
12507 if a == 3 {
12508 let b = 2;
12509 } else {
12510 let c = 3;
12511 }
12512 }"
12513 .unindent(),
12514 cx,
12515 )
12516 .await;
12517
12518 assert_indent_guides(
12519 1..11,
12520 vec![
12521 indent_guide(buffer_id, 1, 9, 0),
12522 indent_guide(buffer_id, 6, 6, 1),
12523 indent_guide(buffer_id, 8, 8, 1),
12524 ],
12525 None,
12526 &mut cx,
12527 );
12528}
12529
12530#[gpui::test]
12531async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12532 let (buffer_id, mut cx) = setup_indent_guides_editor(
12533 &"
12534 fn main() {
12535 let a = 1;
12536
12537 let c = 3;
12538
12539 if a == 3 {
12540 let b = 2;
12541 } else {
12542 let c = 3;
12543 }
12544 }"
12545 .unindent(),
12546 cx,
12547 )
12548 .await;
12549
12550 assert_indent_guides(
12551 1..10,
12552 vec![
12553 indent_guide(buffer_id, 1, 9, 0),
12554 indent_guide(buffer_id, 6, 6, 1),
12555 indent_guide(buffer_id, 8, 8, 1),
12556 ],
12557 None,
12558 &mut cx,
12559 );
12560}
12561
12562#[gpui::test]
12563async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12564 let (buffer_id, mut cx) = setup_indent_guides_editor(
12565 &"
12566 block1
12567 block2
12568 block3
12569 block4
12570 block2
12571 block1
12572 block1"
12573 .unindent(),
12574 cx,
12575 )
12576 .await;
12577
12578 assert_indent_guides(
12579 1..10,
12580 vec![
12581 indent_guide(buffer_id, 1, 4, 0),
12582 indent_guide(buffer_id, 2, 3, 1),
12583 indent_guide(buffer_id, 3, 3, 2),
12584 ],
12585 None,
12586 &mut cx,
12587 );
12588}
12589
12590#[gpui::test]
12591async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12592 let (buffer_id, mut cx) = setup_indent_guides_editor(
12593 &"
12594 block1
12595 block2
12596 block3
12597
12598 block1
12599 block1"
12600 .unindent(),
12601 cx,
12602 )
12603 .await;
12604
12605 assert_indent_guides(
12606 0..6,
12607 vec![
12608 indent_guide(buffer_id, 1, 2, 0),
12609 indent_guide(buffer_id, 2, 2, 1),
12610 ],
12611 None,
12612 &mut cx,
12613 );
12614}
12615
12616#[gpui::test]
12617async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12618 let (buffer_id, mut cx) = setup_indent_guides_editor(
12619 &"
12620 block1
12621
12622
12623
12624 block2
12625 "
12626 .unindent(),
12627 cx,
12628 )
12629 .await;
12630
12631 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12632}
12633
12634#[gpui::test]
12635async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12636 let (buffer_id, mut cx) = setup_indent_guides_editor(
12637 &"
12638 def a:
12639 \tb = 3
12640 \tif True:
12641 \t\tc = 4
12642 \t\td = 5
12643 \tprint(b)
12644 "
12645 .unindent(),
12646 cx,
12647 )
12648 .await;
12649
12650 assert_indent_guides(
12651 0..6,
12652 vec![
12653 indent_guide(buffer_id, 1, 6, 0),
12654 indent_guide(buffer_id, 3, 4, 1),
12655 ],
12656 None,
12657 &mut cx,
12658 );
12659}
12660
12661#[gpui::test]
12662async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12663 let (buffer_id, mut cx) = setup_indent_guides_editor(
12664 &"
12665 fn main() {
12666 let a = 1;
12667 }"
12668 .unindent(),
12669 cx,
12670 )
12671 .await;
12672
12673 cx.update_editor(|editor, cx| {
12674 editor.change_selections(None, cx, |s| {
12675 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12676 });
12677 });
12678
12679 assert_indent_guides(
12680 0..3,
12681 vec![indent_guide(buffer_id, 1, 1, 0)],
12682 Some(vec![0]),
12683 &mut cx,
12684 );
12685}
12686
12687#[gpui::test]
12688async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12689 let (buffer_id, mut cx) = setup_indent_guides_editor(
12690 &"
12691 fn main() {
12692 if 1 == 2 {
12693 let a = 1;
12694 }
12695 }"
12696 .unindent(),
12697 cx,
12698 )
12699 .await;
12700
12701 cx.update_editor(|editor, cx| {
12702 editor.change_selections(None, cx, |s| {
12703 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12704 });
12705 });
12706
12707 assert_indent_guides(
12708 0..4,
12709 vec![
12710 indent_guide(buffer_id, 1, 3, 0),
12711 indent_guide(buffer_id, 2, 2, 1),
12712 ],
12713 Some(vec![1]),
12714 &mut cx,
12715 );
12716
12717 cx.update_editor(|editor, cx| {
12718 editor.change_selections(None, cx, |s| {
12719 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12720 });
12721 });
12722
12723 assert_indent_guides(
12724 0..4,
12725 vec![
12726 indent_guide(buffer_id, 1, 3, 0),
12727 indent_guide(buffer_id, 2, 2, 1),
12728 ],
12729 Some(vec![1]),
12730 &mut cx,
12731 );
12732
12733 cx.update_editor(|editor, cx| {
12734 editor.change_selections(None, cx, |s| {
12735 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12736 });
12737 });
12738
12739 assert_indent_guides(
12740 0..4,
12741 vec![
12742 indent_guide(buffer_id, 1, 3, 0),
12743 indent_guide(buffer_id, 2, 2, 1),
12744 ],
12745 Some(vec![0]),
12746 &mut cx,
12747 );
12748}
12749
12750#[gpui::test]
12751async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12752 let (buffer_id, mut cx) = setup_indent_guides_editor(
12753 &"
12754 fn main() {
12755 let a = 1;
12756
12757 let b = 2;
12758 }"
12759 .unindent(),
12760 cx,
12761 )
12762 .await;
12763
12764 cx.update_editor(|editor, cx| {
12765 editor.change_selections(None, cx, |s| {
12766 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12767 });
12768 });
12769
12770 assert_indent_guides(
12771 0..5,
12772 vec![indent_guide(buffer_id, 1, 3, 0)],
12773 Some(vec![0]),
12774 &mut cx,
12775 );
12776}
12777
12778#[gpui::test]
12779async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12780 let (buffer_id, mut cx) = setup_indent_guides_editor(
12781 &"
12782 def m:
12783 a = 1
12784 pass"
12785 .unindent(),
12786 cx,
12787 )
12788 .await;
12789
12790 cx.update_editor(|editor, cx| {
12791 editor.change_selections(None, cx, |s| {
12792 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12793 });
12794 });
12795
12796 assert_indent_guides(
12797 0..3,
12798 vec![indent_guide(buffer_id, 1, 2, 0)],
12799 Some(vec![0]),
12800 &mut cx,
12801 );
12802}
12803
12804#[gpui::test]
12805fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12806 init_test(cx, |_| {});
12807
12808 let editor = cx.add_window(|cx| {
12809 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12810 build_editor(buffer, cx)
12811 });
12812
12813 let render_args = Arc::new(Mutex::new(None));
12814 let snapshot = editor
12815 .update(cx, |editor, cx| {
12816 let snapshot = editor.buffer().read(cx).snapshot(cx);
12817 let range =
12818 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12819
12820 struct RenderArgs {
12821 row: MultiBufferRow,
12822 folded: bool,
12823 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12824 }
12825
12826 let crease = Crease::new(
12827 range,
12828 FoldPlaceholder::test(),
12829 {
12830 let toggle_callback = render_args.clone();
12831 move |row, folded, callback, _cx| {
12832 *toggle_callback.lock() = Some(RenderArgs {
12833 row,
12834 folded,
12835 callback,
12836 });
12837 div()
12838 }
12839 },
12840 |_row, _folded, _cx| div(),
12841 );
12842
12843 editor.insert_creases(Some(crease), cx);
12844 let snapshot = editor.snapshot(cx);
12845 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12846 snapshot
12847 })
12848 .unwrap();
12849
12850 let render_args = render_args.lock().take().unwrap();
12851 assert_eq!(render_args.row, MultiBufferRow(1));
12852 assert!(!render_args.folded);
12853 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12854
12855 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12856 .unwrap();
12857 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12858 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12859
12860 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12861 .unwrap();
12862 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12863 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12864}
12865
12866#[gpui::test]
12867async fn test_input_text(cx: &mut gpui::TestAppContext) {
12868 init_test(cx, |_| {});
12869 let mut cx = EditorTestContext::new(cx).await;
12870
12871 cx.set_state(
12872 &r#"ˇone
12873 two
12874
12875 three
12876 fourˇ
12877 five
12878
12879 siˇx"#
12880 .unindent(),
12881 );
12882
12883 cx.dispatch_action(HandleInput(String::new()));
12884 cx.assert_editor_state(
12885 &r#"ˇone
12886 two
12887
12888 three
12889 fourˇ
12890 five
12891
12892 siˇx"#
12893 .unindent(),
12894 );
12895
12896 cx.dispatch_action(HandleInput("AAAA".to_string()));
12897 cx.assert_editor_state(
12898 &r#"AAAAˇone
12899 two
12900
12901 three
12902 fourAAAAˇ
12903 five
12904
12905 siAAAAˇx"#
12906 .unindent(),
12907 );
12908}
12909
12910#[gpui::test]
12911async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
12912 init_test(cx, |_| {});
12913
12914 let mut cx = EditorTestContext::new(cx).await;
12915 cx.set_state(
12916 r#"let foo = 1;
12917let foo = 2;
12918let foo = 3;
12919let fooˇ = 4;
12920let foo = 5;
12921let foo = 6;
12922let foo = 7;
12923let foo = 8;
12924let foo = 9;
12925let foo = 10;
12926let foo = 11;
12927let foo = 12;
12928let foo = 13;
12929let foo = 14;
12930let foo = 15;"#,
12931 );
12932
12933 cx.update_editor(|e, cx| {
12934 assert_eq!(
12935 e.next_scroll_position,
12936 NextScrollCursorCenterTopBottom::Center,
12937 "Default next scroll direction is center",
12938 );
12939
12940 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12941 assert_eq!(
12942 e.next_scroll_position,
12943 NextScrollCursorCenterTopBottom::Top,
12944 "After center, next scroll direction should be top",
12945 );
12946
12947 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12948 assert_eq!(
12949 e.next_scroll_position,
12950 NextScrollCursorCenterTopBottom::Bottom,
12951 "After top, next scroll direction should be bottom",
12952 );
12953
12954 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12955 assert_eq!(
12956 e.next_scroll_position,
12957 NextScrollCursorCenterTopBottom::Center,
12958 "After bottom, scrolling should start over",
12959 );
12960
12961 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12962 assert_eq!(
12963 e.next_scroll_position,
12964 NextScrollCursorCenterTopBottom::Top,
12965 "Scrolling continues if retriggered fast enough"
12966 );
12967 });
12968
12969 cx.executor()
12970 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
12971 cx.executor().run_until_parked();
12972 cx.update_editor(|e, _| {
12973 assert_eq!(
12974 e.next_scroll_position,
12975 NextScrollCursorCenterTopBottom::Center,
12976 "If scrolling is not triggered fast enough, it should reset"
12977 );
12978 });
12979}
12980
12981#[gpui::test]
12982async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
12983 init_test(cx, |_| {});
12984 let mut cx = EditorLspTestContext::new_rust(
12985 lsp::ServerCapabilities {
12986 definition_provider: Some(lsp::OneOf::Left(true)),
12987 references_provider: Some(lsp::OneOf::Left(true)),
12988 ..lsp::ServerCapabilities::default()
12989 },
12990 cx,
12991 )
12992 .await;
12993
12994 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
12995 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
12996 move |params, _| async move {
12997 if empty_go_to_definition {
12998 Ok(None)
12999 } else {
13000 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13001 uri: params.text_document_position_params.text_document.uri,
13002 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13003 })))
13004 }
13005 },
13006 );
13007 let references =
13008 cx.lsp
13009 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13010 Ok(Some(vec![lsp::Location {
13011 uri: params.text_document_position.text_document.uri,
13012 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13013 }]))
13014 });
13015 (go_to_definition, references)
13016 };
13017
13018 cx.set_state(
13019 &r#"fn one() {
13020 let mut a = ˇtwo();
13021 }
13022
13023 fn two() {}"#
13024 .unindent(),
13025 );
13026 set_up_lsp_handlers(false, &mut cx);
13027 let navigated = cx
13028 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13029 .await
13030 .expect("Failed to navigate to definition");
13031 assert_eq!(
13032 navigated,
13033 Navigated::Yes,
13034 "Should have navigated to definition from the GetDefinition response"
13035 );
13036 cx.assert_editor_state(
13037 &r#"fn one() {
13038 let mut a = two();
13039 }
13040
13041 fn «twoˇ»() {}"#
13042 .unindent(),
13043 );
13044
13045 let editors = cx.update_workspace(|workspace, cx| {
13046 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13047 });
13048 cx.update_editor(|_, test_editor_cx| {
13049 assert_eq!(
13050 editors.len(),
13051 1,
13052 "Initially, only one, test, editor should be open in the workspace"
13053 );
13054 assert_eq!(
13055 test_editor_cx.view(),
13056 editors.last().expect("Asserted len is 1")
13057 );
13058 });
13059
13060 set_up_lsp_handlers(true, &mut cx);
13061 let navigated = cx
13062 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13063 .await
13064 .expect("Failed to navigate to lookup references");
13065 assert_eq!(
13066 navigated,
13067 Navigated::Yes,
13068 "Should have navigated to references as a fallback after empty GoToDefinition response"
13069 );
13070 // We should not change the selections in the existing file,
13071 // if opening another milti buffer with the references
13072 cx.assert_editor_state(
13073 &r#"fn one() {
13074 let mut a = two();
13075 }
13076
13077 fn «twoˇ»() {}"#
13078 .unindent(),
13079 );
13080 let editors = cx.update_workspace(|workspace, cx| {
13081 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13082 });
13083 cx.update_editor(|_, test_editor_cx| {
13084 assert_eq!(
13085 editors.len(),
13086 2,
13087 "After falling back to references search, we open a new editor with the results"
13088 );
13089 let references_fallback_text = editors
13090 .into_iter()
13091 .find(|new_editor| new_editor != test_editor_cx.view())
13092 .expect("Should have one non-test editor now")
13093 .read(test_editor_cx)
13094 .text(test_editor_cx);
13095 assert_eq!(
13096 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13097 "Should use the range from the references response and not the GoToDefinition one"
13098 );
13099 });
13100}
13101
13102fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13103 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13104 point..point
13105}
13106
13107fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13108 let (text, ranges) = marked_text_ranges(marked_text, true);
13109 assert_eq!(view.text(cx), text);
13110 assert_eq!(
13111 view.selections.ranges(cx),
13112 ranges,
13113 "Assert selections are {}",
13114 marked_text
13115 );
13116}
13117
13118pub fn handle_signature_help_request(
13119 cx: &mut EditorLspTestContext,
13120 mocked_response: lsp::SignatureHelp,
13121) -> impl Future<Output = ()> {
13122 let mut request =
13123 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13124 let mocked_response = mocked_response.clone();
13125 async move { Ok(Some(mocked_response)) }
13126 });
13127
13128 async move {
13129 request.next().await;
13130 }
13131}
13132
13133/// Handle completion request passing a marked string specifying where the completion
13134/// should be triggered from using '|' character, what range should be replaced, and what completions
13135/// should be returned using '<' and '>' to delimit the range
13136pub fn handle_completion_request(
13137 cx: &mut EditorLspTestContext,
13138 marked_string: &str,
13139 completions: Vec<&'static str>,
13140 counter: Arc<AtomicUsize>,
13141) -> impl Future<Output = ()> {
13142 let complete_from_marker: TextRangeMarker = '|'.into();
13143 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13144 let (_, mut marked_ranges) = marked_text_ranges_by(
13145 marked_string,
13146 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13147 );
13148
13149 let complete_from_position =
13150 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13151 let replace_range =
13152 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13153
13154 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13155 let completions = completions.clone();
13156 counter.fetch_add(1, atomic::Ordering::Release);
13157 async move {
13158 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13159 assert_eq!(
13160 params.text_document_position.position,
13161 complete_from_position
13162 );
13163 Ok(Some(lsp::CompletionResponse::Array(
13164 completions
13165 .iter()
13166 .map(|completion_text| lsp::CompletionItem {
13167 label: completion_text.to_string(),
13168 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13169 range: replace_range,
13170 new_text: completion_text.to_string(),
13171 })),
13172 ..Default::default()
13173 })
13174 .collect(),
13175 )))
13176 }
13177 });
13178
13179 async move {
13180 request.next().await;
13181 }
13182}
13183
13184fn handle_resolve_completion_request(
13185 cx: &mut EditorLspTestContext,
13186 edits: Option<Vec<(&'static str, &'static str)>>,
13187) -> impl Future<Output = ()> {
13188 let edits = edits.map(|edits| {
13189 edits
13190 .iter()
13191 .map(|(marked_string, new_text)| {
13192 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13193 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13194 lsp::TextEdit::new(replace_range, new_text.to_string())
13195 })
13196 .collect::<Vec<_>>()
13197 });
13198
13199 let mut request =
13200 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13201 let edits = edits.clone();
13202 async move {
13203 Ok(lsp::CompletionItem {
13204 additional_text_edits: edits,
13205 ..Default::default()
13206 })
13207 }
13208 });
13209
13210 async move {
13211 request.next().await;
13212 }
13213}
13214
13215pub(crate) fn update_test_language_settings(
13216 cx: &mut TestAppContext,
13217 f: impl Fn(&mut AllLanguageSettingsContent),
13218) {
13219 cx.update(|cx| {
13220 SettingsStore::update_global(cx, |store, cx| {
13221 store.update_user_settings::<AllLanguageSettings>(cx, f);
13222 });
13223 });
13224}
13225
13226pub(crate) fn update_test_project_settings(
13227 cx: &mut TestAppContext,
13228 f: impl Fn(&mut ProjectSettings),
13229) {
13230 cx.update(|cx| {
13231 SettingsStore::update_global(cx, |store, cx| {
13232 store.update_user_settings::<ProjectSettings>(cx, f);
13233 });
13234 });
13235}
13236
13237pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13238 cx.update(|cx| {
13239 assets::Assets.load_test_fonts(cx);
13240 let store = SettingsStore::test(cx);
13241 cx.set_global(store);
13242 theme::init(theme::LoadThemes::JustBase, cx);
13243 release_channel::init(SemanticVersion::default(), cx);
13244 client::init_settings(cx);
13245 language::init(cx);
13246 Project::init_settings(cx);
13247 workspace::init_settings(cx);
13248 crate::init(cx);
13249 });
13250
13251 update_test_language_settings(cx, f);
13252}
13253
13254pub(crate) fn rust_lang() -> Arc<Language> {
13255 Arc::new(Language::new(
13256 LanguageConfig {
13257 name: "Rust".into(),
13258 matcher: LanguageMatcher {
13259 path_suffixes: vec!["rs".to_string()],
13260 ..Default::default()
13261 },
13262 ..Default::default()
13263 },
13264 Some(tree_sitter_rust::LANGUAGE.into()),
13265 ))
13266}
13267
13268#[track_caller]
13269fn assert_hunk_revert(
13270 not_reverted_text_with_selections: &str,
13271 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13272 expected_reverted_text_with_selections: &str,
13273 base_text: &str,
13274 cx: &mut EditorLspTestContext,
13275) {
13276 cx.set_state(not_reverted_text_with_selections);
13277 cx.update_editor(|editor, cx| {
13278 editor
13279 .buffer()
13280 .read(cx)
13281 .as_singleton()
13282 .unwrap()
13283 .update(cx, |buffer, cx| {
13284 buffer.set_diff_base(Some(base_text.into()), cx);
13285 });
13286 });
13287 cx.executor().run_until_parked();
13288
13289 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13290 let snapshot = editor.buffer().read(cx).snapshot(cx);
13291 let reverted_hunk_statuses = snapshot
13292 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13293 .map(|hunk| hunk_status(&hunk))
13294 .collect::<Vec<_>>();
13295
13296 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13297 reverted_hunk_statuses
13298 });
13299 cx.executor().run_until_parked();
13300 cx.assert_editor_state(expected_reverted_text_with_selections);
13301 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13302}