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_fold_at_level(cx: &mut TestAppContext) {
1085 init_test(cx, |_| {});
1086
1087 let view = cx.add_window(|cx| {
1088 let buffer = MultiBuffer::build_simple(
1089 &"
1090 class Foo:
1091 # Hello!
1092
1093 def a():
1094 print(1)
1095
1096 def b():
1097 print(2)
1098
1099
1100 class Bar:
1101 # World!
1102
1103 def a():
1104 print(1)
1105
1106 def b():
1107 print(2)
1108
1109
1110 "
1111 .unindent(),
1112 cx,
1113 );
1114 build_editor(buffer.clone(), cx)
1115 });
1116
1117 _ = view.update(cx, |view, cx| {
1118 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1119 assert_eq!(
1120 view.display_text(cx),
1121 "
1122 class Foo:
1123 # Hello!
1124
1125 def a():⋯
1126
1127 def b():⋯
1128
1129
1130 class Bar:
1131 # World!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 "
1139 .unindent(),
1140 );
1141
1142 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1143 assert_eq!(
1144 view.display_text(cx),
1145 "
1146 class Foo:⋯
1147
1148
1149 class Bar:⋯
1150
1151
1152 "
1153 .unindent(),
1154 );
1155
1156 view.unfold_all(&UnfoldAll, cx);
1157 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1158 assert_eq!(
1159 view.display_text(cx),
1160 "
1161 class Foo:
1162 # Hello!
1163
1164 def a():
1165 print(1)
1166
1167 def b():
1168 print(2)
1169
1170
1171 class Bar:
1172 # World!
1173
1174 def a():
1175 print(1)
1176
1177 def b():
1178 print(2)
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1186 });
1187}
1188
1189#[gpui::test]
1190fn test_move_cursor(cx: &mut TestAppContext) {
1191 init_test(cx, |_| {});
1192
1193 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1194 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1195
1196 buffer.update(cx, |buffer, cx| {
1197 buffer.edit(
1198 vec![
1199 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1200 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1201 ],
1202 None,
1203 cx,
1204 );
1205 });
1206 _ = view.update(cx, |view, cx| {
1207 assert_eq!(
1208 view.selections.display_ranges(cx),
1209 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1210 );
1211
1212 view.move_down(&MoveDown, cx);
1213 assert_eq!(
1214 view.selections.display_ranges(cx),
1215 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1216 );
1217
1218 view.move_right(&MoveRight, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1222 );
1223
1224 view.move_left(&MoveLeft, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1228 );
1229
1230 view.move_up(&MoveUp, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1234 );
1235
1236 view.move_to_end(&MoveToEnd, cx);
1237 assert_eq!(
1238 view.selections.display_ranges(cx),
1239 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1240 );
1241
1242 view.move_to_beginning(&MoveToBeginning, cx);
1243 assert_eq!(
1244 view.selections.display_ranges(cx),
1245 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1246 );
1247
1248 view.change_selections(None, cx, |s| {
1249 s.select_display_ranges([
1250 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1251 ]);
1252 });
1253 view.select_to_beginning(&SelectToBeginning, cx);
1254 assert_eq!(
1255 view.selections.display_ranges(cx),
1256 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1257 );
1258
1259 view.select_to_end(&SelectToEnd, cx);
1260 assert_eq!(
1261 view.selections.display_ranges(cx),
1262 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1263 );
1264 });
1265}
1266
1267// TODO: Re-enable this test
1268#[cfg(target_os = "macos")]
1269#[gpui::test]
1270fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1271 init_test(cx, |_| {});
1272
1273 let view = cx.add_window(|cx| {
1274 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1275 build_editor(buffer.clone(), cx)
1276 });
1277
1278 assert_eq!('ⓐ'.len_utf8(), 3);
1279 assert_eq!('α'.len_utf8(), 2);
1280
1281 _ = view.update(cx, |view, cx| {
1282 view.fold_ranges(
1283 vec![
1284 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1285 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1286 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1287 ],
1288 true,
1289 cx,
1290 );
1291 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1292
1293 view.move_right(&MoveRight, cx);
1294 assert_eq!(
1295 view.selections.display_ranges(cx),
1296 &[empty_range(0, "ⓐ".len())]
1297 );
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐⓑ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ⋯".len())]
1307 );
1308
1309 view.move_down(&MoveDown, cx);
1310 assert_eq!(
1311 view.selections.display_ranges(cx),
1312 &[empty_range(1, "ab⋯e".len())]
1313 );
1314 view.move_left(&MoveLeft, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "a".len())]
1328 );
1329
1330 view.move_down(&MoveDown, cx);
1331 assert_eq!(
1332 view.selections.display_ranges(cx),
1333 &[empty_range(2, "α".len())]
1334 );
1335 view.move_right(&MoveRight, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "αβ".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ⋯".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯ε".len())]
1349 );
1350
1351 view.move_up(&MoveUp, cx);
1352 assert_eq!(
1353 view.selections.display_ranges(cx),
1354 &[empty_range(1, "ab⋯e".len())]
1355 );
1356 view.move_down(&MoveDown, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(2, "αβ⋯ε".len())]
1360 );
1361 view.move_up(&MoveUp, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯e".len())]
1365 );
1366
1367 view.move_up(&MoveUp, cx);
1368 assert_eq!(
1369 view.selections.display_ranges(cx),
1370 &[empty_range(0, "ⓐⓑ".len())]
1371 );
1372 view.move_left(&MoveLeft, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "".len())]
1381 );
1382 });
1383}
1384
1385#[gpui::test]
1386fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1387 init_test(cx, |_| {});
1388
1389 let view = cx.add_window(|cx| {
1390 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1391 build_editor(buffer.clone(), cx)
1392 });
1393 _ = view.update(cx, |view, cx| {
1394 view.change_selections(None, cx, |s| {
1395 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1396 });
1397 view.move_down(&MoveDown, cx);
1398 assert_eq!(
1399 view.selections.display_ranges(cx),
1400 &[empty_range(1, "abcd".len())]
1401 );
1402
1403 view.move_down(&MoveDown, cx);
1404 assert_eq!(
1405 view.selections.display_ranges(cx),
1406 &[empty_range(2, "αβγ".len())]
1407 );
1408
1409 view.move_down(&MoveDown, cx);
1410 assert_eq!(
1411 view.selections.display_ranges(cx),
1412 &[empty_range(3, "abcd".len())]
1413 );
1414
1415 view.move_down(&MoveDown, cx);
1416 assert_eq!(
1417 view.selections.display_ranges(cx),
1418 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1419 );
1420
1421 view.move_up(&MoveUp, cx);
1422 assert_eq!(
1423 view.selections.display_ranges(cx),
1424 &[empty_range(3, "abcd".len())]
1425 );
1426
1427 view.move_up(&MoveUp, cx);
1428 assert_eq!(
1429 view.selections.display_ranges(cx),
1430 &[empty_range(2, "αβγ".len())]
1431 );
1432 });
1433}
1434
1435#[gpui::test]
1436fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1437 init_test(cx, |_| {});
1438 let move_to_beg = MoveToBeginningOfLine {
1439 stop_at_soft_wraps: true,
1440 };
1441
1442 let move_to_end = MoveToEndOfLine {
1443 stop_at_soft_wraps: true,
1444 };
1445
1446 let view = cx.add_window(|cx| {
1447 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1448 build_editor(buffer, cx)
1449 });
1450 _ = view.update(cx, |view, cx| {
1451 view.change_selections(None, cx, |s| {
1452 s.select_display_ranges([
1453 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1455 ]);
1456 });
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.move_to_beginning_of_line(&move_to_beg, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[
1464 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1465 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1466 ]
1467 );
1468 });
1469
1470 _ = view.update(cx, |view, cx| {
1471 view.move_to_beginning_of_line(&move_to_beg, cx);
1472 assert_eq!(
1473 view.selections.display_ranges(cx),
1474 &[
1475 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1476 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1477 ]
1478 );
1479 });
1480
1481 _ = view.update(cx, |view, cx| {
1482 view.move_to_beginning_of_line(&move_to_beg, cx);
1483 assert_eq!(
1484 view.selections.display_ranges(cx),
1485 &[
1486 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1487 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1488 ]
1489 );
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_end_of_line(&move_to_end, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1498 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1499 ]
1500 );
1501 });
1502
1503 // Moving to the end of line again is a no-op.
1504 _ = view.update(cx, |view, cx| {
1505 view.move_to_end_of_line(&move_to_end, cx);
1506 assert_eq!(
1507 view.selections.display_ranges(cx),
1508 &[
1509 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1510 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1511 ]
1512 );
1513 });
1514
1515 _ = view.update(cx, |view, cx| {
1516 view.move_left(&MoveLeft, cx);
1517 view.select_to_beginning_of_line(
1518 &SelectToBeginningOfLine {
1519 stop_at_soft_wraps: true,
1520 },
1521 cx,
1522 );
1523 assert_eq!(
1524 view.selections.display_ranges(cx),
1525 &[
1526 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1527 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1528 ]
1529 );
1530 });
1531
1532 _ = view.update(cx, |view, cx| {
1533 view.select_to_beginning_of_line(
1534 &SelectToBeginningOfLine {
1535 stop_at_soft_wraps: true,
1536 },
1537 cx,
1538 );
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1543 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.select_to_beginning_of_line(
1550 &SelectToBeginningOfLine {
1551 stop_at_soft_wraps: true,
1552 },
1553 cx,
1554 );
1555 assert_eq!(
1556 view.selections.display_ranges(cx),
1557 &[
1558 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1559 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1560 ]
1561 );
1562 });
1563
1564 _ = view.update(cx, |view, cx| {
1565 view.select_to_end_of_line(
1566 &SelectToEndOfLine {
1567 stop_at_soft_wraps: true,
1568 },
1569 cx,
1570 );
1571 assert_eq!(
1572 view.selections.display_ranges(cx),
1573 &[
1574 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1582 assert_eq!(view.display_text(cx), "ab\n de");
1583 assert_eq!(
1584 view.selections.display_ranges(cx),
1585 &[
1586 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1587 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1588 ]
1589 );
1590 });
1591
1592 _ = view.update(cx, |view, cx| {
1593 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1594 assert_eq!(view.display_text(cx), "\n");
1595 assert_eq!(
1596 view.selections.display_ranges(cx),
1597 &[
1598 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1599 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1600 ]
1601 );
1602 });
1603}
1604
1605#[gpui::test]
1606fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1607 init_test(cx, |_| {});
1608 let move_to_beg = MoveToBeginningOfLine {
1609 stop_at_soft_wraps: false,
1610 };
1611
1612 let move_to_end = MoveToEndOfLine {
1613 stop_at_soft_wraps: false,
1614 };
1615
1616 let view = cx.add_window(|cx| {
1617 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1618 build_editor(buffer, cx)
1619 });
1620
1621 _ = view.update(cx, |view, cx| {
1622 view.set_wrap_width(Some(140.0.into()), cx);
1623
1624 // We expect the following lines after wrapping
1625 // ```
1626 // thequickbrownfox
1627 // jumpedoverthelazydo
1628 // gs
1629 // ```
1630 // The final `gs` was soft-wrapped onto a new line.
1631 assert_eq!(
1632 "thequickbrownfox\njumpedoverthelaz\nydogs",
1633 view.display_text(cx),
1634 );
1635
1636 // First, let's assert behavior on the first line, that was not soft-wrapped.
1637 // Start the cursor at the `k` on the first line
1638 view.change_selections(None, cx, |s| {
1639 s.select_display_ranges([
1640 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1641 ]);
1642 });
1643
1644 // Moving to the beginning of the line should put us at the beginning of the line.
1645 view.move_to_beginning_of_line(&move_to_beg, cx);
1646 assert_eq!(
1647 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1648 view.selections.display_ranges(cx)
1649 );
1650
1651 // Moving to the end of the line should put us at the end of the line.
1652 view.move_to_end_of_line(&move_to_end, cx);
1653 assert_eq!(
1654 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1655 view.selections.display_ranges(cx)
1656 );
1657
1658 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1659 // Start the cursor at the last line (`y` that was wrapped to a new line)
1660 view.change_selections(None, cx, |s| {
1661 s.select_display_ranges([
1662 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1663 ]);
1664 });
1665
1666 // Moving to the beginning of the line should put us at the start of the second line of
1667 // display text, i.e., the `j`.
1668 view.move_to_beginning_of_line(&move_to_beg, cx);
1669 assert_eq!(
1670 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1671 view.selections.display_ranges(cx)
1672 );
1673
1674 // Moving to the beginning of the line again should be a no-op.
1675 view.move_to_beginning_of_line(&move_to_beg, cx);
1676 assert_eq!(
1677 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1678 view.selections.display_ranges(cx)
1679 );
1680
1681 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1682 // next display line.
1683 view.move_to_end_of_line(&move_to_end, cx);
1684 assert_eq!(
1685 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1686 view.selections.display_ranges(cx)
1687 );
1688
1689 // Moving to the end of the line again should be a no-op.
1690 view.move_to_end_of_line(&move_to_end, cx);
1691 assert_eq!(
1692 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1693 view.selections.display_ranges(cx)
1694 );
1695 });
1696}
1697
1698#[gpui::test]
1699fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1700 init_test(cx, |_| {});
1701
1702 let view = cx.add_window(|cx| {
1703 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1704 build_editor(buffer, cx)
1705 });
1706 _ = view.update(cx, |view, cx| {
1707 view.change_selections(None, cx, |s| {
1708 s.select_display_ranges([
1709 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1710 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1711 ])
1712 });
1713
1714 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1715 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1716
1717 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1718 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1719
1720 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1721 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1722
1723 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1724 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1725
1726 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1727 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1728
1729 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1730 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1731
1732 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1733 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1734
1735 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1736 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1737
1738 view.move_right(&MoveRight, cx);
1739 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1740 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1741
1742 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1743 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1744
1745 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1746 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1747 });
1748}
1749
1750#[gpui::test]
1751fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1752 init_test(cx, |_| {});
1753
1754 let view = cx.add_window(|cx| {
1755 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1756 build_editor(buffer, cx)
1757 });
1758
1759 _ = view.update(cx, |view, cx| {
1760 view.set_wrap_width(Some(140.0.into()), cx);
1761 assert_eq!(
1762 view.display_text(cx),
1763 "use one::{\n two::three::\n four::five\n};"
1764 );
1765
1766 view.change_selections(None, cx, |s| {
1767 s.select_display_ranges([
1768 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1769 ]);
1770 });
1771
1772 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1773 assert_eq!(
1774 view.selections.display_ranges(cx),
1775 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1776 );
1777
1778 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1779 assert_eq!(
1780 view.selections.display_ranges(cx),
1781 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1782 );
1783
1784 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1785 assert_eq!(
1786 view.selections.display_ranges(cx),
1787 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1788 );
1789
1790 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1791 assert_eq!(
1792 view.selections.display_ranges(cx),
1793 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1794 );
1795
1796 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1797 assert_eq!(
1798 view.selections.display_ranges(cx),
1799 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1800 );
1801
1802 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1803 assert_eq!(
1804 view.selections.display_ranges(cx),
1805 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1806 );
1807 });
1808}
1809
1810#[gpui::test]
1811async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814
1815 let line_height = cx.editor(|editor, cx| {
1816 editor
1817 .style()
1818 .unwrap()
1819 .text
1820 .line_height_in_pixels(cx.rem_size())
1821 });
1822 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827
1828 three
1829 fourˇ
1830 five
1831
1832 six"#
1833 .unindent(),
1834 );
1835
1836 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1837 cx.assert_editor_state(
1838 &r#"one
1839 two
1840 ˇ
1841 three
1842 four
1843 five
1844 ˇ
1845 six"#
1846 .unindent(),
1847 );
1848
1849 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1850 cx.assert_editor_state(
1851 &r#"one
1852 two
1853
1854 three
1855 four
1856 five
1857 ˇ
1858 sixˇ"#
1859 .unindent(),
1860 );
1861
1862 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1863 cx.assert_editor_state(
1864 &r#"one
1865 two
1866
1867 three
1868 four
1869 five
1870
1871 sixˇ"#
1872 .unindent(),
1873 );
1874
1875 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1876 cx.assert_editor_state(
1877 &r#"one
1878 two
1879
1880 three
1881 four
1882 five
1883 ˇ
1884 six"#
1885 .unindent(),
1886 );
1887
1888 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1889 cx.assert_editor_state(
1890 &r#"one
1891 two
1892 ˇ
1893 three
1894 four
1895 five
1896
1897 six"#
1898 .unindent(),
1899 );
1900
1901 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1902 cx.assert_editor_state(
1903 &r#"ˇone
1904 two
1905
1906 three
1907 four
1908 five
1909
1910 six"#
1911 .unindent(),
1912 );
1913}
1914
1915#[gpui::test]
1916async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1917 init_test(cx, |_| {});
1918 let mut cx = EditorTestContext::new(cx).await;
1919 let line_height = cx.editor(|editor, cx| {
1920 editor
1921 .style()
1922 .unwrap()
1923 .text
1924 .line_height_in_pixels(cx.rem_size())
1925 });
1926 let window = cx.window;
1927 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1928
1929 cx.set_state(
1930 r#"ˇone
1931 two
1932 three
1933 four
1934 five
1935 six
1936 seven
1937 eight
1938 nine
1939 ten
1940 "#,
1941 );
1942
1943 cx.update_editor(|editor, cx| {
1944 assert_eq!(
1945 editor.snapshot(cx).scroll_position(),
1946 gpui::Point::new(0., 0.)
1947 );
1948 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1949 assert_eq!(
1950 editor.snapshot(cx).scroll_position(),
1951 gpui::Point::new(0., 3.)
1952 );
1953 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1954 assert_eq!(
1955 editor.snapshot(cx).scroll_position(),
1956 gpui::Point::new(0., 6.)
1957 );
1958 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1959 assert_eq!(
1960 editor.snapshot(cx).scroll_position(),
1961 gpui::Point::new(0., 3.)
1962 );
1963
1964 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1965 assert_eq!(
1966 editor.snapshot(cx).scroll_position(),
1967 gpui::Point::new(0., 1.)
1968 );
1969 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1970 assert_eq!(
1971 editor.snapshot(cx).scroll_position(),
1972 gpui::Point::new(0., 3.)
1973 );
1974 });
1975}
1976
1977#[gpui::test]
1978async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1979 init_test(cx, |_| {});
1980 let mut cx = EditorTestContext::new(cx).await;
1981
1982 let line_height = cx.update_editor(|editor, cx| {
1983 editor.set_vertical_scroll_margin(2, cx);
1984 editor
1985 .style()
1986 .unwrap()
1987 .text
1988 .line_height_in_pixels(cx.rem_size())
1989 });
1990 let window = cx.window;
1991 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1992
1993 cx.set_state(
1994 r#"ˇone
1995 two
1996 three
1997 four
1998 five
1999 six
2000 seven
2001 eight
2002 nine
2003 ten
2004 "#,
2005 );
2006 cx.update_editor(|editor, cx| {
2007 assert_eq!(
2008 editor.snapshot(cx).scroll_position(),
2009 gpui::Point::new(0., 0.0)
2010 );
2011 });
2012
2013 // Add a cursor below the visible area. Since both cursors cannot fit
2014 // on screen, the editor autoscrolls to reveal the newest cursor, and
2015 // allows the vertical scroll margin below that cursor.
2016 cx.update_editor(|editor, cx| {
2017 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2018 selections.select_ranges([
2019 Point::new(0, 0)..Point::new(0, 0),
2020 Point::new(6, 0)..Point::new(6, 0),
2021 ]);
2022 })
2023 });
2024 cx.update_editor(|editor, cx| {
2025 assert_eq!(
2026 editor.snapshot(cx).scroll_position(),
2027 gpui::Point::new(0., 3.0)
2028 );
2029 });
2030
2031 // Move down. The editor cursor scrolls down to track the newest cursor.
2032 cx.update_editor(|editor, cx| {
2033 editor.move_down(&Default::default(), cx);
2034 });
2035 cx.update_editor(|editor, cx| {
2036 assert_eq!(
2037 editor.snapshot(cx).scroll_position(),
2038 gpui::Point::new(0., 4.0)
2039 );
2040 });
2041
2042 // Add a cursor above the visible area. Since both cursors fit on screen,
2043 // the editor scrolls to show both.
2044 cx.update_editor(|editor, cx| {
2045 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2046 selections.select_ranges([
2047 Point::new(1, 0)..Point::new(1, 0),
2048 Point::new(6, 0)..Point::new(6, 0),
2049 ]);
2050 })
2051 });
2052 cx.update_editor(|editor, cx| {
2053 assert_eq!(
2054 editor.snapshot(cx).scroll_position(),
2055 gpui::Point::new(0., 1.0)
2056 );
2057 });
2058}
2059
2060#[gpui::test]
2061async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2062 init_test(cx, |_| {});
2063 let mut cx = EditorTestContext::new(cx).await;
2064
2065 let line_height = cx.editor(|editor, cx| {
2066 editor
2067 .style()
2068 .unwrap()
2069 .text
2070 .line_height_in_pixels(cx.rem_size())
2071 });
2072 let window = cx.window;
2073 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2074 cx.set_state(
2075 &r#"
2076 ˇone
2077 two
2078 threeˇ
2079 four
2080 five
2081 six
2082 seven
2083 eight
2084 nine
2085 ten
2086 "#
2087 .unindent(),
2088 );
2089
2090 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2091 cx.assert_editor_state(
2092 &r#"
2093 one
2094 two
2095 three
2096 ˇfour
2097 five
2098 sixˇ
2099 seven
2100 eight
2101 nine
2102 ten
2103 "#
2104 .unindent(),
2105 );
2106
2107 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2108 cx.assert_editor_state(
2109 &r#"
2110 one
2111 two
2112 three
2113 four
2114 five
2115 six
2116 ˇseven
2117 eight
2118 nineˇ
2119 ten
2120 "#
2121 .unindent(),
2122 );
2123
2124 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2125 cx.assert_editor_state(
2126 &r#"
2127 one
2128 two
2129 three
2130 ˇfour
2131 five
2132 sixˇ
2133 seven
2134 eight
2135 nine
2136 ten
2137 "#
2138 .unindent(),
2139 );
2140
2141 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2142 cx.assert_editor_state(
2143 &r#"
2144 ˇone
2145 two
2146 threeˇ
2147 four
2148 five
2149 six
2150 seven
2151 eight
2152 nine
2153 ten
2154 "#
2155 .unindent(),
2156 );
2157
2158 // Test select collapsing
2159 cx.update_editor(|editor, cx| {
2160 editor.move_page_down(&MovePageDown::default(), cx);
2161 editor.move_page_down(&MovePageDown::default(), cx);
2162 editor.move_page_down(&MovePageDown::default(), cx);
2163 });
2164 cx.assert_editor_state(
2165 &r#"
2166 one
2167 two
2168 three
2169 four
2170 five
2171 six
2172 seven
2173 eight
2174 nine
2175 ˇten
2176 ˇ"#
2177 .unindent(),
2178 );
2179}
2180
2181#[gpui::test]
2182async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2183 init_test(cx, |_| {});
2184 let mut cx = EditorTestContext::new(cx).await;
2185 cx.set_state("one «two threeˇ» four");
2186 cx.update_editor(|editor, cx| {
2187 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2188 assert_eq!(editor.text(cx), " four");
2189 });
2190}
2191
2192#[gpui::test]
2193fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2194 init_test(cx, |_| {});
2195
2196 let view = cx.add_window(|cx| {
2197 let buffer = MultiBuffer::build_simple("one two three four", cx);
2198 build_editor(buffer.clone(), cx)
2199 });
2200
2201 _ = view.update(cx, |view, cx| {
2202 view.change_selections(None, cx, |s| {
2203 s.select_display_ranges([
2204 // an empty selection - the preceding word fragment is deleted
2205 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2206 // characters selected - they are deleted
2207 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2208 ])
2209 });
2210 view.delete_to_previous_word_start(
2211 &DeleteToPreviousWordStart {
2212 ignore_newlines: false,
2213 },
2214 cx,
2215 );
2216 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2217 });
2218
2219 _ = view.update(cx, |view, cx| {
2220 view.change_selections(None, cx, |s| {
2221 s.select_display_ranges([
2222 // an empty selection - the following word fragment is deleted
2223 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2224 // characters selected - they are deleted
2225 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2226 ])
2227 });
2228 view.delete_to_next_word_end(
2229 &DeleteToNextWordEnd {
2230 ignore_newlines: false,
2231 },
2232 cx,
2233 );
2234 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2235 });
2236}
2237
2238#[gpui::test]
2239fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2240 init_test(cx, |_| {});
2241
2242 let view = cx.add_window(|cx| {
2243 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2244 build_editor(buffer.clone(), cx)
2245 });
2246 let del_to_prev_word_start = DeleteToPreviousWordStart {
2247 ignore_newlines: false,
2248 };
2249 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2250 ignore_newlines: true,
2251 };
2252
2253 _ = view.update(cx, |view, cx| {
2254 view.change_selections(None, cx, |s| {
2255 s.select_display_ranges([
2256 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2257 ])
2258 });
2259 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2260 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2261 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2262 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2263 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2264 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2265 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2267 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2268 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2269 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_next_word_end = DeleteToNextWordEnd {
2283 ignore_newlines: false,
2284 };
2285 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2293 ])
2294 });
2295 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2296 assert_eq!(
2297 view.buffer.read(cx).read(cx).text(),
2298 "one\n two\nthree\n four"
2299 );
2300 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2301 assert_eq!(
2302 view.buffer.read(cx).read(cx).text(),
2303 "\n two\nthree\n four"
2304 );
2305 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2307 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2308 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2309 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2310 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2311 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2312 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2313 });
2314}
2315
2316#[gpui::test]
2317fn test_newline(cx: &mut TestAppContext) {
2318 init_test(cx, |_| {});
2319
2320 let view = cx.add_window(|cx| {
2321 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2322 build_editor(buffer.clone(), cx)
2323 });
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2329 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2330 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2331 ])
2332 });
2333
2334 view.newline(&Newline, cx);
2335 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2336 });
2337}
2338
2339#[gpui::test]
2340fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2341 init_test(cx, |_| {});
2342
2343 let editor = cx.add_window(|cx| {
2344 let buffer = MultiBuffer::build_simple(
2345 "
2346 a
2347 b(
2348 X
2349 )
2350 c(
2351 X
2352 )
2353 "
2354 .unindent()
2355 .as_str(),
2356 cx,
2357 );
2358 let mut editor = build_editor(buffer.clone(), cx);
2359 editor.change_selections(None, cx, |s| {
2360 s.select_ranges([
2361 Point::new(2, 4)..Point::new(2, 5),
2362 Point::new(5, 4)..Point::new(5, 5),
2363 ])
2364 });
2365 editor
2366 });
2367
2368 _ = editor.update(cx, |editor, cx| {
2369 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2370 editor.buffer.update(cx, |buffer, cx| {
2371 buffer.edit(
2372 [
2373 (Point::new(1, 2)..Point::new(3, 0), ""),
2374 (Point::new(4, 2)..Point::new(6, 0), ""),
2375 ],
2376 None,
2377 cx,
2378 );
2379 assert_eq!(
2380 buffer.read(cx).text(),
2381 "
2382 a
2383 b()
2384 c()
2385 "
2386 .unindent()
2387 );
2388 });
2389 assert_eq!(
2390 editor.selections.ranges(cx),
2391 &[
2392 Point::new(1, 2)..Point::new(1, 2),
2393 Point::new(2, 2)..Point::new(2, 2),
2394 ],
2395 );
2396
2397 editor.newline(&Newline, cx);
2398 assert_eq!(
2399 editor.text(cx),
2400 "
2401 a
2402 b(
2403 )
2404 c(
2405 )
2406 "
2407 .unindent()
2408 );
2409
2410 // The selections are moved after the inserted newlines
2411 assert_eq!(
2412 editor.selections.ranges(cx),
2413 &[
2414 Point::new(2, 0)..Point::new(2, 0),
2415 Point::new(4, 0)..Point::new(4, 0),
2416 ],
2417 );
2418 });
2419}
2420
2421#[gpui::test]
2422async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2423 init_test(cx, |settings| {
2424 settings.defaults.tab_size = NonZeroU32::new(4)
2425 });
2426
2427 let language = Arc::new(
2428 Language::new(
2429 LanguageConfig::default(),
2430 Some(tree_sitter_rust::LANGUAGE.into()),
2431 )
2432 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2433 .unwrap(),
2434 );
2435
2436 let mut cx = EditorTestContext::new(cx).await;
2437 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2438 cx.set_state(indoc! {"
2439 const a: ˇA = (
2440 (ˇ
2441 «const_functionˇ»(ˇ),
2442 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2443 )ˇ
2444 ˇ);ˇ
2445 "});
2446
2447 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2448 cx.assert_editor_state(indoc! {"
2449 ˇ
2450 const a: A = (
2451 ˇ
2452 (
2453 ˇ
2454 ˇ
2455 const_function(),
2456 ˇ
2457 ˇ
2458 ˇ
2459 ˇ
2460 something_else,
2461 ˇ
2462 )
2463 ˇ
2464 ˇ
2465 );
2466 "});
2467}
2468
2469#[gpui::test]
2470async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2471 init_test(cx, |settings| {
2472 settings.defaults.tab_size = NonZeroU32::new(4)
2473 });
2474
2475 let language = Arc::new(
2476 Language::new(
2477 LanguageConfig::default(),
2478 Some(tree_sitter_rust::LANGUAGE.into()),
2479 )
2480 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2481 .unwrap(),
2482 );
2483
2484 let mut cx = EditorTestContext::new(cx).await;
2485 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2486 cx.set_state(indoc! {"
2487 const a: ˇA = (
2488 (ˇ
2489 «const_functionˇ»(ˇ),
2490 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2491 )ˇ
2492 ˇ);ˇ
2493 "});
2494
2495 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2496 cx.assert_editor_state(indoc! {"
2497 const a: A = (
2498 ˇ
2499 (
2500 ˇ
2501 const_function(),
2502 ˇ
2503 ˇ
2504 something_else,
2505 ˇ
2506 ˇ
2507 ˇ
2508 ˇ
2509 )
2510 ˇ
2511 );
2512 ˇ
2513 ˇ
2514 "});
2515}
2516
2517#[gpui::test]
2518async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2519 init_test(cx, |settings| {
2520 settings.defaults.tab_size = NonZeroU32::new(4)
2521 });
2522
2523 let language = Arc::new(Language::new(
2524 LanguageConfig {
2525 line_comments: vec!["//".into()],
2526 ..LanguageConfig::default()
2527 },
2528 None,
2529 ));
2530 {
2531 let mut cx = EditorTestContext::new(cx).await;
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533 cx.set_state(indoc! {"
2534 // Fooˇ
2535 "});
2536
2537 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2538 cx.assert_editor_state(indoc! {"
2539 // Foo
2540 //ˇ
2541 "});
2542 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2543 cx.set_state(indoc! {"
2544 ˇ// Foo
2545 "});
2546 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2547 cx.assert_editor_state(indoc! {"
2548
2549 ˇ// Foo
2550 "});
2551 }
2552 // Ensure that comment continuations can be disabled.
2553 update_test_language_settings(cx, |settings| {
2554 settings.defaults.extend_comment_on_newline = Some(false);
2555 });
2556 let mut cx = EditorTestContext::new(cx).await;
2557 cx.set_state(indoc! {"
2558 // Fooˇ
2559 "});
2560 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2561 cx.assert_editor_state(indoc! {"
2562 // Foo
2563 ˇ
2564 "});
2565}
2566
2567#[gpui::test]
2568fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|cx| {
2572 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2573 let mut editor = build_editor(buffer.clone(), cx);
2574 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2575 editor
2576 });
2577
2578 _ = editor.update(cx, |editor, cx| {
2579 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2580 editor.buffer.update(cx, |buffer, cx| {
2581 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2582 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2583 });
2584 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2585
2586 editor.insert("Z", cx);
2587 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2588
2589 // The selections are moved after the inserted characters
2590 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2591 });
2592}
2593
2594#[gpui::test]
2595async fn test_tab(cx: &mut gpui::TestAppContext) {
2596 init_test(cx, |settings| {
2597 settings.defaults.tab_size = NonZeroU32::new(3)
2598 });
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.set_state(indoc! {"
2602 ˇabˇc
2603 ˇ🏀ˇ🏀ˇefg
2604 dˇ
2605 "});
2606 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2607 cx.assert_editor_state(indoc! {"
2608 ˇab ˇc
2609 ˇ🏀 ˇ🏀 ˇefg
2610 d ˇ
2611 "});
2612
2613 cx.set_state(indoc! {"
2614 a
2615 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2616 "});
2617 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2618 cx.assert_editor_state(indoc! {"
2619 a
2620 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2621 "});
2622}
2623
2624#[gpui::test]
2625async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2626 init_test(cx, |_| {});
2627
2628 let mut cx = EditorTestContext::new(cx).await;
2629 let language = Arc::new(
2630 Language::new(
2631 LanguageConfig::default(),
2632 Some(tree_sitter_rust::LANGUAGE.into()),
2633 )
2634 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2635 .unwrap(),
2636 );
2637 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2638
2639 // cursors that are already at the suggested indent level insert
2640 // a soft tab. cursors that are to the left of the suggested indent
2641 // auto-indent their line.
2642 cx.set_state(indoc! {"
2643 ˇ
2644 const a: B = (
2645 c(
2646 d(
2647 ˇ
2648 )
2649 ˇ
2650 ˇ )
2651 );
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 ˇ
2656 const a: B = (
2657 c(
2658 d(
2659 ˇ
2660 )
2661 ˇ
2662 ˇ)
2663 );
2664 "});
2665
2666 // handle auto-indent when there are multiple cursors on the same line
2667 cx.set_state(indoc! {"
2668 const a: B = (
2669 c(
2670 ˇ ˇ
2671 ˇ )
2672 );
2673 "});
2674 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2675 cx.assert_editor_state(indoc! {"
2676 const a: B = (
2677 c(
2678 ˇ
2679 ˇ)
2680 );
2681 "});
2682}
2683
2684#[gpui::test]
2685async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2686 init_test(cx, |settings| {
2687 settings.defaults.tab_size = NonZeroU32::new(4)
2688 });
2689
2690 let language = Arc::new(
2691 Language::new(
2692 LanguageConfig::default(),
2693 Some(tree_sitter_rust::LANGUAGE.into()),
2694 )
2695 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2696 .unwrap(),
2697 );
2698
2699 let mut cx = EditorTestContext::new(cx).await;
2700 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2701 cx.set_state(indoc! {"
2702 fn a() {
2703 if b {
2704 \t ˇc
2705 }
2706 }
2707 "});
2708
2709 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2710 cx.assert_editor_state(indoc! {"
2711 fn a() {
2712 if b {
2713 ˇc
2714 }
2715 }
2716 "});
2717}
2718
2719#[gpui::test]
2720async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2721 init_test(cx, |settings| {
2722 settings.defaults.tab_size = NonZeroU32::new(4);
2723 });
2724
2725 let mut cx = EditorTestContext::new(cx).await;
2726
2727 cx.set_state(indoc! {"
2728 «oneˇ» «twoˇ»
2729 three
2730 four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 «oneˇ» «twoˇ»
2735 three
2736 four
2737 "});
2738
2739 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2740 cx.assert_editor_state(indoc! {"
2741 «oneˇ» «twoˇ»
2742 three
2743 four
2744 "});
2745
2746 // select across line ending
2747 cx.set_state(indoc! {"
2748 one two
2749 t«hree
2750 ˇ» four
2751 "});
2752 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2753 cx.assert_editor_state(indoc! {"
2754 one two
2755 t«hree
2756 ˇ» four
2757 "});
2758
2759 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2760 cx.assert_editor_state(indoc! {"
2761 one two
2762 t«hree
2763 ˇ» four
2764 "});
2765
2766 // Ensure that indenting/outdenting works when the cursor is at column 0.
2767 cx.set_state(indoc! {"
2768 one two
2769 ˇthree
2770 four
2771 "});
2772 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2773 cx.assert_editor_state(indoc! {"
2774 one two
2775 ˇthree
2776 four
2777 "});
2778
2779 cx.set_state(indoc! {"
2780 one two
2781 ˇ three
2782 four
2783 "});
2784 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 ˇthree
2788 four
2789 "});
2790}
2791
2792#[gpui::test]
2793async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2794 init_test(cx, |settings| {
2795 settings.defaults.hard_tabs = Some(true);
2796 });
2797
2798 let mut cx = EditorTestContext::new(cx).await;
2799
2800 // select two ranges on one line
2801 cx.set_state(indoc! {"
2802 «oneˇ» «twoˇ»
2803 three
2804 four
2805 "});
2806 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2807 cx.assert_editor_state(indoc! {"
2808 \t«oneˇ» «twoˇ»
2809 three
2810 four
2811 "});
2812 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2813 cx.assert_editor_state(indoc! {"
2814 \t\t«oneˇ» «twoˇ»
2815 three
2816 four
2817 "});
2818 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2819 cx.assert_editor_state(indoc! {"
2820 \t«oneˇ» «twoˇ»
2821 three
2822 four
2823 "});
2824 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2825 cx.assert_editor_state(indoc! {"
2826 «oneˇ» «twoˇ»
2827 three
2828 four
2829 "});
2830
2831 // select across a line ending
2832 cx.set_state(indoc! {"
2833 one two
2834 t«hree
2835 ˇ»four
2836 "});
2837 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2838 cx.assert_editor_state(indoc! {"
2839 one two
2840 \tt«hree
2841 ˇ»four
2842 "});
2843 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2844 cx.assert_editor_state(indoc! {"
2845 one two
2846 \t\tt«hree
2847 ˇ»four
2848 "});
2849 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2850 cx.assert_editor_state(indoc! {"
2851 one two
2852 \tt«hree
2853 ˇ»four
2854 "});
2855 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2856 cx.assert_editor_state(indoc! {"
2857 one two
2858 t«hree
2859 ˇ»four
2860 "});
2861
2862 // Ensure that indenting/outdenting works when the cursor is at column 0.
2863 cx.set_state(indoc! {"
2864 one two
2865 ˇthree
2866 four
2867 "});
2868 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2869 cx.assert_editor_state(indoc! {"
2870 one two
2871 ˇthree
2872 four
2873 "});
2874 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2875 cx.assert_editor_state(indoc! {"
2876 one two
2877 \tˇthree
2878 four
2879 "});
2880 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2881 cx.assert_editor_state(indoc! {"
2882 one two
2883 ˇthree
2884 four
2885 "});
2886}
2887
2888#[gpui::test]
2889fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2890 init_test(cx, |settings| {
2891 settings.languages.extend([
2892 (
2893 "TOML".into(),
2894 LanguageSettingsContent {
2895 tab_size: NonZeroU32::new(2),
2896 ..Default::default()
2897 },
2898 ),
2899 (
2900 "Rust".into(),
2901 LanguageSettingsContent {
2902 tab_size: NonZeroU32::new(4),
2903 ..Default::default()
2904 },
2905 ),
2906 ]);
2907 });
2908
2909 let toml_language = Arc::new(Language::new(
2910 LanguageConfig {
2911 name: "TOML".into(),
2912 ..Default::default()
2913 },
2914 None,
2915 ));
2916 let rust_language = Arc::new(Language::new(
2917 LanguageConfig {
2918 name: "Rust".into(),
2919 ..Default::default()
2920 },
2921 None,
2922 ));
2923
2924 let toml_buffer =
2925 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2926 let rust_buffer = cx.new_model(|cx| {
2927 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2928 });
2929 let multibuffer = cx.new_model(|cx| {
2930 let mut multibuffer = MultiBuffer::new(ReadWrite);
2931 multibuffer.push_excerpts(
2932 toml_buffer.clone(),
2933 [ExcerptRange {
2934 context: Point::new(0, 0)..Point::new(2, 0),
2935 primary: None,
2936 }],
2937 cx,
2938 );
2939 multibuffer.push_excerpts(
2940 rust_buffer.clone(),
2941 [ExcerptRange {
2942 context: Point::new(0, 0)..Point::new(1, 0),
2943 primary: None,
2944 }],
2945 cx,
2946 );
2947 multibuffer
2948 });
2949
2950 cx.add_window(|cx| {
2951 let mut editor = build_editor(multibuffer, cx);
2952
2953 assert_eq!(
2954 editor.text(cx),
2955 indoc! {"
2956 a = 1
2957 b = 2
2958
2959 const c: usize = 3;
2960 "}
2961 );
2962
2963 select_ranges(
2964 &mut editor,
2965 indoc! {"
2966 «aˇ» = 1
2967 b = 2
2968
2969 «const c:ˇ» usize = 3;
2970 "},
2971 cx,
2972 );
2973
2974 editor.tab(&Tab, cx);
2975 assert_text_with_selections(
2976 &mut editor,
2977 indoc! {"
2978 «aˇ» = 1
2979 b = 2
2980
2981 «const c:ˇ» usize = 3;
2982 "},
2983 cx,
2984 );
2985 editor.tab_prev(&TabPrev, cx);
2986 assert_text_with_selections(
2987 &mut editor,
2988 indoc! {"
2989 «aˇ» = 1
2990 b = 2
2991
2992 «const c:ˇ» usize = 3;
2993 "},
2994 cx,
2995 );
2996
2997 editor
2998 });
2999}
3000
3001#[gpui::test]
3002async fn test_backspace(cx: &mut gpui::TestAppContext) {
3003 init_test(cx, |_| {});
3004
3005 let mut cx = EditorTestContext::new(cx).await;
3006
3007 // Basic backspace
3008 cx.set_state(indoc! {"
3009 onˇe two three
3010 fou«rˇ» five six
3011 seven «ˇeight nine
3012 »ten
3013 "});
3014 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3015 cx.assert_editor_state(indoc! {"
3016 oˇe two three
3017 fouˇ five six
3018 seven ˇten
3019 "});
3020
3021 // Test backspace inside and around indents
3022 cx.set_state(indoc! {"
3023 zero
3024 ˇone
3025 ˇtwo
3026 ˇ ˇ ˇ three
3027 ˇ ˇ four
3028 "});
3029 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3030 cx.assert_editor_state(indoc! {"
3031 zero
3032 ˇone
3033 ˇtwo
3034 ˇ threeˇ four
3035 "});
3036
3037 // Test backspace with line_mode set to true
3038 cx.update_editor(|e, _| e.selections.line_mode = true);
3039 cx.set_state(indoc! {"
3040 The ˇquick ˇbrown
3041 fox jumps over
3042 the lazy dog
3043 ˇThe qu«ick bˇ»rown"});
3044 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3045 cx.assert_editor_state(indoc! {"
3046 ˇfox jumps over
3047 the lazy dogˇ"});
3048}
3049
3050#[gpui::test]
3051async fn test_delete(cx: &mut gpui::TestAppContext) {
3052 init_test(cx, |_| {});
3053
3054 let mut cx = EditorTestContext::new(cx).await;
3055 cx.set_state(indoc! {"
3056 onˇe two three
3057 fou«rˇ» five six
3058 seven «ˇeight nine
3059 »ten
3060 "});
3061 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3062 cx.assert_editor_state(indoc! {"
3063 onˇ two three
3064 fouˇ five six
3065 seven ˇten
3066 "});
3067
3068 // Test backspace with line_mode set to true
3069 cx.update_editor(|e, _| e.selections.line_mode = true);
3070 cx.set_state(indoc! {"
3071 The ˇquick ˇbrown
3072 fox «ˇjum»ps over
3073 the lazy dog
3074 ˇThe qu«ick bˇ»rown"});
3075 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3076 cx.assert_editor_state("ˇthe lazy dogˇ");
3077}
3078
3079#[gpui::test]
3080fn test_delete_line(cx: &mut TestAppContext) {
3081 init_test(cx, |_| {});
3082
3083 let view = cx.add_window(|cx| {
3084 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3085 build_editor(buffer, cx)
3086 });
3087 _ = view.update(cx, |view, cx| {
3088 view.change_selections(None, cx, |s| {
3089 s.select_display_ranges([
3090 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3091 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3092 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3093 ])
3094 });
3095 view.delete_line(&DeleteLine, cx);
3096 assert_eq!(view.display_text(cx), "ghi");
3097 assert_eq!(
3098 view.selections.display_ranges(cx),
3099 vec![
3100 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3101 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3102 ]
3103 );
3104 });
3105
3106 let view = cx.add_window(|cx| {
3107 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3108 build_editor(buffer, cx)
3109 });
3110 _ = view.update(cx, |view, cx| {
3111 view.change_selections(None, cx, |s| {
3112 s.select_display_ranges([
3113 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3114 ])
3115 });
3116 view.delete_line(&DeleteLine, cx);
3117 assert_eq!(view.display_text(cx), "ghi\n");
3118 assert_eq!(
3119 view.selections.display_ranges(cx),
3120 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3121 );
3122 });
3123}
3124
3125#[gpui::test]
3126fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3127 init_test(cx, |_| {});
3128
3129 cx.add_window(|cx| {
3130 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3131 let mut editor = build_editor(buffer.clone(), cx);
3132 let buffer = buffer.read(cx).as_singleton().unwrap();
3133
3134 assert_eq!(
3135 editor.selections.ranges::<Point>(cx),
3136 &[Point::new(0, 0)..Point::new(0, 0)]
3137 );
3138
3139 // When on single line, replace newline at end by space
3140 editor.join_lines(&JoinLines, cx);
3141 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3142 assert_eq!(
3143 editor.selections.ranges::<Point>(cx),
3144 &[Point::new(0, 3)..Point::new(0, 3)]
3145 );
3146
3147 // When multiple lines are selected, remove newlines that are spanned by the selection
3148 editor.change_selections(None, cx, |s| {
3149 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3150 });
3151 editor.join_lines(&JoinLines, cx);
3152 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3153 assert_eq!(
3154 editor.selections.ranges::<Point>(cx),
3155 &[Point::new(0, 11)..Point::new(0, 11)]
3156 );
3157
3158 // Undo should be transactional
3159 editor.undo(&Undo, cx);
3160 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3161 assert_eq!(
3162 editor.selections.ranges::<Point>(cx),
3163 &[Point::new(0, 5)..Point::new(2, 2)]
3164 );
3165
3166 // When joining an empty line don't insert a space
3167 editor.change_selections(None, cx, |s| {
3168 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3169 });
3170 editor.join_lines(&JoinLines, cx);
3171 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3172 assert_eq!(
3173 editor.selections.ranges::<Point>(cx),
3174 [Point::new(2, 3)..Point::new(2, 3)]
3175 );
3176
3177 // We can remove trailing newlines
3178 editor.join_lines(&JoinLines, cx);
3179 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3180 assert_eq!(
3181 editor.selections.ranges::<Point>(cx),
3182 [Point::new(2, 3)..Point::new(2, 3)]
3183 );
3184
3185 // We don't blow up on the last line
3186 editor.join_lines(&JoinLines, cx);
3187 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3188 assert_eq!(
3189 editor.selections.ranges::<Point>(cx),
3190 [Point::new(2, 3)..Point::new(2, 3)]
3191 );
3192
3193 // reset to test indentation
3194 editor.buffer.update(cx, |buffer, cx| {
3195 buffer.edit(
3196 [
3197 (Point::new(1, 0)..Point::new(1, 2), " "),
3198 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3199 ],
3200 None,
3201 cx,
3202 )
3203 });
3204
3205 // We remove any leading spaces
3206 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3207 editor.change_selections(None, cx, |s| {
3208 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3209 });
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3212
3213 // We don't insert a space for a line containing only spaces
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3216
3217 // We ignore any leading tabs
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3220
3221 editor
3222 });
3223}
3224
3225#[gpui::test]
3226fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3227 init_test(cx, |_| {});
3228
3229 cx.add_window(|cx| {
3230 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3231 let mut editor = build_editor(buffer.clone(), cx);
3232 let buffer = buffer.read(cx).as_singleton().unwrap();
3233
3234 editor.change_selections(None, cx, |s| {
3235 s.select_ranges([
3236 Point::new(0, 2)..Point::new(1, 1),
3237 Point::new(1, 2)..Point::new(1, 2),
3238 Point::new(3, 1)..Point::new(3, 2),
3239 ])
3240 });
3241
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3244
3245 assert_eq!(
3246 editor.selections.ranges::<Point>(cx),
3247 [
3248 Point::new(0, 7)..Point::new(0, 7),
3249 Point::new(1, 3)..Point::new(1, 3)
3250 ]
3251 );
3252 editor
3253 });
3254}
3255
3256#[gpui::test]
3257async fn test_join_lines_with_git_diff_base(
3258 executor: BackgroundExecutor,
3259 cx: &mut gpui::TestAppContext,
3260) {
3261 init_test(cx, |_| {});
3262
3263 let mut cx = EditorTestContext::new(cx).await;
3264
3265 let diff_base = r#"
3266 Line 0
3267 Line 1
3268 Line 2
3269 Line 3
3270 "#
3271 .unindent();
3272
3273 cx.set_state(
3274 &r#"
3275 ˇLine 0
3276 Line 1
3277 Line 2
3278 Line 3
3279 "#
3280 .unindent(),
3281 );
3282
3283 cx.set_diff_base(Some(&diff_base));
3284 executor.run_until_parked();
3285
3286 // Join lines
3287 cx.update_editor(|editor, cx| {
3288 editor.join_lines(&JoinLines, cx);
3289 });
3290 executor.run_until_parked();
3291
3292 cx.assert_editor_state(
3293 &r#"
3294 Line 0ˇ Line 1
3295 Line 2
3296 Line 3
3297 "#
3298 .unindent(),
3299 );
3300 // Join again
3301 cx.update_editor(|editor, cx| {
3302 editor.join_lines(&JoinLines, cx);
3303 });
3304 executor.run_until_parked();
3305
3306 cx.assert_editor_state(
3307 &r#"
3308 Line 0 Line 1ˇ Line 2
3309 Line 3
3310 "#
3311 .unindent(),
3312 );
3313}
3314
3315#[gpui::test]
3316async fn test_custom_newlines_cause_no_false_positive_diffs(
3317 executor: BackgroundExecutor,
3318 cx: &mut gpui::TestAppContext,
3319) {
3320 init_test(cx, |_| {});
3321 let mut cx = EditorTestContext::new(cx).await;
3322 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3323 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3324 executor.run_until_parked();
3325
3326 cx.update_editor(|editor, cx| {
3327 assert_eq!(
3328 editor
3329 .buffer()
3330 .read(cx)
3331 .snapshot(cx)
3332 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3333 .collect::<Vec<_>>(),
3334 Vec::new(),
3335 "Should not have any diffs for files with custom newlines"
3336 );
3337 });
3338}
3339
3340#[gpui::test]
3341async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 // Test sort_lines_case_insensitive()
3347 cx.set_state(indoc! {"
3348 «z
3349 y
3350 x
3351 Z
3352 Y
3353 Xˇ»
3354 "});
3355 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3356 cx.assert_editor_state(indoc! {"
3357 «x
3358 X
3359 y
3360 Y
3361 z
3362 Zˇ»
3363 "});
3364
3365 // Test reverse_lines()
3366 cx.set_state(indoc! {"
3367 «5
3368 4
3369 3
3370 2
3371 1ˇ»
3372 "});
3373 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3374 cx.assert_editor_state(indoc! {"
3375 «1
3376 2
3377 3
3378 4
3379 5ˇ»
3380 "});
3381
3382 // Skip testing shuffle_line()
3383
3384 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3385 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3386
3387 // Don't manipulate when cursor is on single line, but expand the selection
3388 cx.set_state(indoc! {"
3389 ddˇdd
3390 ccc
3391 bb
3392 a
3393 "});
3394 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3395 cx.assert_editor_state(indoc! {"
3396 «ddddˇ»
3397 ccc
3398 bb
3399 a
3400 "});
3401
3402 // Basic manipulate case
3403 // Start selection moves to column 0
3404 // End of selection shrinks to fit shorter line
3405 cx.set_state(indoc! {"
3406 dd«d
3407 ccc
3408 bb
3409 aaaaaˇ»
3410 "});
3411 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3412 cx.assert_editor_state(indoc! {"
3413 «aaaaa
3414 bb
3415 ccc
3416 dddˇ»
3417 "});
3418
3419 // Manipulate case with newlines
3420 cx.set_state(indoc! {"
3421 dd«d
3422 ccc
3423
3424 bb
3425 aaaaa
3426
3427 ˇ»
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «
3432
3433 aaaaa
3434 bb
3435 ccc
3436 dddˇ»
3437
3438 "});
3439
3440 // Adding new line
3441 cx.set_state(indoc! {"
3442 aa«a
3443 bbˇ»b
3444 "});
3445 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3446 cx.assert_editor_state(indoc! {"
3447 «aaa
3448 bbb
3449 added_lineˇ»
3450 "});
3451
3452 // Removing line
3453 cx.set_state(indoc! {"
3454 aa«a
3455 bbbˇ»
3456 "});
3457 cx.update_editor(|e, cx| {
3458 e.manipulate_lines(cx, |lines| {
3459 lines.pop();
3460 })
3461 });
3462 cx.assert_editor_state(indoc! {"
3463 «aaaˇ»
3464 "});
3465
3466 // Removing all lines
3467 cx.set_state(indoc! {"
3468 aa«a
3469 bbbˇ»
3470 "});
3471 cx.update_editor(|e, cx| {
3472 e.manipulate_lines(cx, |lines| {
3473 lines.drain(..);
3474 })
3475 });
3476 cx.assert_editor_state(indoc! {"
3477 ˇ
3478 "});
3479}
3480
3481#[gpui::test]
3482async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3483 init_test(cx, |_| {});
3484
3485 let mut cx = EditorTestContext::new(cx).await;
3486
3487 // Consider continuous selection as single selection
3488 cx.set_state(indoc! {"
3489 Aaa«aa
3490 cˇ»c«c
3491 bb
3492 aaaˇ»aa
3493 "});
3494 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3495 cx.assert_editor_state(indoc! {"
3496 «Aaaaa
3497 ccc
3498 bb
3499 aaaaaˇ»
3500 "});
3501
3502 cx.set_state(indoc! {"
3503 Aaa«aa
3504 cˇ»c«c
3505 bb
3506 aaaˇ»aa
3507 "});
3508 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3509 cx.assert_editor_state(indoc! {"
3510 «Aaaaa
3511 ccc
3512 bbˇ»
3513 "});
3514
3515 // Consider non continuous selection as distinct dedup operations
3516 cx.set_state(indoc! {"
3517 «aaaaa
3518 bb
3519 aaaaa
3520 aaaaaˇ»
3521
3522 aaa«aaˇ»
3523 "});
3524 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3525 cx.assert_editor_state(indoc! {"
3526 «aaaaa
3527 bbˇ»
3528
3529 «aaaaaˇ»
3530 "});
3531}
3532
3533#[gpui::test]
3534async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3535 init_test(cx, |_| {});
3536
3537 let mut cx = EditorTestContext::new(cx).await;
3538
3539 cx.set_state(indoc! {"
3540 «Aaa
3541 aAa
3542 Aaaˇ»
3543 "});
3544 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3545 cx.assert_editor_state(indoc! {"
3546 «Aaa
3547 aAaˇ»
3548 "});
3549
3550 cx.set_state(indoc! {"
3551 «Aaa
3552 aAa
3553 aaAˇ»
3554 "});
3555 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «Aaaˇ»
3558 "});
3559}
3560
3561#[gpui::test]
3562async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3563 init_test(cx, |_| {});
3564
3565 let mut cx = EditorTestContext::new(cx).await;
3566
3567 // Manipulate with multiple selections on a single line
3568 cx.set_state(indoc! {"
3569 dd«dd
3570 cˇ»c«c
3571 bb
3572 aaaˇ»aa
3573 "});
3574 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «aaaaa
3577 bb
3578 ccc
3579 ddddˇ»
3580 "});
3581
3582 // Manipulate with multiple disjoin selections
3583 cx.set_state(indoc! {"
3584 5«
3585 4
3586 3
3587 2
3588 1ˇ»
3589
3590 dd«dd
3591 ccc
3592 bb
3593 aaaˇ»aa
3594 "});
3595 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3596 cx.assert_editor_state(indoc! {"
3597 «1
3598 2
3599 3
3600 4
3601 5ˇ»
3602
3603 «aaaaa
3604 bb
3605 ccc
3606 ddddˇ»
3607 "});
3608
3609 // Adding lines on each selection
3610 cx.set_state(indoc! {"
3611 2«
3612 1ˇ»
3613
3614 bb«bb
3615 aaaˇ»aa
3616 "});
3617 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3618 cx.assert_editor_state(indoc! {"
3619 «2
3620 1
3621 added lineˇ»
3622
3623 «bbbb
3624 aaaaa
3625 added lineˇ»
3626 "});
3627
3628 // Removing lines on each selection
3629 cx.set_state(indoc! {"
3630 2«
3631 1ˇ»
3632
3633 bb«bb
3634 aaaˇ»aa
3635 "});
3636 cx.update_editor(|e, cx| {
3637 e.manipulate_lines(cx, |lines| {
3638 lines.pop();
3639 })
3640 });
3641 cx.assert_editor_state(indoc! {"
3642 «2ˇ»
3643
3644 «bbbbˇ»
3645 "});
3646}
3647
3648#[gpui::test]
3649async fn test_manipulate_text(cx: &mut TestAppContext) {
3650 init_test(cx, |_| {});
3651
3652 let mut cx = EditorTestContext::new(cx).await;
3653
3654 // Test convert_to_upper_case()
3655 cx.set_state(indoc! {"
3656 «hello worldˇ»
3657 "});
3658 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3659 cx.assert_editor_state(indoc! {"
3660 «HELLO WORLDˇ»
3661 "});
3662
3663 // Test convert_to_lower_case()
3664 cx.set_state(indoc! {"
3665 «HELLO WORLDˇ»
3666 "});
3667 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3668 cx.assert_editor_state(indoc! {"
3669 «hello worldˇ»
3670 "});
3671
3672 // Test multiple line, single selection case
3673 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3674 cx.set_state(indoc! {"
3675 «The quick brown
3676 fox jumps over
3677 the lazy dogˇ»
3678 "});
3679 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3680 cx.assert_editor_state(indoc! {"
3681 «The Quick Brown
3682 Fox Jumps Over
3683 The Lazy Dogˇ»
3684 "});
3685
3686 // Test multiple line, single selection case
3687 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3688 cx.set_state(indoc! {"
3689 «The quick brown
3690 fox jumps over
3691 the lazy dogˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «TheQuickBrown
3696 FoxJumpsOver
3697 TheLazyDogˇ»
3698 "});
3699
3700 // From here on out, test more complex cases of manipulate_text()
3701
3702 // Test no selection case - should affect words cursors are in
3703 // Cursor at beginning, middle, and end of word
3704 cx.set_state(indoc! {"
3705 ˇhello big beauˇtiful worldˇ
3706 "});
3707 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3708 cx.assert_editor_state(indoc! {"
3709 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3710 "});
3711
3712 // Test multiple selections on a single line and across multiple lines
3713 cx.set_state(indoc! {"
3714 «Theˇ» quick «brown
3715 foxˇ» jumps «overˇ»
3716 the «lazyˇ» dog
3717 "});
3718 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3719 cx.assert_editor_state(indoc! {"
3720 «THEˇ» quick «BROWN
3721 FOXˇ» jumps «OVERˇ»
3722 the «LAZYˇ» dog
3723 "});
3724
3725 // Test case where text length grows
3726 cx.set_state(indoc! {"
3727 «tschüߡ»
3728 "});
3729 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3730 cx.assert_editor_state(indoc! {"
3731 «TSCHÜSSˇ»
3732 "});
3733
3734 // Test to make sure we don't crash when text shrinks
3735 cx.set_state(indoc! {"
3736 aaa_bbbˇ
3737 "});
3738 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3739 cx.assert_editor_state(indoc! {"
3740 «aaaBbbˇ»
3741 "});
3742
3743 // Test to make sure we all aware of the fact that each word can grow and shrink
3744 // Final selections should be aware of this fact
3745 cx.set_state(indoc! {"
3746 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3747 "});
3748 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3749 cx.assert_editor_state(indoc! {"
3750 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3751 "});
3752
3753 cx.set_state(indoc! {"
3754 «hElLo, WoRld!ˇ»
3755 "});
3756 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3757 cx.assert_editor_state(indoc! {"
3758 «HeLlO, wOrLD!ˇ»
3759 "});
3760}
3761
3762#[gpui::test]
3763fn test_duplicate_line(cx: &mut TestAppContext) {
3764 init_test(cx, |_| {});
3765
3766 let view = cx.add_window(|cx| {
3767 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3768 build_editor(buffer, cx)
3769 });
3770 _ = view.update(cx, |view, cx| {
3771 view.change_selections(None, cx, |s| {
3772 s.select_display_ranges([
3773 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3774 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3775 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3776 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3777 ])
3778 });
3779 view.duplicate_line_down(&DuplicateLineDown, cx);
3780 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3781 assert_eq!(
3782 view.selections.display_ranges(cx),
3783 vec![
3784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3785 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3786 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3787 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3788 ]
3789 );
3790 });
3791
3792 let view = cx.add_window(|cx| {
3793 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3794 build_editor(buffer, cx)
3795 });
3796 _ = view.update(cx, |view, cx| {
3797 view.change_selections(None, cx, |s| {
3798 s.select_display_ranges([
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3800 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3801 ])
3802 });
3803 view.duplicate_line_down(&DuplicateLineDown, cx);
3804 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3805 assert_eq!(
3806 view.selections.display_ranges(cx),
3807 vec![
3808 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3809 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3810 ]
3811 );
3812 });
3813
3814 // With `move_upwards` the selections stay in place, except for
3815 // the lines inserted above them
3816 let view = cx.add_window(|cx| {
3817 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3818 build_editor(buffer, cx)
3819 });
3820 _ = view.update(cx, |view, cx| {
3821 view.change_selections(None, cx, |s| {
3822 s.select_display_ranges([
3823 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3824 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3825 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3826 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3827 ])
3828 });
3829 view.duplicate_line_up(&DuplicateLineUp, cx);
3830 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3831 assert_eq!(
3832 view.selections.display_ranges(cx),
3833 vec![
3834 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3835 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3836 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3837 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3838 ]
3839 );
3840 });
3841
3842 let view = cx.add_window(|cx| {
3843 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3844 build_editor(buffer, cx)
3845 });
3846 _ = view.update(cx, |view, cx| {
3847 view.change_selections(None, cx, |s| {
3848 s.select_display_ranges([
3849 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3851 ])
3852 });
3853 view.duplicate_line_up(&DuplicateLineUp, cx);
3854 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3855 assert_eq!(
3856 view.selections.display_ranges(cx),
3857 vec![
3858 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3859 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3860 ]
3861 );
3862 });
3863}
3864
3865#[gpui::test]
3866fn test_move_line_up_down(cx: &mut TestAppContext) {
3867 init_test(cx, |_| {});
3868
3869 let view = cx.add_window(|cx| {
3870 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3871 build_editor(buffer, cx)
3872 });
3873 _ = view.update(cx, |view, cx| {
3874 view.fold_ranges(
3875 vec![
3876 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3877 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3878 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3879 ],
3880 true,
3881 cx,
3882 );
3883 view.change_selections(None, cx, |s| {
3884 s.select_display_ranges([
3885 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3886 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3887 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3888 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3889 ])
3890 });
3891 assert_eq!(
3892 view.display_text(cx),
3893 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3894 );
3895
3896 view.move_line_up(&MoveLineUp, cx);
3897 assert_eq!(
3898 view.display_text(cx),
3899 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3900 );
3901 assert_eq!(
3902 view.selections.display_ranges(cx),
3903 vec![
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3905 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3906 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3907 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3908 ]
3909 );
3910 });
3911
3912 _ = view.update(cx, |view, cx| {
3913 view.move_line_down(&MoveLineDown, cx);
3914 assert_eq!(
3915 view.display_text(cx),
3916 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3917 );
3918 assert_eq!(
3919 view.selections.display_ranges(cx),
3920 vec![
3921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3922 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3923 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3924 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3925 ]
3926 );
3927 });
3928
3929 _ = view.update(cx, |view, cx| {
3930 view.move_line_down(&MoveLineDown, cx);
3931 assert_eq!(
3932 view.display_text(cx),
3933 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3934 );
3935 assert_eq!(
3936 view.selections.display_ranges(cx),
3937 vec![
3938 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3939 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3940 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3941 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3942 ]
3943 );
3944 });
3945
3946 _ = view.update(cx, |view, cx| {
3947 view.move_line_up(&MoveLineUp, cx);
3948 assert_eq!(
3949 view.display_text(cx),
3950 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3951 );
3952 assert_eq!(
3953 view.selections.display_ranges(cx),
3954 vec![
3955 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3956 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3957 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3958 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3959 ]
3960 );
3961 });
3962}
3963
3964#[gpui::test]
3965fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3966 init_test(cx, |_| {});
3967
3968 let editor = cx.add_window(|cx| {
3969 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3970 build_editor(buffer, cx)
3971 });
3972 _ = editor.update(cx, |editor, cx| {
3973 let snapshot = editor.buffer.read(cx).snapshot(cx);
3974 editor.insert_blocks(
3975 [BlockProperties {
3976 style: BlockStyle::Fixed,
3977 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3978 height: 1,
3979 render: Box::new(|_| div().into_any()),
3980 priority: 0,
3981 }],
3982 Some(Autoscroll::fit()),
3983 cx,
3984 );
3985 editor.change_selections(None, cx, |s| {
3986 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3987 });
3988 editor.move_line_down(&MoveLineDown, cx);
3989 });
3990}
3991
3992#[gpui::test]
3993fn test_transpose(cx: &mut TestAppContext) {
3994 init_test(cx, |_| {});
3995
3996 _ = cx.add_window(|cx| {
3997 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3998 editor.set_style(EditorStyle::default(), cx);
3999 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4000 editor.transpose(&Default::default(), cx);
4001 assert_eq!(editor.text(cx), "bac");
4002 assert_eq!(editor.selections.ranges(cx), [2..2]);
4003
4004 editor.transpose(&Default::default(), cx);
4005 assert_eq!(editor.text(cx), "bca");
4006 assert_eq!(editor.selections.ranges(cx), [3..3]);
4007
4008 editor.transpose(&Default::default(), cx);
4009 assert_eq!(editor.text(cx), "bac");
4010 assert_eq!(editor.selections.ranges(cx), [3..3]);
4011
4012 editor
4013 });
4014
4015 _ = cx.add_window(|cx| {
4016 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4017 editor.set_style(EditorStyle::default(), cx);
4018 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4019 editor.transpose(&Default::default(), cx);
4020 assert_eq!(editor.text(cx), "acb\nde");
4021 assert_eq!(editor.selections.ranges(cx), [3..3]);
4022
4023 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4024 editor.transpose(&Default::default(), cx);
4025 assert_eq!(editor.text(cx), "acbd\ne");
4026 assert_eq!(editor.selections.ranges(cx), [5..5]);
4027
4028 editor.transpose(&Default::default(), cx);
4029 assert_eq!(editor.text(cx), "acbde\n");
4030 assert_eq!(editor.selections.ranges(cx), [6..6]);
4031
4032 editor.transpose(&Default::default(), cx);
4033 assert_eq!(editor.text(cx), "acbd\ne");
4034 assert_eq!(editor.selections.ranges(cx), [6..6]);
4035
4036 editor
4037 });
4038
4039 _ = cx.add_window(|cx| {
4040 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4041 editor.set_style(EditorStyle::default(), cx);
4042 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4043 editor.transpose(&Default::default(), cx);
4044 assert_eq!(editor.text(cx), "bacd\ne");
4045 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4046
4047 editor.transpose(&Default::default(), cx);
4048 assert_eq!(editor.text(cx), "bcade\n");
4049 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4050
4051 editor.transpose(&Default::default(), cx);
4052 assert_eq!(editor.text(cx), "bcda\ne");
4053 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4054
4055 editor.transpose(&Default::default(), cx);
4056 assert_eq!(editor.text(cx), "bcade\n");
4057 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4058
4059 editor.transpose(&Default::default(), cx);
4060 assert_eq!(editor.text(cx), "bcaed\n");
4061 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4062
4063 editor
4064 });
4065
4066 _ = cx.add_window(|cx| {
4067 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4068 editor.set_style(EditorStyle::default(), cx);
4069 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4070 editor.transpose(&Default::default(), cx);
4071 assert_eq!(editor.text(cx), "🏀🍐✋");
4072 assert_eq!(editor.selections.ranges(cx), [8..8]);
4073
4074 editor.transpose(&Default::default(), cx);
4075 assert_eq!(editor.text(cx), "🏀✋🍐");
4076 assert_eq!(editor.selections.ranges(cx), [11..11]);
4077
4078 editor.transpose(&Default::default(), cx);
4079 assert_eq!(editor.text(cx), "🏀🍐✋");
4080 assert_eq!(editor.selections.ranges(cx), [11..11]);
4081
4082 editor
4083 });
4084}
4085
4086#[gpui::test]
4087async fn test_rewrap(cx: &mut TestAppContext) {
4088 init_test(cx, |_| {});
4089
4090 let mut cx = EditorTestContext::new(cx).await;
4091
4092 {
4093 let language = Arc::new(Language::new(
4094 LanguageConfig {
4095 line_comments: vec!["// ".into()],
4096 ..LanguageConfig::default()
4097 },
4098 None,
4099 ));
4100 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4101
4102 let unwrapped_text = indoc! {"
4103 // ˇ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.
4104 "};
4105
4106 let wrapped_text = indoc! {"
4107 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4108 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4109 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4110 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4111 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4112 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4113 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4114 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4115 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4116 // porttitor id. Aliquam id accumsan eros.ˇ
4117 "};
4118
4119 cx.set_state(unwrapped_text);
4120 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4121 cx.assert_editor_state(wrapped_text);
4122 }
4123
4124 // Test that rewrapping works inside of a selection
4125 {
4126 let language = Arc::new(Language::new(
4127 LanguageConfig {
4128 line_comments: vec!["// ".into()],
4129 ..LanguageConfig::default()
4130 },
4131 None,
4132 ));
4133 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4134
4135 let unwrapped_text = indoc! {"
4136 «// 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.ˇ»
4137 "};
4138
4139 let wrapped_text = indoc! {"
4140 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4141 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4142 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4143 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4144 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4145 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4146 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4147 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4148 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4149 // porttitor id. Aliquam id accumsan eros.ˇ
4150 "};
4151
4152 cx.set_state(unwrapped_text);
4153 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4154 cx.assert_editor_state(wrapped_text);
4155 }
4156
4157 // Test that cursors that expand to the same region are collapsed.
4158 {
4159 let language = Arc::new(Language::new(
4160 LanguageConfig {
4161 line_comments: vec!["// ".into()],
4162 ..LanguageConfig::default()
4163 },
4164 None,
4165 ));
4166 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4167
4168 let unwrapped_text = indoc! {"
4169 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4170 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4171 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4172 // ˇ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.
4173 "};
4174
4175 let wrapped_text = indoc! {"
4176 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4177 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4178 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4179 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4180 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4181 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4182 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4183 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4184 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4185 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4186 "};
4187
4188 cx.set_state(unwrapped_text);
4189 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4190 cx.assert_editor_state(wrapped_text);
4191 }
4192
4193 // Test that non-contiguous selections are treated separately.
4194 {
4195 let language = Arc::new(Language::new(
4196 LanguageConfig {
4197 line_comments: vec!["// ".into()],
4198 ..LanguageConfig::default()
4199 },
4200 None,
4201 ));
4202 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4203
4204 let unwrapped_text = indoc! {"
4205 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4206 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4207 //
4208 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4209 // ˇ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.
4210 "};
4211
4212 let wrapped_text = indoc! {"
4213 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4214 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4215 // auctor, eu lacinia sapien scelerisque.ˇˇ
4216 //
4217 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4218 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4219 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4220 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4221 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4222 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4223 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4224 "};
4225
4226 cx.set_state(unwrapped_text);
4227 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4228 cx.assert_editor_state(wrapped_text);
4229 }
4230
4231 // Test that different comment prefixes are supported.
4232 {
4233 let language = Arc::new(Language::new(
4234 LanguageConfig {
4235 line_comments: vec!["# ".into()],
4236 ..LanguageConfig::default()
4237 },
4238 None,
4239 ));
4240 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4241
4242 let unwrapped_text = indoc! {"
4243 # ˇ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.
4244 "};
4245
4246 let wrapped_text = indoc! {"
4247 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4248 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4249 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4250 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4251 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4252 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4253 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4254 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4255 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4256 # accumsan eros.ˇ
4257 "};
4258
4259 cx.set_state(unwrapped_text);
4260 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4261 cx.assert_editor_state(wrapped_text);
4262 }
4263
4264 // Test that rewrapping is ignored outside of comments in most languages.
4265 {
4266 let language = Arc::new(Language::new(
4267 LanguageConfig {
4268 line_comments: vec!["// ".into(), "/// ".into()],
4269 ..LanguageConfig::default()
4270 },
4271 Some(tree_sitter_rust::LANGUAGE.into()),
4272 ));
4273 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4274
4275 let unwrapped_text = indoc! {"
4276 /// Adds two numbers.
4277 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4278 fn add(a: u32, b: u32) -> u32 {
4279 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ˇ
4280 }
4281 "};
4282
4283 let wrapped_text = indoc! {"
4284 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4285 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4286 fn add(a: u32, b: u32) -> u32 {
4287 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ˇ
4288 }
4289 "};
4290
4291 cx.set_state(unwrapped_text);
4292 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4293 cx.assert_editor_state(wrapped_text);
4294 }
4295
4296 // Test that rewrapping works in Markdown and Plain Text languages.
4297 {
4298 let markdown_language = Arc::new(Language::new(
4299 LanguageConfig {
4300 name: "Markdown".into(),
4301 ..LanguageConfig::default()
4302 },
4303 None,
4304 ));
4305 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4306
4307 let unwrapped_text = indoc! {"
4308 # Hello
4309
4310 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.
4311 "};
4312
4313 let wrapped_text = indoc! {"
4314 # Hello
4315
4316 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4317 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4318 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4319 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4320 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4321 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4322 Integer sit amet scelerisque nisi.ˇ
4323 "};
4324
4325 cx.set_state(unwrapped_text);
4326 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4327 cx.assert_editor_state(wrapped_text);
4328
4329 let plaintext_language = Arc::new(Language::new(
4330 LanguageConfig {
4331 name: "Plain Text".into(),
4332 ..LanguageConfig::default()
4333 },
4334 None,
4335 ));
4336 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4337
4338 let unwrapped_text = indoc! {"
4339 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.
4340 "};
4341
4342 let wrapped_text = indoc! {"
4343 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4344 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4345 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4346 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4347 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4348 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4349 Integer sit amet scelerisque nisi.ˇ
4350 "};
4351
4352 cx.set_state(unwrapped_text);
4353 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4354 cx.assert_editor_state(wrapped_text);
4355 }
4356
4357 // Test rewrapping unaligned comments in a selection.
4358 {
4359 let language = Arc::new(Language::new(
4360 LanguageConfig {
4361 line_comments: vec!["// ".into(), "/// ".into()],
4362 ..LanguageConfig::default()
4363 },
4364 Some(tree_sitter_rust::LANGUAGE.into()),
4365 ));
4366 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4367
4368 let unwrapped_text = indoc! {"
4369 fn foo() {
4370 if true {
4371 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4372 // Praesent semper egestas tellus id dignissim.ˇ»
4373 do_something();
4374 } else {
4375 //
4376 }
4377 }
4378 "};
4379
4380 let wrapped_text = indoc! {"
4381 fn foo() {
4382 if true {
4383 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4384 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4385 // egestas tellus id dignissim.ˇ
4386 do_something();
4387 } else {
4388 //
4389 }
4390 }
4391 "};
4392
4393 cx.set_state(unwrapped_text);
4394 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4395 cx.assert_editor_state(wrapped_text);
4396
4397 let unwrapped_text = indoc! {"
4398 fn foo() {
4399 if true {
4400 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4401 // Praesent semper egestas tellus id dignissim.»
4402 do_something();
4403 } else {
4404 //
4405 }
4406
4407 }
4408 "};
4409
4410 let wrapped_text = indoc! {"
4411 fn foo() {
4412 if true {
4413 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4414 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4415 // egestas tellus id dignissim.ˇ
4416 do_something();
4417 } else {
4418 //
4419 }
4420
4421 }
4422 "};
4423
4424 cx.set_state(unwrapped_text);
4425 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4426 cx.assert_editor_state(wrapped_text);
4427 }
4428}
4429
4430#[gpui::test]
4431async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4432 init_test(cx, |_| {});
4433
4434 let mut cx = EditorTestContext::new(cx).await;
4435
4436 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4437 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4438 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4439
4440 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4441 cx.set_state("two ˇfour ˇsix ˇ");
4442 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4443 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4444
4445 // Paste again but with only two cursors. Since the number of cursors doesn't
4446 // match the number of slices in the clipboard, the entire clipboard text
4447 // is pasted at each cursor.
4448 cx.set_state("ˇtwo one✅ four three six five ˇ");
4449 cx.update_editor(|e, cx| {
4450 e.handle_input("( ", cx);
4451 e.paste(&Paste, cx);
4452 e.handle_input(") ", cx);
4453 });
4454 cx.assert_editor_state(
4455 &([
4456 "( one✅ ",
4457 "three ",
4458 "five ) ˇtwo one✅ four three six five ( one✅ ",
4459 "three ",
4460 "five ) ˇ",
4461 ]
4462 .join("\n")),
4463 );
4464
4465 // Cut with three selections, one of which is full-line.
4466 cx.set_state(indoc! {"
4467 1«2ˇ»3
4468 4ˇ567
4469 «8ˇ»9"});
4470 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4471 cx.assert_editor_state(indoc! {"
4472 1ˇ3
4473 ˇ9"});
4474
4475 // Paste with three selections, noticing how the copied selection that was full-line
4476 // gets inserted before the second cursor.
4477 cx.set_state(indoc! {"
4478 1ˇ3
4479 9ˇ
4480 «oˇ»ne"});
4481 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4482 cx.assert_editor_state(indoc! {"
4483 12ˇ3
4484 4567
4485 9ˇ
4486 8ˇne"});
4487
4488 // Copy with a single cursor only, which writes the whole line into the clipboard.
4489 cx.set_state(indoc! {"
4490 The quick brown
4491 fox juˇmps over
4492 the lazy dog"});
4493 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4494 assert_eq!(
4495 cx.read_from_clipboard()
4496 .and_then(|item| item.text().as_deref().map(str::to_string)),
4497 Some("fox jumps over\n".to_string())
4498 );
4499
4500 // Paste with three selections, noticing how the copied full-line selection is inserted
4501 // before the empty selections but replaces the selection that is non-empty.
4502 cx.set_state(indoc! {"
4503 Tˇhe quick brown
4504 «foˇ»x jumps over
4505 tˇhe lazy dog"});
4506 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4507 cx.assert_editor_state(indoc! {"
4508 fox jumps over
4509 Tˇhe quick brown
4510 fox jumps over
4511 ˇx jumps over
4512 fox jumps over
4513 tˇhe lazy dog"});
4514}
4515
4516#[gpui::test]
4517async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4518 init_test(cx, |_| {});
4519
4520 let mut cx = EditorTestContext::new(cx).await;
4521 let language = Arc::new(Language::new(
4522 LanguageConfig::default(),
4523 Some(tree_sitter_rust::LANGUAGE.into()),
4524 ));
4525 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4526
4527 // Cut an indented block, without the leading whitespace.
4528 cx.set_state(indoc! {"
4529 const a: B = (
4530 c(),
4531 «d(
4532 e,
4533 f
4534 )ˇ»
4535 );
4536 "});
4537 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4538 cx.assert_editor_state(indoc! {"
4539 const a: B = (
4540 c(),
4541 ˇ
4542 );
4543 "});
4544
4545 // Paste it at the same position.
4546 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4547 cx.assert_editor_state(indoc! {"
4548 const a: B = (
4549 c(),
4550 d(
4551 e,
4552 f
4553 )ˇ
4554 );
4555 "});
4556
4557 // Paste it at a line with a lower indent level.
4558 cx.set_state(indoc! {"
4559 ˇ
4560 const a: B = (
4561 c(),
4562 );
4563 "});
4564 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4565 cx.assert_editor_state(indoc! {"
4566 d(
4567 e,
4568 f
4569 )ˇ
4570 const a: B = (
4571 c(),
4572 );
4573 "});
4574
4575 // Cut an indented block, with the leading whitespace.
4576 cx.set_state(indoc! {"
4577 const a: B = (
4578 c(),
4579 « d(
4580 e,
4581 f
4582 )
4583 ˇ»);
4584 "});
4585 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4586 cx.assert_editor_state(indoc! {"
4587 const a: B = (
4588 c(),
4589 ˇ);
4590 "});
4591
4592 // Paste it at the same position.
4593 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4594 cx.assert_editor_state(indoc! {"
4595 const a: B = (
4596 c(),
4597 d(
4598 e,
4599 f
4600 )
4601 ˇ);
4602 "});
4603
4604 // Paste it at a line with a higher indent level.
4605 cx.set_state(indoc! {"
4606 const a: B = (
4607 c(),
4608 d(
4609 e,
4610 fˇ
4611 )
4612 );
4613 "});
4614 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4615 cx.assert_editor_state(indoc! {"
4616 const a: B = (
4617 c(),
4618 d(
4619 e,
4620 f d(
4621 e,
4622 f
4623 )
4624 ˇ
4625 )
4626 );
4627 "});
4628}
4629
4630#[gpui::test]
4631fn test_select_all(cx: &mut TestAppContext) {
4632 init_test(cx, |_| {});
4633
4634 let view = cx.add_window(|cx| {
4635 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4636 build_editor(buffer, cx)
4637 });
4638 _ = view.update(cx, |view, cx| {
4639 view.select_all(&SelectAll, cx);
4640 assert_eq!(
4641 view.selections.display_ranges(cx),
4642 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4643 );
4644 });
4645}
4646
4647#[gpui::test]
4648fn test_select_line(cx: &mut TestAppContext) {
4649 init_test(cx, |_| {});
4650
4651 let view = cx.add_window(|cx| {
4652 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4653 build_editor(buffer, cx)
4654 });
4655 _ = view.update(cx, |view, cx| {
4656 view.change_selections(None, cx, |s| {
4657 s.select_display_ranges([
4658 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4659 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4660 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4661 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4662 ])
4663 });
4664 view.select_line(&SelectLine, cx);
4665 assert_eq!(
4666 view.selections.display_ranges(cx),
4667 vec![
4668 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4669 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4670 ]
4671 );
4672 });
4673
4674 _ = view.update(cx, |view, cx| {
4675 view.select_line(&SelectLine, cx);
4676 assert_eq!(
4677 view.selections.display_ranges(cx),
4678 vec![
4679 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4680 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4681 ]
4682 );
4683 });
4684
4685 _ = view.update(cx, |view, cx| {
4686 view.select_line(&SelectLine, cx);
4687 assert_eq!(
4688 view.selections.display_ranges(cx),
4689 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4690 );
4691 });
4692}
4693
4694#[gpui::test]
4695fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4696 init_test(cx, |_| {});
4697
4698 let view = cx.add_window(|cx| {
4699 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4700 build_editor(buffer, cx)
4701 });
4702 _ = view.update(cx, |view, cx| {
4703 view.fold_ranges(
4704 vec![
4705 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4706 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4707 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4708 ],
4709 true,
4710 cx,
4711 );
4712 view.change_selections(None, cx, |s| {
4713 s.select_display_ranges([
4714 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4715 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4716 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4717 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4718 ])
4719 });
4720 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4721 });
4722
4723 _ = view.update(cx, |view, cx| {
4724 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4725 assert_eq!(
4726 view.display_text(cx),
4727 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4728 );
4729 assert_eq!(
4730 view.selections.display_ranges(cx),
4731 [
4732 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4733 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4734 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4735 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4736 ]
4737 );
4738 });
4739
4740 _ = view.update(cx, |view, cx| {
4741 view.change_selections(None, cx, |s| {
4742 s.select_display_ranges([
4743 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4744 ])
4745 });
4746 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4747 assert_eq!(
4748 view.display_text(cx),
4749 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4750 );
4751 assert_eq!(
4752 view.selections.display_ranges(cx),
4753 [
4754 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4755 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4756 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4757 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4758 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4759 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4760 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4761 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4762 ]
4763 );
4764 });
4765}
4766
4767#[gpui::test]
4768async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4769 init_test(cx, |_| {});
4770
4771 let mut cx = EditorTestContext::new(cx).await;
4772
4773 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4774 cx.set_state(indoc!(
4775 r#"abc
4776 defˇghi
4777
4778 jk
4779 nlmo
4780 "#
4781 ));
4782
4783 cx.update_editor(|editor, cx| {
4784 editor.add_selection_above(&Default::default(), cx);
4785 });
4786
4787 cx.assert_editor_state(indoc!(
4788 r#"abcˇ
4789 defˇghi
4790
4791 jk
4792 nlmo
4793 "#
4794 ));
4795
4796 cx.update_editor(|editor, cx| {
4797 editor.add_selection_above(&Default::default(), cx);
4798 });
4799
4800 cx.assert_editor_state(indoc!(
4801 r#"abcˇ
4802 defˇghi
4803
4804 jk
4805 nlmo
4806 "#
4807 ));
4808
4809 cx.update_editor(|view, cx| {
4810 view.add_selection_below(&Default::default(), cx);
4811 });
4812
4813 cx.assert_editor_state(indoc!(
4814 r#"abc
4815 defˇghi
4816
4817 jk
4818 nlmo
4819 "#
4820 ));
4821
4822 cx.update_editor(|view, cx| {
4823 view.undo_selection(&Default::default(), cx);
4824 });
4825
4826 cx.assert_editor_state(indoc!(
4827 r#"abcˇ
4828 defˇghi
4829
4830 jk
4831 nlmo
4832 "#
4833 ));
4834
4835 cx.update_editor(|view, cx| {
4836 view.redo_selection(&Default::default(), cx);
4837 });
4838
4839 cx.assert_editor_state(indoc!(
4840 r#"abc
4841 defˇghi
4842
4843 jk
4844 nlmo
4845 "#
4846 ));
4847
4848 cx.update_editor(|view, cx| {
4849 view.add_selection_below(&Default::default(), cx);
4850 });
4851
4852 cx.assert_editor_state(indoc!(
4853 r#"abc
4854 defˇghi
4855
4856 jk
4857 nlmˇo
4858 "#
4859 ));
4860
4861 cx.update_editor(|view, cx| {
4862 view.add_selection_below(&Default::default(), cx);
4863 });
4864
4865 cx.assert_editor_state(indoc!(
4866 r#"abc
4867 defˇghi
4868
4869 jk
4870 nlmˇo
4871 "#
4872 ));
4873
4874 // change selections
4875 cx.set_state(indoc!(
4876 r#"abc
4877 def«ˇg»hi
4878
4879 jk
4880 nlmo
4881 "#
4882 ));
4883
4884 cx.update_editor(|view, cx| {
4885 view.add_selection_below(&Default::default(), cx);
4886 });
4887
4888 cx.assert_editor_state(indoc!(
4889 r#"abc
4890 def«ˇg»hi
4891
4892 jk
4893 nlm«ˇo»
4894 "#
4895 ));
4896
4897 cx.update_editor(|view, cx| {
4898 view.add_selection_below(&Default::default(), cx);
4899 });
4900
4901 cx.assert_editor_state(indoc!(
4902 r#"abc
4903 def«ˇg»hi
4904
4905 jk
4906 nlm«ˇo»
4907 "#
4908 ));
4909
4910 cx.update_editor(|view, cx| {
4911 view.add_selection_above(&Default::default(), cx);
4912 });
4913
4914 cx.assert_editor_state(indoc!(
4915 r#"abc
4916 def«ˇg»hi
4917
4918 jk
4919 nlmo
4920 "#
4921 ));
4922
4923 cx.update_editor(|view, cx| {
4924 view.add_selection_above(&Default::default(), cx);
4925 });
4926
4927 cx.assert_editor_state(indoc!(
4928 r#"abc
4929 def«ˇg»hi
4930
4931 jk
4932 nlmo
4933 "#
4934 ));
4935
4936 // Change selections again
4937 cx.set_state(indoc!(
4938 r#"a«bc
4939 defgˇ»hi
4940
4941 jk
4942 nlmo
4943 "#
4944 ));
4945
4946 cx.update_editor(|view, cx| {
4947 view.add_selection_below(&Default::default(), cx);
4948 });
4949
4950 cx.assert_editor_state(indoc!(
4951 r#"a«bcˇ»
4952 d«efgˇ»hi
4953
4954 j«kˇ»
4955 nlmo
4956 "#
4957 ));
4958
4959 cx.update_editor(|view, cx| {
4960 view.add_selection_below(&Default::default(), cx);
4961 });
4962 cx.assert_editor_state(indoc!(
4963 r#"a«bcˇ»
4964 d«efgˇ»hi
4965
4966 j«kˇ»
4967 n«lmoˇ»
4968 "#
4969 ));
4970 cx.update_editor(|view, cx| {
4971 view.add_selection_above(&Default::default(), cx);
4972 });
4973
4974 cx.assert_editor_state(indoc!(
4975 r#"a«bcˇ»
4976 d«efgˇ»hi
4977
4978 j«kˇ»
4979 nlmo
4980 "#
4981 ));
4982
4983 // Change selections again
4984 cx.set_state(indoc!(
4985 r#"abc
4986 d«ˇefghi
4987
4988 jk
4989 nlm»o
4990 "#
4991 ));
4992
4993 cx.update_editor(|view, cx| {
4994 view.add_selection_above(&Default::default(), cx);
4995 });
4996
4997 cx.assert_editor_state(indoc!(
4998 r#"a«ˇbc»
4999 d«ˇef»ghi
5000
5001 j«ˇk»
5002 n«ˇlm»o
5003 "#
5004 ));
5005
5006 cx.update_editor(|view, cx| {
5007 view.add_selection_below(&Default::default(), cx);
5008 });
5009
5010 cx.assert_editor_state(indoc!(
5011 r#"abc
5012 d«ˇef»ghi
5013
5014 j«ˇk»
5015 n«ˇlm»o
5016 "#
5017 ));
5018}
5019
5020#[gpui::test]
5021async fn test_select_next(cx: &mut gpui::TestAppContext) {
5022 init_test(cx, |_| {});
5023
5024 let mut cx = EditorTestContext::new(cx).await;
5025 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5026
5027 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5028 .unwrap();
5029 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5030
5031 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5032 .unwrap();
5033 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5034
5035 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5036 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5037
5038 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5039 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5040
5041 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5042 .unwrap();
5043 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5044
5045 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5046 .unwrap();
5047 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5048}
5049
5050#[gpui::test]
5051async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5052 init_test(cx, |_| {});
5053
5054 let mut cx = EditorTestContext::new(cx).await;
5055 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5056
5057 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5058 .unwrap();
5059 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5060}
5061
5062#[gpui::test]
5063async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5064 init_test(cx, |_| {});
5065
5066 let mut cx = EditorTestContext::new(cx).await;
5067 cx.set_state(
5068 r#"let foo = 2;
5069lˇet foo = 2;
5070let fooˇ = 2;
5071let foo = 2;
5072let foo = ˇ2;"#,
5073 );
5074
5075 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5076 .unwrap();
5077 cx.assert_editor_state(
5078 r#"let foo = 2;
5079«letˇ» foo = 2;
5080let «fooˇ» = 2;
5081let foo = 2;
5082let foo = «2ˇ»;"#,
5083 );
5084
5085 // noop for multiple selections with different contents
5086 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5087 .unwrap();
5088 cx.assert_editor_state(
5089 r#"let foo = 2;
5090«letˇ» foo = 2;
5091let «fooˇ» = 2;
5092let foo = 2;
5093let foo = «2ˇ»;"#,
5094 );
5095}
5096
5097#[gpui::test]
5098async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5099 init_test(cx, |_| {});
5100
5101 let mut cx =
5102 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5103
5104 cx.assert_editor_state(indoc! {"
5105 ˇbbb
5106 ccc
5107
5108 bbb
5109 ccc
5110 "});
5111 cx.dispatch_action(SelectPrevious::default());
5112 cx.assert_editor_state(indoc! {"
5113 «bbbˇ»
5114 ccc
5115
5116 bbb
5117 ccc
5118 "});
5119 cx.dispatch_action(SelectPrevious::default());
5120 cx.assert_editor_state(indoc! {"
5121 «bbbˇ»
5122 ccc
5123
5124 «bbbˇ»
5125 ccc
5126 "});
5127}
5128
5129#[gpui::test]
5130async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5131 init_test(cx, |_| {});
5132
5133 let mut cx = EditorTestContext::new(cx).await;
5134 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5135
5136 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5137 .unwrap();
5138 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5139
5140 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5141 .unwrap();
5142 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5143
5144 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5145 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5146
5147 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5148 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5149
5150 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5151 .unwrap();
5152 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5153
5154 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5155 .unwrap();
5156 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5157
5158 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5159 .unwrap();
5160 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5161}
5162
5163#[gpui::test]
5164async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5165 init_test(cx, |_| {});
5166
5167 let mut cx = EditorTestContext::new(cx).await;
5168 cx.set_state(
5169 r#"let foo = 2;
5170lˇet foo = 2;
5171let fooˇ = 2;
5172let foo = 2;
5173let foo = ˇ2;"#,
5174 );
5175
5176 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5177 .unwrap();
5178 cx.assert_editor_state(
5179 r#"let foo = 2;
5180«letˇ» foo = 2;
5181let «fooˇ» = 2;
5182let foo = 2;
5183let foo = «2ˇ»;"#,
5184 );
5185
5186 // noop for multiple selections with different contents
5187 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5188 .unwrap();
5189 cx.assert_editor_state(
5190 r#"let foo = 2;
5191«letˇ» foo = 2;
5192let «fooˇ» = 2;
5193let foo = 2;
5194let foo = «2ˇ»;"#,
5195 );
5196}
5197
5198#[gpui::test]
5199async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5200 init_test(cx, |_| {});
5201
5202 let mut cx = EditorTestContext::new(cx).await;
5203 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5204
5205 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5206 .unwrap();
5207 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5208
5209 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5210 .unwrap();
5211 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5212
5213 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5214 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5215
5216 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5217 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5218
5219 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5220 .unwrap();
5221 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5222
5223 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5224 .unwrap();
5225 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5226}
5227
5228#[gpui::test]
5229async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5230 init_test(cx, |_| {});
5231
5232 let language = Arc::new(Language::new(
5233 LanguageConfig::default(),
5234 Some(tree_sitter_rust::LANGUAGE.into()),
5235 ));
5236
5237 let text = r#"
5238 use mod1::mod2::{mod3, mod4};
5239
5240 fn fn_1(param1: bool, param2: &str) {
5241 let var1 = "text";
5242 }
5243 "#
5244 .unindent();
5245
5246 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5247 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5248 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5249
5250 editor
5251 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5252 .await;
5253
5254 editor.update(cx, |view, cx| {
5255 view.change_selections(None, cx, |s| {
5256 s.select_display_ranges([
5257 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5258 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5259 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5260 ]);
5261 });
5262 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5263 });
5264 editor.update(cx, |editor, cx| {
5265 assert_text_with_selections(
5266 editor,
5267 indoc! {r#"
5268 use mod1::mod2::{mod3, «mod4ˇ»};
5269
5270 fn fn_1«ˇ(param1: bool, param2: &str)» {
5271 let var1 = "«textˇ»";
5272 }
5273 "#},
5274 cx,
5275 );
5276 });
5277
5278 editor.update(cx, |view, cx| {
5279 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5280 });
5281 editor.update(cx, |editor, cx| {
5282 assert_text_with_selections(
5283 editor,
5284 indoc! {r#"
5285 use mod1::mod2::«{mod3, mod4}ˇ»;
5286
5287 «ˇfn fn_1(param1: bool, param2: &str) {
5288 let var1 = "text";
5289 }»
5290 "#},
5291 cx,
5292 );
5293 });
5294
5295 editor.update(cx, |view, cx| {
5296 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5297 });
5298 assert_eq!(
5299 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5300 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5301 );
5302
5303 // Trying to expand the selected syntax node one more time has no effect.
5304 editor.update(cx, |view, cx| {
5305 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5306 });
5307 assert_eq!(
5308 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5309 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5310 );
5311
5312 editor.update(cx, |view, cx| {
5313 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5314 });
5315 editor.update(cx, |editor, cx| {
5316 assert_text_with_selections(
5317 editor,
5318 indoc! {r#"
5319 use mod1::mod2::«{mod3, mod4}ˇ»;
5320
5321 «ˇfn fn_1(param1: bool, param2: &str) {
5322 let var1 = "text";
5323 }»
5324 "#},
5325 cx,
5326 );
5327 });
5328
5329 editor.update(cx, |view, cx| {
5330 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5331 });
5332 editor.update(cx, |editor, cx| {
5333 assert_text_with_selections(
5334 editor,
5335 indoc! {r#"
5336 use mod1::mod2::{mod3, «mod4ˇ»};
5337
5338 fn fn_1«ˇ(param1: bool, param2: &str)» {
5339 let var1 = "«textˇ»";
5340 }
5341 "#},
5342 cx,
5343 );
5344 });
5345
5346 editor.update(cx, |view, cx| {
5347 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5348 });
5349 editor.update(cx, |editor, cx| {
5350 assert_text_with_selections(
5351 editor,
5352 indoc! {r#"
5353 use mod1::mod2::{mod3, mo«ˇ»d4};
5354
5355 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5356 let var1 = "te«ˇ»xt";
5357 }
5358 "#},
5359 cx,
5360 );
5361 });
5362
5363 // Trying to shrink the selected syntax node one more time has no effect.
5364 editor.update(cx, |view, cx| {
5365 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5366 });
5367 editor.update(cx, |editor, cx| {
5368 assert_text_with_selections(
5369 editor,
5370 indoc! {r#"
5371 use mod1::mod2::{mod3, mo«ˇ»d4};
5372
5373 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5374 let var1 = "te«ˇ»xt";
5375 }
5376 "#},
5377 cx,
5378 );
5379 });
5380
5381 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5382 // a fold.
5383 editor.update(cx, |view, cx| {
5384 view.fold_ranges(
5385 vec![
5386 (
5387 Point::new(0, 21)..Point::new(0, 24),
5388 FoldPlaceholder::test(),
5389 ),
5390 (
5391 Point::new(3, 20)..Point::new(3, 22),
5392 FoldPlaceholder::test(),
5393 ),
5394 ],
5395 true,
5396 cx,
5397 );
5398 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5399 });
5400 editor.update(cx, |editor, cx| {
5401 assert_text_with_selections(
5402 editor,
5403 indoc! {r#"
5404 use mod1::mod2::«{mod3, mod4}ˇ»;
5405
5406 fn fn_1«ˇ(param1: bool, param2: &str)» {
5407 «let var1 = "text";ˇ»
5408 }
5409 "#},
5410 cx,
5411 );
5412 });
5413}
5414
5415#[gpui::test]
5416async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5417 init_test(cx, |_| {});
5418
5419 let language = Arc::new(
5420 Language::new(
5421 LanguageConfig {
5422 brackets: BracketPairConfig {
5423 pairs: vec![
5424 BracketPair {
5425 start: "{".to_string(),
5426 end: "}".to_string(),
5427 close: false,
5428 surround: false,
5429 newline: true,
5430 },
5431 BracketPair {
5432 start: "(".to_string(),
5433 end: ")".to_string(),
5434 close: false,
5435 surround: false,
5436 newline: true,
5437 },
5438 ],
5439 ..Default::default()
5440 },
5441 ..Default::default()
5442 },
5443 Some(tree_sitter_rust::LANGUAGE.into()),
5444 )
5445 .with_indents_query(
5446 r#"
5447 (_ "(" ")" @end) @indent
5448 (_ "{" "}" @end) @indent
5449 "#,
5450 )
5451 .unwrap(),
5452 );
5453
5454 let text = "fn a() {}";
5455
5456 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5457 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5458 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5459 editor
5460 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5461 .await;
5462
5463 editor.update(cx, |editor, cx| {
5464 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5465 editor.newline(&Newline, cx);
5466 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5467 assert_eq!(
5468 editor.selections.ranges(cx),
5469 &[
5470 Point::new(1, 4)..Point::new(1, 4),
5471 Point::new(3, 4)..Point::new(3, 4),
5472 Point::new(5, 0)..Point::new(5, 0)
5473 ]
5474 );
5475 });
5476}
5477
5478#[gpui::test]
5479async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5480 init_test(cx, |_| {});
5481
5482 let mut cx = EditorTestContext::new(cx).await;
5483
5484 let language = Arc::new(Language::new(
5485 LanguageConfig {
5486 brackets: BracketPairConfig {
5487 pairs: vec![
5488 BracketPair {
5489 start: "{".to_string(),
5490 end: "}".to_string(),
5491 close: true,
5492 surround: true,
5493 newline: true,
5494 },
5495 BracketPair {
5496 start: "(".to_string(),
5497 end: ")".to_string(),
5498 close: true,
5499 surround: true,
5500 newline: true,
5501 },
5502 BracketPair {
5503 start: "/*".to_string(),
5504 end: " */".to_string(),
5505 close: true,
5506 surround: true,
5507 newline: true,
5508 },
5509 BracketPair {
5510 start: "[".to_string(),
5511 end: "]".to_string(),
5512 close: false,
5513 surround: false,
5514 newline: true,
5515 },
5516 BracketPair {
5517 start: "\"".to_string(),
5518 end: "\"".to_string(),
5519 close: true,
5520 surround: true,
5521 newline: false,
5522 },
5523 BracketPair {
5524 start: "<".to_string(),
5525 end: ">".to_string(),
5526 close: false,
5527 surround: true,
5528 newline: true,
5529 },
5530 ],
5531 ..Default::default()
5532 },
5533 autoclose_before: "})]".to_string(),
5534 ..Default::default()
5535 },
5536 Some(tree_sitter_rust::LANGUAGE.into()),
5537 ));
5538
5539 cx.language_registry().add(language.clone());
5540 cx.update_buffer(|buffer, cx| {
5541 buffer.set_language(Some(language), cx);
5542 });
5543
5544 cx.set_state(
5545 &r#"
5546 🏀ˇ
5547 εˇ
5548 ❤️ˇ
5549 "#
5550 .unindent(),
5551 );
5552
5553 // autoclose multiple nested brackets at multiple cursors
5554 cx.update_editor(|view, cx| {
5555 view.handle_input("{", cx);
5556 view.handle_input("{", cx);
5557 view.handle_input("{", cx);
5558 });
5559 cx.assert_editor_state(
5560 &"
5561 🏀{{{ˇ}}}
5562 ε{{{ˇ}}}
5563 ❤️{{{ˇ}}}
5564 "
5565 .unindent(),
5566 );
5567
5568 // insert a different closing bracket
5569 cx.update_editor(|view, cx| {
5570 view.handle_input(")", cx);
5571 });
5572 cx.assert_editor_state(
5573 &"
5574 🏀{{{)ˇ}}}
5575 ε{{{)ˇ}}}
5576 ❤️{{{)ˇ}}}
5577 "
5578 .unindent(),
5579 );
5580
5581 // skip over the auto-closed brackets when typing a closing bracket
5582 cx.update_editor(|view, cx| {
5583 view.move_right(&MoveRight, cx);
5584 view.handle_input("}", cx);
5585 view.handle_input("}", cx);
5586 view.handle_input("}", cx);
5587 });
5588 cx.assert_editor_state(
5589 &"
5590 🏀{{{)}}}}ˇ
5591 ε{{{)}}}}ˇ
5592 ❤️{{{)}}}}ˇ
5593 "
5594 .unindent(),
5595 );
5596
5597 // autoclose multi-character pairs
5598 cx.set_state(
5599 &"
5600 ˇ
5601 ˇ
5602 "
5603 .unindent(),
5604 );
5605 cx.update_editor(|view, cx| {
5606 view.handle_input("/", cx);
5607 view.handle_input("*", cx);
5608 });
5609 cx.assert_editor_state(
5610 &"
5611 /*ˇ */
5612 /*ˇ */
5613 "
5614 .unindent(),
5615 );
5616
5617 // one cursor autocloses a multi-character pair, one cursor
5618 // does not autoclose.
5619 cx.set_state(
5620 &"
5621 /ˇ
5622 ˇ
5623 "
5624 .unindent(),
5625 );
5626 cx.update_editor(|view, cx| view.handle_input("*", cx));
5627 cx.assert_editor_state(
5628 &"
5629 /*ˇ */
5630 *ˇ
5631 "
5632 .unindent(),
5633 );
5634
5635 // Don't autoclose if the next character isn't whitespace and isn't
5636 // listed in the language's "autoclose_before" section.
5637 cx.set_state("ˇa b");
5638 cx.update_editor(|view, cx| view.handle_input("{", cx));
5639 cx.assert_editor_state("{ˇa b");
5640
5641 // Don't autoclose if `close` is false for the bracket pair
5642 cx.set_state("ˇ");
5643 cx.update_editor(|view, cx| view.handle_input("[", cx));
5644 cx.assert_editor_state("[ˇ");
5645
5646 // Surround with brackets if text is selected
5647 cx.set_state("«aˇ» b");
5648 cx.update_editor(|view, cx| view.handle_input("{", cx));
5649 cx.assert_editor_state("{«aˇ»} b");
5650
5651 // Autclose pair where the start and end characters are the same
5652 cx.set_state("aˇ");
5653 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5654 cx.assert_editor_state("a\"ˇ\"");
5655 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5656 cx.assert_editor_state("a\"\"ˇ");
5657
5658 // Don't autoclose pair if autoclose is disabled
5659 cx.set_state("ˇ");
5660 cx.update_editor(|view, cx| view.handle_input("<", cx));
5661 cx.assert_editor_state("<ˇ");
5662
5663 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5664 cx.set_state("«aˇ» b");
5665 cx.update_editor(|view, cx| view.handle_input("<", cx));
5666 cx.assert_editor_state("<«aˇ»> b");
5667}
5668
5669#[gpui::test]
5670async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5671 init_test(cx, |settings| {
5672 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5673 });
5674
5675 let mut cx = EditorTestContext::new(cx).await;
5676
5677 let language = Arc::new(Language::new(
5678 LanguageConfig {
5679 brackets: BracketPairConfig {
5680 pairs: vec![
5681 BracketPair {
5682 start: "{".to_string(),
5683 end: "}".to_string(),
5684 close: true,
5685 surround: true,
5686 newline: true,
5687 },
5688 BracketPair {
5689 start: "(".to_string(),
5690 end: ")".to_string(),
5691 close: true,
5692 surround: true,
5693 newline: true,
5694 },
5695 BracketPair {
5696 start: "[".to_string(),
5697 end: "]".to_string(),
5698 close: false,
5699 surround: false,
5700 newline: true,
5701 },
5702 ],
5703 ..Default::default()
5704 },
5705 autoclose_before: "})]".to_string(),
5706 ..Default::default()
5707 },
5708 Some(tree_sitter_rust::LANGUAGE.into()),
5709 ));
5710
5711 cx.language_registry().add(language.clone());
5712 cx.update_buffer(|buffer, cx| {
5713 buffer.set_language(Some(language), cx);
5714 });
5715
5716 cx.set_state(
5717 &"
5718 ˇ
5719 ˇ
5720 ˇ
5721 "
5722 .unindent(),
5723 );
5724
5725 // ensure only matching closing brackets are skipped over
5726 cx.update_editor(|view, cx| {
5727 view.handle_input("}", cx);
5728 view.move_left(&MoveLeft, cx);
5729 view.handle_input(")", cx);
5730 view.move_left(&MoveLeft, cx);
5731 });
5732 cx.assert_editor_state(
5733 &"
5734 ˇ)}
5735 ˇ)}
5736 ˇ)}
5737 "
5738 .unindent(),
5739 );
5740
5741 // skip-over closing brackets at multiple cursors
5742 cx.update_editor(|view, cx| {
5743 view.handle_input(")", cx);
5744 view.handle_input("}", cx);
5745 });
5746 cx.assert_editor_state(
5747 &"
5748 )}ˇ
5749 )}ˇ
5750 )}ˇ
5751 "
5752 .unindent(),
5753 );
5754
5755 // ignore non-close brackets
5756 cx.update_editor(|view, cx| {
5757 view.handle_input("]", cx);
5758 view.move_left(&MoveLeft, cx);
5759 view.handle_input("]", cx);
5760 });
5761 cx.assert_editor_state(
5762 &"
5763 )}]ˇ]
5764 )}]ˇ]
5765 )}]ˇ]
5766 "
5767 .unindent(),
5768 );
5769}
5770
5771#[gpui::test]
5772async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5773 init_test(cx, |_| {});
5774
5775 let mut cx = EditorTestContext::new(cx).await;
5776
5777 let html_language = Arc::new(
5778 Language::new(
5779 LanguageConfig {
5780 name: "HTML".into(),
5781 brackets: BracketPairConfig {
5782 pairs: vec![
5783 BracketPair {
5784 start: "<".into(),
5785 end: ">".into(),
5786 close: true,
5787 ..Default::default()
5788 },
5789 BracketPair {
5790 start: "{".into(),
5791 end: "}".into(),
5792 close: true,
5793 ..Default::default()
5794 },
5795 BracketPair {
5796 start: "(".into(),
5797 end: ")".into(),
5798 close: true,
5799 ..Default::default()
5800 },
5801 ],
5802 ..Default::default()
5803 },
5804 autoclose_before: "})]>".into(),
5805 ..Default::default()
5806 },
5807 Some(tree_sitter_html::language()),
5808 )
5809 .with_injection_query(
5810 r#"
5811 (script_element
5812 (raw_text) @content
5813 (#set! "language" "javascript"))
5814 "#,
5815 )
5816 .unwrap(),
5817 );
5818
5819 let javascript_language = Arc::new(Language::new(
5820 LanguageConfig {
5821 name: "JavaScript".into(),
5822 brackets: BracketPairConfig {
5823 pairs: vec![
5824 BracketPair {
5825 start: "/*".into(),
5826 end: " */".into(),
5827 close: true,
5828 ..Default::default()
5829 },
5830 BracketPair {
5831 start: "{".into(),
5832 end: "}".into(),
5833 close: true,
5834 ..Default::default()
5835 },
5836 BracketPair {
5837 start: "(".into(),
5838 end: ")".into(),
5839 close: true,
5840 ..Default::default()
5841 },
5842 ],
5843 ..Default::default()
5844 },
5845 autoclose_before: "})]>".into(),
5846 ..Default::default()
5847 },
5848 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5849 ));
5850
5851 cx.language_registry().add(html_language.clone());
5852 cx.language_registry().add(javascript_language.clone());
5853
5854 cx.update_buffer(|buffer, cx| {
5855 buffer.set_language(Some(html_language), cx);
5856 });
5857
5858 cx.set_state(
5859 &r#"
5860 <body>ˇ
5861 <script>
5862 var x = 1;ˇ
5863 </script>
5864 </body>ˇ
5865 "#
5866 .unindent(),
5867 );
5868
5869 // Precondition: different languages are active at different locations.
5870 cx.update_editor(|editor, cx| {
5871 let snapshot = editor.snapshot(cx);
5872 let cursors = editor.selections.ranges::<usize>(cx);
5873 let languages = cursors
5874 .iter()
5875 .map(|c| snapshot.language_at(c.start).unwrap().name())
5876 .collect::<Vec<_>>();
5877 assert_eq!(
5878 languages,
5879 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5880 );
5881 });
5882
5883 // Angle brackets autoclose in HTML, but not JavaScript.
5884 cx.update_editor(|editor, cx| {
5885 editor.handle_input("<", cx);
5886 editor.handle_input("a", cx);
5887 });
5888 cx.assert_editor_state(
5889 &r#"
5890 <body><aˇ>
5891 <script>
5892 var x = 1;<aˇ
5893 </script>
5894 </body><aˇ>
5895 "#
5896 .unindent(),
5897 );
5898
5899 // Curly braces and parens autoclose in both HTML and JavaScript.
5900 cx.update_editor(|editor, cx| {
5901 editor.handle_input(" b=", cx);
5902 editor.handle_input("{", cx);
5903 editor.handle_input("c", cx);
5904 editor.handle_input("(", cx);
5905 });
5906 cx.assert_editor_state(
5907 &r#"
5908 <body><a b={c(ˇ)}>
5909 <script>
5910 var x = 1;<a b={c(ˇ)}
5911 </script>
5912 </body><a b={c(ˇ)}>
5913 "#
5914 .unindent(),
5915 );
5916
5917 // Brackets that were already autoclosed are skipped.
5918 cx.update_editor(|editor, cx| {
5919 editor.handle_input(")", cx);
5920 editor.handle_input("d", cx);
5921 editor.handle_input("}", cx);
5922 });
5923 cx.assert_editor_state(
5924 &r#"
5925 <body><a b={c()d}ˇ>
5926 <script>
5927 var x = 1;<a b={c()d}ˇ
5928 </script>
5929 </body><a b={c()d}ˇ>
5930 "#
5931 .unindent(),
5932 );
5933 cx.update_editor(|editor, cx| {
5934 editor.handle_input(">", cx);
5935 });
5936 cx.assert_editor_state(
5937 &r#"
5938 <body><a b={c()d}>ˇ
5939 <script>
5940 var x = 1;<a b={c()d}>ˇ
5941 </script>
5942 </body><a b={c()d}>ˇ
5943 "#
5944 .unindent(),
5945 );
5946
5947 // Reset
5948 cx.set_state(
5949 &r#"
5950 <body>ˇ
5951 <script>
5952 var x = 1;ˇ
5953 </script>
5954 </body>ˇ
5955 "#
5956 .unindent(),
5957 );
5958
5959 cx.update_editor(|editor, cx| {
5960 editor.handle_input("<", cx);
5961 });
5962 cx.assert_editor_state(
5963 &r#"
5964 <body><ˇ>
5965 <script>
5966 var x = 1;<ˇ
5967 </script>
5968 </body><ˇ>
5969 "#
5970 .unindent(),
5971 );
5972
5973 // When backspacing, the closing angle brackets are removed.
5974 cx.update_editor(|editor, cx| {
5975 editor.backspace(&Backspace, cx);
5976 });
5977 cx.assert_editor_state(
5978 &r#"
5979 <body>ˇ
5980 <script>
5981 var x = 1;ˇ
5982 </script>
5983 </body>ˇ
5984 "#
5985 .unindent(),
5986 );
5987
5988 // Block comments autoclose in JavaScript, but not HTML.
5989 cx.update_editor(|editor, cx| {
5990 editor.handle_input("/", cx);
5991 editor.handle_input("*", cx);
5992 });
5993 cx.assert_editor_state(
5994 &r#"
5995 <body>/*ˇ
5996 <script>
5997 var x = 1;/*ˇ */
5998 </script>
5999 </body>/*ˇ
6000 "#
6001 .unindent(),
6002 );
6003}
6004
6005#[gpui::test]
6006async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6007 init_test(cx, |_| {});
6008
6009 let mut cx = EditorTestContext::new(cx).await;
6010
6011 let rust_language = Arc::new(
6012 Language::new(
6013 LanguageConfig {
6014 name: "Rust".into(),
6015 brackets: serde_json::from_value(json!([
6016 { "start": "{", "end": "}", "close": true, "newline": true },
6017 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6018 ]))
6019 .unwrap(),
6020 autoclose_before: "})]>".into(),
6021 ..Default::default()
6022 },
6023 Some(tree_sitter_rust::LANGUAGE.into()),
6024 )
6025 .with_override_query("(string_literal) @string")
6026 .unwrap(),
6027 );
6028
6029 cx.language_registry().add(rust_language.clone());
6030 cx.update_buffer(|buffer, cx| {
6031 buffer.set_language(Some(rust_language), cx);
6032 });
6033
6034 cx.set_state(
6035 &r#"
6036 let x = ˇ
6037 "#
6038 .unindent(),
6039 );
6040
6041 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6042 cx.update_editor(|editor, cx| {
6043 editor.handle_input("\"", cx);
6044 });
6045 cx.assert_editor_state(
6046 &r#"
6047 let x = "ˇ"
6048 "#
6049 .unindent(),
6050 );
6051
6052 // Inserting another quotation mark. The cursor moves across the existing
6053 // automatically-inserted quotation mark.
6054 cx.update_editor(|editor, cx| {
6055 editor.handle_input("\"", cx);
6056 });
6057 cx.assert_editor_state(
6058 &r#"
6059 let x = ""ˇ
6060 "#
6061 .unindent(),
6062 );
6063
6064 // Reset
6065 cx.set_state(
6066 &r#"
6067 let x = ˇ
6068 "#
6069 .unindent(),
6070 );
6071
6072 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6073 cx.update_editor(|editor, cx| {
6074 editor.handle_input("\"", cx);
6075 editor.handle_input(" ", cx);
6076 editor.move_left(&Default::default(), cx);
6077 editor.handle_input("\\", cx);
6078 editor.handle_input("\"", cx);
6079 });
6080 cx.assert_editor_state(
6081 &r#"
6082 let x = "\"ˇ "
6083 "#
6084 .unindent(),
6085 );
6086
6087 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6088 // mark. Nothing is inserted.
6089 cx.update_editor(|editor, cx| {
6090 editor.move_right(&Default::default(), cx);
6091 editor.handle_input("\"", cx);
6092 });
6093 cx.assert_editor_state(
6094 &r#"
6095 let x = "\" "ˇ
6096 "#
6097 .unindent(),
6098 );
6099}
6100
6101#[gpui::test]
6102async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6103 init_test(cx, |_| {});
6104
6105 let language = Arc::new(Language::new(
6106 LanguageConfig {
6107 brackets: BracketPairConfig {
6108 pairs: vec![
6109 BracketPair {
6110 start: "{".to_string(),
6111 end: "}".to_string(),
6112 close: true,
6113 surround: true,
6114 newline: true,
6115 },
6116 BracketPair {
6117 start: "/* ".to_string(),
6118 end: "*/".to_string(),
6119 close: true,
6120 surround: true,
6121 ..Default::default()
6122 },
6123 ],
6124 ..Default::default()
6125 },
6126 ..Default::default()
6127 },
6128 Some(tree_sitter_rust::LANGUAGE.into()),
6129 ));
6130
6131 let text = r#"
6132 a
6133 b
6134 c
6135 "#
6136 .unindent();
6137
6138 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6139 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6140 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6141 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6142 .await;
6143
6144 view.update(cx, |view, cx| {
6145 view.change_selections(None, cx, |s| {
6146 s.select_display_ranges([
6147 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6148 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6149 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6150 ])
6151 });
6152
6153 view.handle_input("{", cx);
6154 view.handle_input("{", cx);
6155 view.handle_input("{", cx);
6156 assert_eq!(
6157 view.text(cx),
6158 "
6159 {{{a}}}
6160 {{{b}}}
6161 {{{c}}}
6162 "
6163 .unindent()
6164 );
6165 assert_eq!(
6166 view.selections.display_ranges(cx),
6167 [
6168 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6169 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6170 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6171 ]
6172 );
6173
6174 view.undo(&Undo, cx);
6175 view.undo(&Undo, cx);
6176 view.undo(&Undo, cx);
6177 assert_eq!(
6178 view.text(cx),
6179 "
6180 a
6181 b
6182 c
6183 "
6184 .unindent()
6185 );
6186 assert_eq!(
6187 view.selections.display_ranges(cx),
6188 [
6189 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6190 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6191 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6192 ]
6193 );
6194
6195 // Ensure inserting the first character of a multi-byte bracket pair
6196 // doesn't surround the selections with the bracket.
6197 view.handle_input("/", cx);
6198 assert_eq!(
6199 view.text(cx),
6200 "
6201 /
6202 /
6203 /
6204 "
6205 .unindent()
6206 );
6207 assert_eq!(
6208 view.selections.display_ranges(cx),
6209 [
6210 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6211 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6212 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6213 ]
6214 );
6215
6216 view.undo(&Undo, cx);
6217 assert_eq!(
6218 view.text(cx),
6219 "
6220 a
6221 b
6222 c
6223 "
6224 .unindent()
6225 );
6226 assert_eq!(
6227 view.selections.display_ranges(cx),
6228 [
6229 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6230 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6231 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6232 ]
6233 );
6234
6235 // Ensure inserting the last character of a multi-byte bracket pair
6236 // doesn't surround the selections with the bracket.
6237 view.handle_input("*", cx);
6238 assert_eq!(
6239 view.text(cx),
6240 "
6241 *
6242 *
6243 *
6244 "
6245 .unindent()
6246 );
6247 assert_eq!(
6248 view.selections.display_ranges(cx),
6249 [
6250 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6251 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6252 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6253 ]
6254 );
6255 });
6256}
6257
6258#[gpui::test]
6259async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6260 init_test(cx, |_| {});
6261
6262 let language = Arc::new(Language::new(
6263 LanguageConfig {
6264 brackets: BracketPairConfig {
6265 pairs: vec![BracketPair {
6266 start: "{".to_string(),
6267 end: "}".to_string(),
6268 close: true,
6269 surround: true,
6270 newline: true,
6271 }],
6272 ..Default::default()
6273 },
6274 autoclose_before: "}".to_string(),
6275 ..Default::default()
6276 },
6277 Some(tree_sitter_rust::LANGUAGE.into()),
6278 ));
6279
6280 let text = r#"
6281 a
6282 b
6283 c
6284 "#
6285 .unindent();
6286
6287 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6288 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6289 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6290 editor
6291 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6292 .await;
6293
6294 editor.update(cx, |editor, cx| {
6295 editor.change_selections(None, cx, |s| {
6296 s.select_ranges([
6297 Point::new(0, 1)..Point::new(0, 1),
6298 Point::new(1, 1)..Point::new(1, 1),
6299 Point::new(2, 1)..Point::new(2, 1),
6300 ])
6301 });
6302
6303 editor.handle_input("{", cx);
6304 editor.handle_input("{", cx);
6305 editor.handle_input("_", cx);
6306 assert_eq!(
6307 editor.text(cx),
6308 "
6309 a{{_}}
6310 b{{_}}
6311 c{{_}}
6312 "
6313 .unindent()
6314 );
6315 assert_eq!(
6316 editor.selections.ranges::<Point>(cx),
6317 [
6318 Point::new(0, 4)..Point::new(0, 4),
6319 Point::new(1, 4)..Point::new(1, 4),
6320 Point::new(2, 4)..Point::new(2, 4)
6321 ]
6322 );
6323
6324 editor.backspace(&Default::default(), cx);
6325 editor.backspace(&Default::default(), cx);
6326 assert_eq!(
6327 editor.text(cx),
6328 "
6329 a{}
6330 b{}
6331 c{}
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 editor.selections.ranges::<Point>(cx),
6337 [
6338 Point::new(0, 2)..Point::new(0, 2),
6339 Point::new(1, 2)..Point::new(1, 2),
6340 Point::new(2, 2)..Point::new(2, 2)
6341 ]
6342 );
6343
6344 editor.delete_to_previous_word_start(&Default::default(), cx);
6345 assert_eq!(
6346 editor.text(cx),
6347 "
6348 a
6349 b
6350 c
6351 "
6352 .unindent()
6353 );
6354 assert_eq!(
6355 editor.selections.ranges::<Point>(cx),
6356 [
6357 Point::new(0, 1)..Point::new(0, 1),
6358 Point::new(1, 1)..Point::new(1, 1),
6359 Point::new(2, 1)..Point::new(2, 1)
6360 ]
6361 );
6362 });
6363}
6364
6365#[gpui::test]
6366async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6367 init_test(cx, |settings| {
6368 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6369 });
6370
6371 let mut cx = EditorTestContext::new(cx).await;
6372
6373 let language = Arc::new(Language::new(
6374 LanguageConfig {
6375 brackets: BracketPairConfig {
6376 pairs: vec![
6377 BracketPair {
6378 start: "{".to_string(),
6379 end: "}".to_string(),
6380 close: true,
6381 surround: true,
6382 newline: true,
6383 },
6384 BracketPair {
6385 start: "(".to_string(),
6386 end: ")".to_string(),
6387 close: true,
6388 surround: true,
6389 newline: true,
6390 },
6391 BracketPair {
6392 start: "[".to_string(),
6393 end: "]".to_string(),
6394 close: false,
6395 surround: true,
6396 newline: true,
6397 },
6398 ],
6399 ..Default::default()
6400 },
6401 autoclose_before: "})]".to_string(),
6402 ..Default::default()
6403 },
6404 Some(tree_sitter_rust::LANGUAGE.into()),
6405 ));
6406
6407 cx.language_registry().add(language.clone());
6408 cx.update_buffer(|buffer, cx| {
6409 buffer.set_language(Some(language), cx);
6410 });
6411
6412 cx.set_state(
6413 &"
6414 {(ˇ)}
6415 [[ˇ]]
6416 {(ˇ)}
6417 "
6418 .unindent(),
6419 );
6420
6421 cx.update_editor(|view, cx| {
6422 view.backspace(&Default::default(), cx);
6423 view.backspace(&Default::default(), cx);
6424 });
6425
6426 cx.assert_editor_state(
6427 &"
6428 ˇ
6429 ˇ]]
6430 ˇ
6431 "
6432 .unindent(),
6433 );
6434
6435 cx.update_editor(|view, cx| {
6436 view.handle_input("{", cx);
6437 view.handle_input("{", cx);
6438 view.move_right(&MoveRight, cx);
6439 view.move_right(&MoveRight, cx);
6440 view.move_left(&MoveLeft, cx);
6441 view.move_left(&MoveLeft, cx);
6442 view.backspace(&Default::default(), cx);
6443 });
6444
6445 cx.assert_editor_state(
6446 &"
6447 {ˇ}
6448 {ˇ}]]
6449 {ˇ}
6450 "
6451 .unindent(),
6452 );
6453
6454 cx.update_editor(|view, cx| {
6455 view.backspace(&Default::default(), cx);
6456 });
6457
6458 cx.assert_editor_state(
6459 &"
6460 ˇ
6461 ˇ]]
6462 ˇ
6463 "
6464 .unindent(),
6465 );
6466}
6467
6468#[gpui::test]
6469async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6470 init_test(cx, |_| {});
6471
6472 let language = Arc::new(Language::new(
6473 LanguageConfig::default(),
6474 Some(tree_sitter_rust::LANGUAGE.into()),
6475 ));
6476
6477 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6478 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6479 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6480 editor
6481 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6482 .await;
6483
6484 editor.update(cx, |editor, cx| {
6485 editor.set_auto_replace_emoji_shortcode(true);
6486
6487 editor.handle_input("Hello ", cx);
6488 editor.handle_input(":wave", cx);
6489 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6490
6491 editor.handle_input(":", cx);
6492 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6493
6494 editor.handle_input(" :smile", cx);
6495 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6496
6497 editor.handle_input(":", cx);
6498 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6499
6500 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6501 editor.handle_input(":wave", cx);
6502 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6503
6504 editor.handle_input(":", cx);
6505 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6506
6507 editor.handle_input(":1", cx);
6508 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6509
6510 editor.handle_input(":", cx);
6511 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6512
6513 // Ensure shortcode does not get replaced when it is part of a word
6514 editor.handle_input(" Test:wave", cx);
6515 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6516
6517 editor.handle_input(":", cx);
6518 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6519
6520 editor.set_auto_replace_emoji_shortcode(false);
6521
6522 // Ensure shortcode does not get replaced when auto replace is off
6523 editor.handle_input(" :wave", cx);
6524 assert_eq!(
6525 editor.text(cx),
6526 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6527 );
6528
6529 editor.handle_input(":", cx);
6530 assert_eq!(
6531 editor.text(cx),
6532 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6533 );
6534 });
6535}
6536
6537#[gpui::test]
6538async fn test_snippets(cx: &mut gpui::TestAppContext) {
6539 init_test(cx, |_| {});
6540
6541 let (text, insertion_ranges) = marked_text_ranges(
6542 indoc! {"
6543 a.ˇ b
6544 a.ˇ b
6545 a.ˇ b
6546 "},
6547 false,
6548 );
6549
6550 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6551 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6552
6553 editor.update(cx, |editor, cx| {
6554 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6555
6556 editor
6557 .insert_snippet(&insertion_ranges, snippet, cx)
6558 .unwrap();
6559
6560 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6561 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6562 assert_eq!(editor.text(cx), expected_text);
6563 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6564 }
6565
6566 assert(
6567 editor,
6568 cx,
6569 indoc! {"
6570 a.f(«one», two, «three») b
6571 a.f(«one», two, «three») b
6572 a.f(«one», two, «three») b
6573 "},
6574 );
6575
6576 // Can't move earlier than the first tab stop
6577 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6578 assert(
6579 editor,
6580 cx,
6581 indoc! {"
6582 a.f(«one», two, «three») b
6583 a.f(«one», two, «three») b
6584 a.f(«one», two, «three») b
6585 "},
6586 );
6587
6588 assert!(editor.move_to_next_snippet_tabstop(cx));
6589 assert(
6590 editor,
6591 cx,
6592 indoc! {"
6593 a.f(one, «two», three) b
6594 a.f(one, «two», three) b
6595 a.f(one, «two», three) b
6596 "},
6597 );
6598
6599 editor.move_to_prev_snippet_tabstop(cx);
6600 assert(
6601 editor,
6602 cx,
6603 indoc! {"
6604 a.f(«one», two, «three») b
6605 a.f(«one», two, «three») b
6606 a.f(«one», two, «three») b
6607 "},
6608 );
6609
6610 assert!(editor.move_to_next_snippet_tabstop(cx));
6611 assert(
6612 editor,
6613 cx,
6614 indoc! {"
6615 a.f(one, «two», three) b
6616 a.f(one, «two», three) b
6617 a.f(one, «two», three) b
6618 "},
6619 );
6620 assert!(editor.move_to_next_snippet_tabstop(cx));
6621 assert(
6622 editor,
6623 cx,
6624 indoc! {"
6625 a.f(one, two, three)ˇ b
6626 a.f(one, two, three)ˇ b
6627 a.f(one, two, three)ˇ b
6628 "},
6629 );
6630
6631 // As soon as the last tab stop is reached, snippet state is gone
6632 editor.move_to_prev_snippet_tabstop(cx);
6633 assert(
6634 editor,
6635 cx,
6636 indoc! {"
6637 a.f(one, two, three)ˇ b
6638 a.f(one, two, three)ˇ b
6639 a.f(one, two, three)ˇ b
6640 "},
6641 );
6642 });
6643}
6644
6645#[gpui::test]
6646async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6647 init_test(cx, |_| {});
6648
6649 let fs = FakeFs::new(cx.executor());
6650 fs.insert_file("/file.rs", Default::default()).await;
6651
6652 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6653
6654 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6655 language_registry.add(rust_lang());
6656 let mut fake_servers = language_registry.register_fake_lsp(
6657 "Rust",
6658 FakeLspAdapter {
6659 capabilities: lsp::ServerCapabilities {
6660 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6661 ..Default::default()
6662 },
6663 ..Default::default()
6664 },
6665 );
6666
6667 let buffer = project
6668 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6669 .await
6670 .unwrap();
6671
6672 cx.executor().start_waiting();
6673 let fake_server = fake_servers.next().await.unwrap();
6674
6675 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6676 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6677 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6678 assert!(cx.read(|cx| editor.is_dirty(cx)));
6679
6680 let save = editor
6681 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6682 .unwrap();
6683 fake_server
6684 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6685 assert_eq!(
6686 params.text_document.uri,
6687 lsp::Url::from_file_path("/file.rs").unwrap()
6688 );
6689 assert_eq!(params.options.tab_size, 4);
6690 Ok(Some(vec![lsp::TextEdit::new(
6691 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6692 ", ".to_string(),
6693 )]))
6694 })
6695 .next()
6696 .await;
6697 cx.executor().start_waiting();
6698 save.await;
6699
6700 assert_eq!(
6701 editor.update(cx, |editor, cx| editor.text(cx)),
6702 "one, two\nthree\n"
6703 );
6704 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6705
6706 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6707 assert!(cx.read(|cx| editor.is_dirty(cx)));
6708
6709 // Ensure we can still save even if formatting hangs.
6710 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6711 assert_eq!(
6712 params.text_document.uri,
6713 lsp::Url::from_file_path("/file.rs").unwrap()
6714 );
6715 futures::future::pending::<()>().await;
6716 unreachable!()
6717 });
6718 let save = editor
6719 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6720 .unwrap();
6721 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6722 cx.executor().start_waiting();
6723 save.await;
6724 assert_eq!(
6725 editor.update(cx, |editor, cx| editor.text(cx)),
6726 "one\ntwo\nthree\n"
6727 );
6728 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6729
6730 // For non-dirty buffer, no formatting request should be sent
6731 let save = editor
6732 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6733 .unwrap();
6734 let _pending_format_request = fake_server
6735 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6736 panic!("Should not be invoked on non-dirty buffer");
6737 })
6738 .next();
6739 cx.executor().start_waiting();
6740 save.await;
6741
6742 // Set rust language override and assert overridden tabsize is sent to language server
6743 update_test_language_settings(cx, |settings| {
6744 settings.languages.insert(
6745 "Rust".into(),
6746 LanguageSettingsContent {
6747 tab_size: NonZeroU32::new(8),
6748 ..Default::default()
6749 },
6750 );
6751 });
6752
6753 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6754 assert!(cx.read(|cx| editor.is_dirty(cx)));
6755 let save = editor
6756 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6757 .unwrap();
6758 fake_server
6759 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6760 assert_eq!(
6761 params.text_document.uri,
6762 lsp::Url::from_file_path("/file.rs").unwrap()
6763 );
6764 assert_eq!(params.options.tab_size, 8);
6765 Ok(Some(vec![]))
6766 })
6767 .next()
6768 .await;
6769 cx.executor().start_waiting();
6770 save.await;
6771}
6772
6773#[gpui::test]
6774async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6775 init_test(cx, |_| {});
6776
6777 let cols = 4;
6778 let rows = 10;
6779 let sample_text_1 = sample_text(rows, cols, 'a');
6780 assert_eq!(
6781 sample_text_1,
6782 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6783 );
6784 let sample_text_2 = sample_text(rows, cols, 'l');
6785 assert_eq!(
6786 sample_text_2,
6787 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6788 );
6789 let sample_text_3 = sample_text(rows, cols, 'v');
6790 assert_eq!(
6791 sample_text_3,
6792 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6793 );
6794
6795 let fs = FakeFs::new(cx.executor());
6796 fs.insert_tree(
6797 "/a",
6798 json!({
6799 "main.rs": sample_text_1,
6800 "other.rs": sample_text_2,
6801 "lib.rs": sample_text_3,
6802 }),
6803 )
6804 .await;
6805
6806 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6807 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6808 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6809
6810 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6811 language_registry.add(rust_lang());
6812 let mut fake_servers = language_registry.register_fake_lsp(
6813 "Rust",
6814 FakeLspAdapter {
6815 capabilities: lsp::ServerCapabilities {
6816 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6817 ..Default::default()
6818 },
6819 ..Default::default()
6820 },
6821 );
6822
6823 let worktree = project.update(cx, |project, cx| {
6824 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6825 assert_eq!(worktrees.len(), 1);
6826 worktrees.pop().unwrap()
6827 });
6828 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6829
6830 let buffer_1 = project
6831 .update(cx, |project, cx| {
6832 project.open_buffer((worktree_id, "main.rs"), cx)
6833 })
6834 .await
6835 .unwrap();
6836 let buffer_2 = project
6837 .update(cx, |project, cx| {
6838 project.open_buffer((worktree_id, "other.rs"), cx)
6839 })
6840 .await
6841 .unwrap();
6842 let buffer_3 = project
6843 .update(cx, |project, cx| {
6844 project.open_buffer((worktree_id, "lib.rs"), cx)
6845 })
6846 .await
6847 .unwrap();
6848
6849 let multi_buffer = cx.new_model(|cx| {
6850 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6851 multi_buffer.push_excerpts(
6852 buffer_1.clone(),
6853 [
6854 ExcerptRange {
6855 context: Point::new(0, 0)..Point::new(3, 0),
6856 primary: None,
6857 },
6858 ExcerptRange {
6859 context: Point::new(5, 0)..Point::new(7, 0),
6860 primary: None,
6861 },
6862 ExcerptRange {
6863 context: Point::new(9, 0)..Point::new(10, 4),
6864 primary: None,
6865 },
6866 ],
6867 cx,
6868 );
6869 multi_buffer.push_excerpts(
6870 buffer_2.clone(),
6871 [
6872 ExcerptRange {
6873 context: Point::new(0, 0)..Point::new(3, 0),
6874 primary: None,
6875 },
6876 ExcerptRange {
6877 context: Point::new(5, 0)..Point::new(7, 0),
6878 primary: None,
6879 },
6880 ExcerptRange {
6881 context: Point::new(9, 0)..Point::new(10, 4),
6882 primary: None,
6883 },
6884 ],
6885 cx,
6886 );
6887 multi_buffer.push_excerpts(
6888 buffer_3.clone(),
6889 [
6890 ExcerptRange {
6891 context: Point::new(0, 0)..Point::new(3, 0),
6892 primary: None,
6893 },
6894 ExcerptRange {
6895 context: Point::new(5, 0)..Point::new(7, 0),
6896 primary: None,
6897 },
6898 ExcerptRange {
6899 context: Point::new(9, 0)..Point::new(10, 4),
6900 primary: None,
6901 },
6902 ],
6903 cx,
6904 );
6905 multi_buffer
6906 });
6907 let multi_buffer_editor = cx.new_view(|cx| {
6908 Editor::new(
6909 EditorMode::Full,
6910 multi_buffer,
6911 Some(project.clone()),
6912 true,
6913 cx,
6914 )
6915 });
6916
6917 multi_buffer_editor.update(cx, |editor, cx| {
6918 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6919 editor.insert("|one|two|three|", cx);
6920 });
6921 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6922 multi_buffer_editor.update(cx, |editor, cx| {
6923 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6924 s.select_ranges(Some(60..70))
6925 });
6926 editor.insert("|four|five|six|", cx);
6927 });
6928 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6929
6930 // First two buffers should be edited, but not the third one.
6931 assert_eq!(
6932 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6933 "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}",
6934 );
6935 buffer_1.update(cx, |buffer, _| {
6936 assert!(buffer.is_dirty());
6937 assert_eq!(
6938 buffer.text(),
6939 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6940 )
6941 });
6942 buffer_2.update(cx, |buffer, _| {
6943 assert!(buffer.is_dirty());
6944 assert_eq!(
6945 buffer.text(),
6946 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6947 )
6948 });
6949 buffer_3.update(cx, |buffer, _| {
6950 assert!(!buffer.is_dirty());
6951 assert_eq!(buffer.text(), sample_text_3,)
6952 });
6953
6954 cx.executor().start_waiting();
6955 let save = multi_buffer_editor
6956 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6957 .unwrap();
6958
6959 let fake_server = fake_servers.next().await.unwrap();
6960 fake_server
6961 .server
6962 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6963 Ok(Some(vec![lsp::TextEdit::new(
6964 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6965 format!("[{} formatted]", params.text_document.uri),
6966 )]))
6967 })
6968 .detach();
6969 save.await;
6970
6971 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6972 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6973 assert_eq!(
6974 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6975 "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}",
6976 );
6977 buffer_1.update(cx, |buffer, _| {
6978 assert!(!buffer.is_dirty());
6979 assert_eq!(
6980 buffer.text(),
6981 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6982 )
6983 });
6984 buffer_2.update(cx, |buffer, _| {
6985 assert!(!buffer.is_dirty());
6986 assert_eq!(
6987 buffer.text(),
6988 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6989 )
6990 });
6991 buffer_3.update(cx, |buffer, _| {
6992 assert!(!buffer.is_dirty());
6993 assert_eq!(buffer.text(), sample_text_3,)
6994 });
6995}
6996
6997#[gpui::test]
6998async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6999 init_test(cx, |_| {});
7000
7001 let fs = FakeFs::new(cx.executor());
7002 fs.insert_file("/file.rs", Default::default()).await;
7003
7004 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7005
7006 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7007 language_registry.add(rust_lang());
7008 let mut fake_servers = language_registry.register_fake_lsp(
7009 "Rust",
7010 FakeLspAdapter {
7011 capabilities: lsp::ServerCapabilities {
7012 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7013 ..Default::default()
7014 },
7015 ..Default::default()
7016 },
7017 );
7018
7019 let buffer = project
7020 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7021 .await
7022 .unwrap();
7023
7024 cx.executor().start_waiting();
7025 let fake_server = fake_servers.next().await.unwrap();
7026
7027 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7028 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7029 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7030 assert!(cx.read(|cx| editor.is_dirty(cx)));
7031
7032 let save = editor
7033 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7034 .unwrap();
7035 fake_server
7036 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7037 assert_eq!(
7038 params.text_document.uri,
7039 lsp::Url::from_file_path("/file.rs").unwrap()
7040 );
7041 assert_eq!(params.options.tab_size, 4);
7042 Ok(Some(vec![lsp::TextEdit::new(
7043 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7044 ", ".to_string(),
7045 )]))
7046 })
7047 .next()
7048 .await;
7049 cx.executor().start_waiting();
7050 save.await;
7051 assert_eq!(
7052 editor.update(cx, |editor, cx| editor.text(cx)),
7053 "one, two\nthree\n"
7054 );
7055 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7056
7057 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7058 assert!(cx.read(|cx| editor.is_dirty(cx)));
7059
7060 // Ensure we can still save even if formatting hangs.
7061 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7062 move |params, _| async move {
7063 assert_eq!(
7064 params.text_document.uri,
7065 lsp::Url::from_file_path("/file.rs").unwrap()
7066 );
7067 futures::future::pending::<()>().await;
7068 unreachable!()
7069 },
7070 );
7071 let save = editor
7072 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7073 .unwrap();
7074 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7075 cx.executor().start_waiting();
7076 save.await;
7077 assert_eq!(
7078 editor.update(cx, |editor, cx| editor.text(cx)),
7079 "one\ntwo\nthree\n"
7080 );
7081 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7082
7083 // For non-dirty buffer, no formatting request should be sent
7084 let save = editor
7085 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7086 .unwrap();
7087 let _pending_format_request = fake_server
7088 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7089 panic!("Should not be invoked on non-dirty buffer");
7090 })
7091 .next();
7092 cx.executor().start_waiting();
7093 save.await;
7094
7095 // Set Rust language override and assert overridden tabsize is sent to language server
7096 update_test_language_settings(cx, |settings| {
7097 settings.languages.insert(
7098 "Rust".into(),
7099 LanguageSettingsContent {
7100 tab_size: NonZeroU32::new(8),
7101 ..Default::default()
7102 },
7103 );
7104 });
7105
7106 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7107 assert!(cx.read(|cx| editor.is_dirty(cx)));
7108 let save = editor
7109 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7110 .unwrap();
7111 fake_server
7112 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7113 assert_eq!(
7114 params.text_document.uri,
7115 lsp::Url::from_file_path("/file.rs").unwrap()
7116 );
7117 assert_eq!(params.options.tab_size, 8);
7118 Ok(Some(vec![]))
7119 })
7120 .next()
7121 .await;
7122 cx.executor().start_waiting();
7123 save.await;
7124}
7125
7126#[gpui::test]
7127async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7128 init_test(cx, |settings| {
7129 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7130 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7131 ))
7132 });
7133
7134 let fs = FakeFs::new(cx.executor());
7135 fs.insert_file("/file.rs", Default::default()).await;
7136
7137 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7138
7139 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7140 language_registry.add(Arc::new(Language::new(
7141 LanguageConfig {
7142 name: "Rust".into(),
7143 matcher: LanguageMatcher {
7144 path_suffixes: vec!["rs".to_string()],
7145 ..Default::default()
7146 },
7147 ..LanguageConfig::default()
7148 },
7149 Some(tree_sitter_rust::LANGUAGE.into()),
7150 )));
7151 update_test_language_settings(cx, |settings| {
7152 // Enable Prettier formatting for the same buffer, and ensure
7153 // LSP is called instead of Prettier.
7154 settings.defaults.prettier = Some(PrettierSettings {
7155 allowed: true,
7156 ..PrettierSettings::default()
7157 });
7158 });
7159 let mut fake_servers = language_registry.register_fake_lsp(
7160 "Rust",
7161 FakeLspAdapter {
7162 capabilities: lsp::ServerCapabilities {
7163 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7164 ..Default::default()
7165 },
7166 ..Default::default()
7167 },
7168 );
7169
7170 let buffer = project
7171 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7172 .await
7173 .unwrap();
7174
7175 cx.executor().start_waiting();
7176 let fake_server = fake_servers.next().await.unwrap();
7177
7178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7179 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7180 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7181
7182 let format = editor
7183 .update(cx, |editor, cx| {
7184 editor.perform_format(
7185 project.clone(),
7186 FormatTrigger::Manual,
7187 FormatTarget::Buffer,
7188 cx,
7189 )
7190 })
7191 .unwrap();
7192 fake_server
7193 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7194 assert_eq!(
7195 params.text_document.uri,
7196 lsp::Url::from_file_path("/file.rs").unwrap()
7197 );
7198 assert_eq!(params.options.tab_size, 4);
7199 Ok(Some(vec![lsp::TextEdit::new(
7200 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7201 ", ".to_string(),
7202 )]))
7203 })
7204 .next()
7205 .await;
7206 cx.executor().start_waiting();
7207 format.await;
7208 assert_eq!(
7209 editor.update(cx, |editor, cx| editor.text(cx)),
7210 "one, two\nthree\n"
7211 );
7212
7213 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7214 // Ensure we don't lock if formatting hangs.
7215 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7216 assert_eq!(
7217 params.text_document.uri,
7218 lsp::Url::from_file_path("/file.rs").unwrap()
7219 );
7220 futures::future::pending::<()>().await;
7221 unreachable!()
7222 });
7223 let format = editor
7224 .update(cx, |editor, cx| {
7225 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7226 })
7227 .unwrap();
7228 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7229 cx.executor().start_waiting();
7230 format.await;
7231 assert_eq!(
7232 editor.update(cx, |editor, cx| editor.text(cx)),
7233 "one\ntwo\nthree\n"
7234 );
7235}
7236
7237#[gpui::test]
7238async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7239 init_test(cx, |_| {});
7240
7241 let mut cx = EditorLspTestContext::new_rust(
7242 lsp::ServerCapabilities {
7243 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7244 ..Default::default()
7245 },
7246 cx,
7247 )
7248 .await;
7249
7250 cx.set_state(indoc! {"
7251 one.twoˇ
7252 "});
7253
7254 // The format request takes a long time. When it completes, it inserts
7255 // a newline and an indent before the `.`
7256 cx.lsp
7257 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7258 let executor = cx.background_executor().clone();
7259 async move {
7260 executor.timer(Duration::from_millis(100)).await;
7261 Ok(Some(vec![lsp::TextEdit {
7262 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7263 new_text: "\n ".into(),
7264 }]))
7265 }
7266 });
7267
7268 // Submit a format request.
7269 let format_1 = cx
7270 .update_editor(|editor, cx| editor.format(&Format, cx))
7271 .unwrap();
7272 cx.executor().run_until_parked();
7273
7274 // Submit a second format request.
7275 let format_2 = cx
7276 .update_editor(|editor, cx| editor.format(&Format, cx))
7277 .unwrap();
7278 cx.executor().run_until_parked();
7279
7280 // Wait for both format requests to complete
7281 cx.executor().advance_clock(Duration::from_millis(200));
7282 cx.executor().start_waiting();
7283 format_1.await.unwrap();
7284 cx.executor().start_waiting();
7285 format_2.await.unwrap();
7286
7287 // The formatting edits only happens once.
7288 cx.assert_editor_state(indoc! {"
7289 one
7290 .twoˇ
7291 "});
7292}
7293
7294#[gpui::test]
7295async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7296 init_test(cx, |settings| {
7297 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7298 });
7299
7300 let mut cx = EditorLspTestContext::new_rust(
7301 lsp::ServerCapabilities {
7302 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7303 ..Default::default()
7304 },
7305 cx,
7306 )
7307 .await;
7308
7309 // Set up a buffer white some trailing whitespace and no trailing newline.
7310 cx.set_state(
7311 &[
7312 "one ", //
7313 "twoˇ", //
7314 "three ", //
7315 "four", //
7316 ]
7317 .join("\n"),
7318 );
7319
7320 // Submit a format request.
7321 let format = cx
7322 .update_editor(|editor, cx| editor.format(&Format, cx))
7323 .unwrap();
7324
7325 // Record which buffer changes have been sent to the language server
7326 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7327 cx.lsp
7328 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7329 let buffer_changes = buffer_changes.clone();
7330 move |params, _| {
7331 buffer_changes.lock().extend(
7332 params
7333 .content_changes
7334 .into_iter()
7335 .map(|e| (e.range.unwrap(), e.text)),
7336 );
7337 }
7338 });
7339
7340 // Handle formatting requests to the language server.
7341 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7342 let buffer_changes = buffer_changes.clone();
7343 move |_, _| {
7344 // When formatting is requested, trailing whitespace has already been stripped,
7345 // and the trailing newline has already been added.
7346 assert_eq!(
7347 &buffer_changes.lock()[1..],
7348 &[
7349 (
7350 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7351 "".into()
7352 ),
7353 (
7354 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7355 "".into()
7356 ),
7357 (
7358 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7359 "\n".into()
7360 ),
7361 ]
7362 );
7363
7364 // Insert blank lines between each line of the buffer.
7365 async move {
7366 Ok(Some(vec![
7367 lsp::TextEdit {
7368 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7369 new_text: "\n".into(),
7370 },
7371 lsp::TextEdit {
7372 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7373 new_text: "\n".into(),
7374 },
7375 ]))
7376 }
7377 }
7378 });
7379
7380 // After formatting the buffer, the trailing whitespace is stripped,
7381 // a newline is appended, and the edits provided by the language server
7382 // have been applied.
7383 format.await.unwrap();
7384 cx.assert_editor_state(
7385 &[
7386 "one", //
7387 "", //
7388 "twoˇ", //
7389 "", //
7390 "three", //
7391 "four", //
7392 "", //
7393 ]
7394 .join("\n"),
7395 );
7396
7397 // Undoing the formatting undoes the trailing whitespace removal, the
7398 // trailing newline, and the LSP edits.
7399 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7400 cx.assert_editor_state(
7401 &[
7402 "one ", //
7403 "twoˇ", //
7404 "three ", //
7405 "four", //
7406 ]
7407 .join("\n"),
7408 );
7409}
7410
7411#[gpui::test]
7412async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7413 cx: &mut gpui::TestAppContext,
7414) {
7415 init_test(cx, |_| {});
7416
7417 cx.update(|cx| {
7418 cx.update_global::<SettingsStore, _>(|settings, cx| {
7419 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7420 settings.auto_signature_help = Some(true);
7421 });
7422 });
7423 });
7424
7425 let mut cx = EditorLspTestContext::new_rust(
7426 lsp::ServerCapabilities {
7427 signature_help_provider: Some(lsp::SignatureHelpOptions {
7428 ..Default::default()
7429 }),
7430 ..Default::default()
7431 },
7432 cx,
7433 )
7434 .await;
7435
7436 let language = Language::new(
7437 LanguageConfig {
7438 name: "Rust".into(),
7439 brackets: BracketPairConfig {
7440 pairs: vec![
7441 BracketPair {
7442 start: "{".to_string(),
7443 end: "}".to_string(),
7444 close: true,
7445 surround: true,
7446 newline: true,
7447 },
7448 BracketPair {
7449 start: "(".to_string(),
7450 end: ")".to_string(),
7451 close: true,
7452 surround: true,
7453 newline: true,
7454 },
7455 BracketPair {
7456 start: "/*".to_string(),
7457 end: " */".to_string(),
7458 close: true,
7459 surround: true,
7460 newline: true,
7461 },
7462 BracketPair {
7463 start: "[".to_string(),
7464 end: "]".to_string(),
7465 close: false,
7466 surround: false,
7467 newline: true,
7468 },
7469 BracketPair {
7470 start: "\"".to_string(),
7471 end: "\"".to_string(),
7472 close: true,
7473 surround: true,
7474 newline: false,
7475 },
7476 BracketPair {
7477 start: "<".to_string(),
7478 end: ">".to_string(),
7479 close: false,
7480 surround: true,
7481 newline: true,
7482 },
7483 ],
7484 ..Default::default()
7485 },
7486 autoclose_before: "})]".to_string(),
7487 ..Default::default()
7488 },
7489 Some(tree_sitter_rust::LANGUAGE.into()),
7490 );
7491 let language = Arc::new(language);
7492
7493 cx.language_registry().add(language.clone());
7494 cx.update_buffer(|buffer, cx| {
7495 buffer.set_language(Some(language), cx);
7496 });
7497
7498 cx.set_state(
7499 &r#"
7500 fn main() {
7501 sampleˇ
7502 }
7503 "#
7504 .unindent(),
7505 );
7506
7507 cx.update_editor(|view, cx| {
7508 view.handle_input("(", cx);
7509 });
7510 cx.assert_editor_state(
7511 &"
7512 fn main() {
7513 sample(ˇ)
7514 }
7515 "
7516 .unindent(),
7517 );
7518
7519 let mocked_response = lsp::SignatureHelp {
7520 signatures: vec![lsp::SignatureInformation {
7521 label: "fn sample(param1: u8, param2: u8)".to_string(),
7522 documentation: None,
7523 parameters: Some(vec![
7524 lsp::ParameterInformation {
7525 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7526 documentation: None,
7527 },
7528 lsp::ParameterInformation {
7529 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7530 documentation: None,
7531 },
7532 ]),
7533 active_parameter: None,
7534 }],
7535 active_signature: Some(0),
7536 active_parameter: Some(0),
7537 };
7538 handle_signature_help_request(&mut cx, mocked_response).await;
7539
7540 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7541 .await;
7542
7543 cx.editor(|editor, _| {
7544 let signature_help_state = editor.signature_help_state.popover().cloned();
7545 assert!(signature_help_state.is_some());
7546 let ParsedMarkdown {
7547 text, highlights, ..
7548 } = signature_help_state.unwrap().parsed_content;
7549 assert_eq!(text, "param1: u8, param2: u8");
7550 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7551 });
7552}
7553
7554#[gpui::test]
7555async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7556 init_test(cx, |_| {});
7557
7558 cx.update(|cx| {
7559 cx.update_global::<SettingsStore, _>(|settings, cx| {
7560 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7561 settings.auto_signature_help = Some(false);
7562 settings.show_signature_help_after_edits = Some(false);
7563 });
7564 });
7565 });
7566
7567 let mut cx = EditorLspTestContext::new_rust(
7568 lsp::ServerCapabilities {
7569 signature_help_provider: Some(lsp::SignatureHelpOptions {
7570 ..Default::default()
7571 }),
7572 ..Default::default()
7573 },
7574 cx,
7575 )
7576 .await;
7577
7578 let language = Language::new(
7579 LanguageConfig {
7580 name: "Rust".into(),
7581 brackets: BracketPairConfig {
7582 pairs: vec![
7583 BracketPair {
7584 start: "{".to_string(),
7585 end: "}".to_string(),
7586 close: true,
7587 surround: true,
7588 newline: true,
7589 },
7590 BracketPair {
7591 start: "(".to_string(),
7592 end: ")".to_string(),
7593 close: true,
7594 surround: true,
7595 newline: true,
7596 },
7597 BracketPair {
7598 start: "/*".to_string(),
7599 end: " */".to_string(),
7600 close: true,
7601 surround: true,
7602 newline: true,
7603 },
7604 BracketPair {
7605 start: "[".to_string(),
7606 end: "]".to_string(),
7607 close: false,
7608 surround: false,
7609 newline: true,
7610 },
7611 BracketPair {
7612 start: "\"".to_string(),
7613 end: "\"".to_string(),
7614 close: true,
7615 surround: true,
7616 newline: false,
7617 },
7618 BracketPair {
7619 start: "<".to_string(),
7620 end: ">".to_string(),
7621 close: false,
7622 surround: true,
7623 newline: true,
7624 },
7625 ],
7626 ..Default::default()
7627 },
7628 autoclose_before: "})]".to_string(),
7629 ..Default::default()
7630 },
7631 Some(tree_sitter_rust::LANGUAGE.into()),
7632 );
7633 let language = Arc::new(language);
7634
7635 cx.language_registry().add(language.clone());
7636 cx.update_buffer(|buffer, cx| {
7637 buffer.set_language(Some(language), cx);
7638 });
7639
7640 // Ensure that signature_help is not called when no signature help is enabled.
7641 cx.set_state(
7642 &r#"
7643 fn main() {
7644 sampleˇ
7645 }
7646 "#
7647 .unindent(),
7648 );
7649 cx.update_editor(|view, cx| {
7650 view.handle_input("(", cx);
7651 });
7652 cx.assert_editor_state(
7653 &"
7654 fn main() {
7655 sample(ˇ)
7656 }
7657 "
7658 .unindent(),
7659 );
7660 cx.editor(|editor, _| {
7661 assert!(editor.signature_help_state.task().is_none());
7662 });
7663
7664 let mocked_response = lsp::SignatureHelp {
7665 signatures: vec![lsp::SignatureInformation {
7666 label: "fn sample(param1: u8, param2: u8)".to_string(),
7667 documentation: None,
7668 parameters: Some(vec![
7669 lsp::ParameterInformation {
7670 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7671 documentation: None,
7672 },
7673 lsp::ParameterInformation {
7674 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7675 documentation: None,
7676 },
7677 ]),
7678 active_parameter: None,
7679 }],
7680 active_signature: Some(0),
7681 active_parameter: Some(0),
7682 };
7683
7684 // Ensure that signature_help is called when enabled afte edits
7685 cx.update(|cx| {
7686 cx.update_global::<SettingsStore, _>(|settings, cx| {
7687 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7688 settings.auto_signature_help = Some(false);
7689 settings.show_signature_help_after_edits = Some(true);
7690 });
7691 });
7692 });
7693 cx.set_state(
7694 &r#"
7695 fn main() {
7696 sampleˇ
7697 }
7698 "#
7699 .unindent(),
7700 );
7701 cx.update_editor(|view, cx| {
7702 view.handle_input("(", cx);
7703 });
7704 cx.assert_editor_state(
7705 &"
7706 fn main() {
7707 sample(ˇ)
7708 }
7709 "
7710 .unindent(),
7711 );
7712 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7713 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7714 .await;
7715 cx.update_editor(|editor, _| {
7716 let signature_help_state = editor.signature_help_state.popover().cloned();
7717 assert!(signature_help_state.is_some());
7718 let ParsedMarkdown {
7719 text, highlights, ..
7720 } = signature_help_state.unwrap().parsed_content;
7721 assert_eq!(text, "param1: u8, param2: u8");
7722 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7723 editor.signature_help_state = SignatureHelpState::default();
7724 });
7725
7726 // Ensure that signature_help is called when auto signature help override is enabled
7727 cx.update(|cx| {
7728 cx.update_global::<SettingsStore, _>(|settings, cx| {
7729 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7730 settings.auto_signature_help = Some(true);
7731 settings.show_signature_help_after_edits = Some(false);
7732 });
7733 });
7734 });
7735 cx.set_state(
7736 &r#"
7737 fn main() {
7738 sampleˇ
7739 }
7740 "#
7741 .unindent(),
7742 );
7743 cx.update_editor(|view, cx| {
7744 view.handle_input("(", cx);
7745 });
7746 cx.assert_editor_state(
7747 &"
7748 fn main() {
7749 sample(ˇ)
7750 }
7751 "
7752 .unindent(),
7753 );
7754 handle_signature_help_request(&mut cx, mocked_response).await;
7755 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7756 .await;
7757 cx.editor(|editor, _| {
7758 let signature_help_state = editor.signature_help_state.popover().cloned();
7759 assert!(signature_help_state.is_some());
7760 let ParsedMarkdown {
7761 text, highlights, ..
7762 } = signature_help_state.unwrap().parsed_content;
7763 assert_eq!(text, "param1: u8, param2: u8");
7764 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7765 });
7766}
7767
7768#[gpui::test]
7769async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7770 init_test(cx, |_| {});
7771 cx.update(|cx| {
7772 cx.update_global::<SettingsStore, _>(|settings, cx| {
7773 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7774 settings.auto_signature_help = Some(true);
7775 });
7776 });
7777 });
7778
7779 let mut cx = EditorLspTestContext::new_rust(
7780 lsp::ServerCapabilities {
7781 signature_help_provider: Some(lsp::SignatureHelpOptions {
7782 ..Default::default()
7783 }),
7784 ..Default::default()
7785 },
7786 cx,
7787 )
7788 .await;
7789
7790 // A test that directly calls `show_signature_help`
7791 cx.update_editor(|editor, cx| {
7792 editor.show_signature_help(&ShowSignatureHelp, cx);
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(0),
7813 };
7814 handle_signature_help_request(&mut cx, mocked_response).await;
7815
7816 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7817 .await;
7818
7819 cx.editor(|editor, _| {
7820 let signature_help_state = editor.signature_help_state.popover().cloned();
7821 assert!(signature_help_state.is_some());
7822 let ParsedMarkdown {
7823 text, highlights, ..
7824 } = signature_help_state.unwrap().parsed_content;
7825 assert_eq!(text, "param1: u8, param2: u8");
7826 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7827 });
7828
7829 // When exiting outside from inside the brackets, `signature_help` is closed.
7830 cx.set_state(indoc! {"
7831 fn main() {
7832 sample(ˇ);
7833 }
7834
7835 fn sample(param1: u8, param2: u8) {}
7836 "});
7837
7838 cx.update_editor(|editor, cx| {
7839 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7840 });
7841
7842 let mocked_response = lsp::SignatureHelp {
7843 signatures: Vec::new(),
7844 active_signature: None,
7845 active_parameter: None,
7846 };
7847 handle_signature_help_request(&mut cx, mocked_response).await;
7848
7849 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7850 .await;
7851
7852 cx.editor(|editor, _| {
7853 assert!(!editor.signature_help_state.is_shown());
7854 });
7855
7856 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7857 cx.set_state(indoc! {"
7858 fn main() {
7859 sample(ˇ);
7860 }
7861
7862 fn sample(param1: u8, param2: u8) {}
7863 "});
7864
7865 let mocked_response = lsp::SignatureHelp {
7866 signatures: vec![lsp::SignatureInformation {
7867 label: "fn sample(param1: u8, param2: u8)".to_string(),
7868 documentation: None,
7869 parameters: Some(vec![
7870 lsp::ParameterInformation {
7871 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7872 documentation: None,
7873 },
7874 lsp::ParameterInformation {
7875 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7876 documentation: None,
7877 },
7878 ]),
7879 active_parameter: None,
7880 }],
7881 active_signature: Some(0),
7882 active_parameter: Some(0),
7883 };
7884 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7885 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7886 .await;
7887 cx.editor(|editor, _| {
7888 assert!(editor.signature_help_state.is_shown());
7889 });
7890
7891 // Restore the popover with more parameter input
7892 cx.set_state(indoc! {"
7893 fn main() {
7894 sample(param1, param2ˇ);
7895 }
7896
7897 fn sample(param1: u8, param2: u8) {}
7898 "});
7899
7900 let mocked_response = lsp::SignatureHelp {
7901 signatures: vec![lsp::SignatureInformation {
7902 label: "fn sample(param1: u8, param2: u8)".to_string(),
7903 documentation: None,
7904 parameters: Some(vec![
7905 lsp::ParameterInformation {
7906 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7907 documentation: None,
7908 },
7909 lsp::ParameterInformation {
7910 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7911 documentation: None,
7912 },
7913 ]),
7914 active_parameter: None,
7915 }],
7916 active_signature: Some(0),
7917 active_parameter: Some(1),
7918 };
7919 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7920 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7921 .await;
7922
7923 // When selecting a range, the popover is gone.
7924 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7925 cx.update_editor(|editor, cx| {
7926 editor.change_selections(None, cx, |s| {
7927 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7928 })
7929 });
7930 cx.assert_editor_state(indoc! {"
7931 fn main() {
7932 sample(param1, «ˇparam2»);
7933 }
7934
7935 fn sample(param1: u8, param2: u8) {}
7936 "});
7937 cx.editor(|editor, _| {
7938 assert!(!editor.signature_help_state.is_shown());
7939 });
7940
7941 // When unselecting again, the popover is back if within the brackets.
7942 cx.update_editor(|editor, cx| {
7943 editor.change_selections(None, cx, |s| {
7944 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7945 })
7946 });
7947 cx.assert_editor_state(indoc! {"
7948 fn main() {
7949 sample(param1, ˇparam2);
7950 }
7951
7952 fn sample(param1: u8, param2: u8) {}
7953 "});
7954 handle_signature_help_request(&mut cx, mocked_response).await;
7955 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7956 .await;
7957 cx.editor(|editor, _| {
7958 assert!(editor.signature_help_state.is_shown());
7959 });
7960
7961 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7962 cx.update_editor(|editor, cx| {
7963 editor.change_selections(None, cx, |s| {
7964 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7965 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7966 })
7967 });
7968 cx.assert_editor_state(indoc! {"
7969 fn main() {
7970 sample(param1, ˇparam2);
7971 }
7972
7973 fn sample(param1: u8, param2: u8) {}
7974 "});
7975
7976 let mocked_response = lsp::SignatureHelp {
7977 signatures: vec![lsp::SignatureInformation {
7978 label: "fn sample(param1: u8, param2: u8)".to_string(),
7979 documentation: None,
7980 parameters: Some(vec![
7981 lsp::ParameterInformation {
7982 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7983 documentation: None,
7984 },
7985 lsp::ParameterInformation {
7986 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7987 documentation: None,
7988 },
7989 ]),
7990 active_parameter: None,
7991 }],
7992 active_signature: Some(0),
7993 active_parameter: Some(1),
7994 };
7995 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7996 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7997 .await;
7998 cx.update_editor(|editor, cx| {
7999 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8000 });
8001 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8002 .await;
8003 cx.update_editor(|editor, cx| {
8004 editor.change_selections(None, cx, |s| {
8005 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8006 })
8007 });
8008 cx.assert_editor_state(indoc! {"
8009 fn main() {
8010 sample(param1, «ˇparam2»);
8011 }
8012
8013 fn sample(param1: u8, param2: u8) {}
8014 "});
8015 cx.update_editor(|editor, cx| {
8016 editor.change_selections(None, cx, |s| {
8017 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8018 })
8019 });
8020 cx.assert_editor_state(indoc! {"
8021 fn main() {
8022 sample(param1, ˇparam2);
8023 }
8024
8025 fn sample(param1: u8, param2: u8) {}
8026 "});
8027 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8028 .await;
8029}
8030
8031#[gpui::test]
8032async fn test_completion(cx: &mut gpui::TestAppContext) {
8033 init_test(cx, |_| {});
8034
8035 let mut cx = EditorLspTestContext::new_rust(
8036 lsp::ServerCapabilities {
8037 completion_provider: Some(lsp::CompletionOptions {
8038 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8039 resolve_provider: Some(true),
8040 ..Default::default()
8041 }),
8042 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8043 ..Default::default()
8044 },
8045 cx,
8046 )
8047 .await;
8048 let counter = Arc::new(AtomicUsize::new(0));
8049
8050 cx.set_state(indoc! {"
8051 oneˇ
8052 two
8053 three
8054 "});
8055 cx.simulate_keystroke(".");
8056 handle_completion_request(
8057 &mut cx,
8058 indoc! {"
8059 one.|<>
8060 two
8061 three
8062 "},
8063 vec!["first_completion", "second_completion"],
8064 counter.clone(),
8065 )
8066 .await;
8067 cx.condition(|editor, _| editor.context_menu_visible())
8068 .await;
8069 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8070
8071 let _handler = handle_signature_help_request(
8072 &mut cx,
8073 lsp::SignatureHelp {
8074 signatures: vec![lsp::SignatureInformation {
8075 label: "test signature".to_string(),
8076 documentation: None,
8077 parameters: Some(vec![lsp::ParameterInformation {
8078 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8079 documentation: None,
8080 }]),
8081 active_parameter: None,
8082 }],
8083 active_signature: None,
8084 active_parameter: None,
8085 },
8086 );
8087 cx.update_editor(|editor, cx| {
8088 assert!(
8089 !editor.signature_help_state.is_shown(),
8090 "No signature help was called for"
8091 );
8092 editor.show_signature_help(&ShowSignatureHelp, cx);
8093 });
8094 cx.run_until_parked();
8095 cx.update_editor(|editor, _| {
8096 assert!(
8097 !editor.signature_help_state.is_shown(),
8098 "No signature help should be shown when completions menu is open"
8099 );
8100 });
8101
8102 let apply_additional_edits = cx.update_editor(|editor, cx| {
8103 editor.context_menu_next(&Default::default(), cx);
8104 editor
8105 .confirm_completion(&ConfirmCompletion::default(), cx)
8106 .unwrap()
8107 });
8108 cx.assert_editor_state(indoc! {"
8109 one.second_completionˇ
8110 two
8111 three
8112 "});
8113
8114 handle_resolve_completion_request(
8115 &mut cx,
8116 Some(vec![
8117 (
8118 //This overlaps with the primary completion edit which is
8119 //misbehavior from the LSP spec, test that we filter it out
8120 indoc! {"
8121 one.second_ˇcompletion
8122 two
8123 threeˇ
8124 "},
8125 "overlapping additional edit",
8126 ),
8127 (
8128 indoc! {"
8129 one.second_completion
8130 two
8131 threeˇ
8132 "},
8133 "\nadditional edit",
8134 ),
8135 ]),
8136 )
8137 .await;
8138 apply_additional_edits.await.unwrap();
8139 cx.assert_editor_state(indoc! {"
8140 one.second_completionˇ
8141 two
8142 three
8143 additional edit
8144 "});
8145
8146 cx.set_state(indoc! {"
8147 one.second_completion
8148 twoˇ
8149 threeˇ
8150 additional edit
8151 "});
8152 cx.simulate_keystroke(" ");
8153 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8154 cx.simulate_keystroke("s");
8155 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8156
8157 cx.assert_editor_state(indoc! {"
8158 one.second_completion
8159 two sˇ
8160 three sˇ
8161 additional edit
8162 "});
8163 handle_completion_request(
8164 &mut cx,
8165 indoc! {"
8166 one.second_completion
8167 two s
8168 three <s|>
8169 additional edit
8170 "},
8171 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8172 counter.clone(),
8173 )
8174 .await;
8175 cx.condition(|editor, _| editor.context_menu_visible())
8176 .await;
8177 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8178
8179 cx.simulate_keystroke("i");
8180
8181 handle_completion_request(
8182 &mut cx,
8183 indoc! {"
8184 one.second_completion
8185 two si
8186 three <si|>
8187 additional edit
8188 "},
8189 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8190 counter.clone(),
8191 )
8192 .await;
8193 cx.condition(|editor, _| editor.context_menu_visible())
8194 .await;
8195 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8196
8197 let apply_additional_edits = cx.update_editor(|editor, cx| {
8198 editor
8199 .confirm_completion(&ConfirmCompletion::default(), cx)
8200 .unwrap()
8201 });
8202 cx.assert_editor_state(indoc! {"
8203 one.second_completion
8204 two sixth_completionˇ
8205 three sixth_completionˇ
8206 additional edit
8207 "});
8208
8209 handle_resolve_completion_request(&mut cx, None).await;
8210 apply_additional_edits.await.unwrap();
8211
8212 cx.update(|cx| {
8213 cx.update_global::<SettingsStore, _>(|settings, cx| {
8214 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8215 settings.show_completions_on_input = Some(false);
8216 });
8217 })
8218 });
8219 cx.set_state("editorˇ");
8220 cx.simulate_keystroke(".");
8221 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8222 cx.simulate_keystroke("c");
8223 cx.simulate_keystroke("l");
8224 cx.simulate_keystroke("o");
8225 cx.assert_editor_state("editor.cloˇ");
8226 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8227 cx.update_editor(|editor, cx| {
8228 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8229 });
8230 handle_completion_request(
8231 &mut cx,
8232 "editor.<clo|>",
8233 vec!["close", "clobber"],
8234 counter.clone(),
8235 )
8236 .await;
8237 cx.condition(|editor, _| editor.context_menu_visible())
8238 .await;
8239 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8240
8241 let apply_additional_edits = cx.update_editor(|editor, cx| {
8242 editor
8243 .confirm_completion(&ConfirmCompletion::default(), cx)
8244 .unwrap()
8245 });
8246 cx.assert_editor_state("editor.closeˇ");
8247 handle_resolve_completion_request(&mut cx, None).await;
8248 apply_additional_edits.await.unwrap();
8249}
8250
8251#[gpui::test]
8252async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8253 init_test(cx, |_| {});
8254 let mut cx = EditorLspTestContext::new_rust(
8255 lsp::ServerCapabilities {
8256 completion_provider: Some(lsp::CompletionOptions {
8257 trigger_characters: Some(vec![".".to_string()]),
8258 ..Default::default()
8259 }),
8260 ..Default::default()
8261 },
8262 cx,
8263 )
8264 .await;
8265 cx.lsp
8266 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8267 Ok(Some(lsp::CompletionResponse::Array(vec![
8268 lsp::CompletionItem {
8269 label: "first".into(),
8270 ..Default::default()
8271 },
8272 lsp::CompletionItem {
8273 label: "last".into(),
8274 ..Default::default()
8275 },
8276 ])))
8277 });
8278 cx.set_state("variableˇ");
8279 cx.simulate_keystroke(".");
8280 cx.executor().run_until_parked();
8281
8282 cx.update_editor(|editor, _| {
8283 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8284 assert_eq!(
8285 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8286 &["first", "last"]
8287 );
8288 } else {
8289 panic!("expected completion menu to be open");
8290 }
8291 });
8292
8293 cx.update_editor(|editor, cx| {
8294 editor.move_page_down(&MovePageDown::default(), cx);
8295 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8296 assert!(
8297 menu.selected_item == 1,
8298 "expected PageDown to select the last item from the context menu"
8299 );
8300 } else {
8301 panic!("expected completion menu to stay open after PageDown");
8302 }
8303 });
8304
8305 cx.update_editor(|editor, cx| {
8306 editor.move_page_up(&MovePageUp::default(), cx);
8307 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8308 assert!(
8309 menu.selected_item == 0,
8310 "expected PageUp to select the first item from the context menu"
8311 );
8312 } else {
8313 panic!("expected completion menu to stay open after PageUp");
8314 }
8315 });
8316}
8317
8318#[gpui::test]
8319async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8320 init_test(cx, |_| {});
8321
8322 let mut cx = EditorLspTestContext::new_rust(
8323 lsp::ServerCapabilities {
8324 completion_provider: Some(lsp::CompletionOptions {
8325 trigger_characters: Some(vec![".".to_string()]),
8326 resolve_provider: Some(true),
8327 ..Default::default()
8328 }),
8329 ..Default::default()
8330 },
8331 cx,
8332 )
8333 .await;
8334
8335 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8336 cx.simulate_keystroke(".");
8337 let completion_item = lsp::CompletionItem {
8338 label: "Some".into(),
8339 kind: Some(lsp::CompletionItemKind::SNIPPET),
8340 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8341 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8342 kind: lsp::MarkupKind::Markdown,
8343 value: "```rust\nSome(2)\n```".to_string(),
8344 })),
8345 deprecated: Some(false),
8346 sort_text: Some("Some".to_string()),
8347 filter_text: Some("Some".to_string()),
8348 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8349 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8350 range: lsp::Range {
8351 start: lsp::Position {
8352 line: 0,
8353 character: 22,
8354 },
8355 end: lsp::Position {
8356 line: 0,
8357 character: 22,
8358 },
8359 },
8360 new_text: "Some(2)".to_string(),
8361 })),
8362 additional_text_edits: Some(vec![lsp::TextEdit {
8363 range: lsp::Range {
8364 start: lsp::Position {
8365 line: 0,
8366 character: 20,
8367 },
8368 end: lsp::Position {
8369 line: 0,
8370 character: 22,
8371 },
8372 },
8373 new_text: "".to_string(),
8374 }]),
8375 ..Default::default()
8376 };
8377
8378 let closure_completion_item = completion_item.clone();
8379 let counter = Arc::new(AtomicUsize::new(0));
8380 let counter_clone = counter.clone();
8381 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8382 let task_completion_item = closure_completion_item.clone();
8383 counter_clone.fetch_add(1, atomic::Ordering::Release);
8384 async move {
8385 Ok(Some(lsp::CompletionResponse::Array(vec![
8386 task_completion_item,
8387 ])))
8388 }
8389 });
8390
8391 cx.condition(|editor, _| editor.context_menu_visible())
8392 .await;
8393 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8394 assert!(request.next().await.is_some());
8395 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8396
8397 cx.simulate_keystroke("S");
8398 cx.simulate_keystroke("o");
8399 cx.simulate_keystroke("m");
8400 cx.condition(|editor, _| editor.context_menu_visible())
8401 .await;
8402 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8403 assert!(request.next().await.is_some());
8404 assert!(request.next().await.is_some());
8405 assert!(request.next().await.is_some());
8406 request.close();
8407 assert!(request.next().await.is_none());
8408 assert_eq!(
8409 counter.load(atomic::Ordering::Acquire),
8410 4,
8411 "With the completions menu open, only one LSP request should happen per input"
8412 );
8413}
8414
8415#[gpui::test]
8416async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8417 init_test(cx, |_| {});
8418 let mut cx = EditorTestContext::new(cx).await;
8419 let language = Arc::new(Language::new(
8420 LanguageConfig {
8421 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8422 ..Default::default()
8423 },
8424 Some(tree_sitter_rust::LANGUAGE.into()),
8425 ));
8426 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8427
8428 // If multiple selections intersect a line, the line is only toggled once.
8429 cx.set_state(indoc! {"
8430 fn a() {
8431 «//b();
8432 ˇ»// «c();
8433 //ˇ» d();
8434 }
8435 "});
8436
8437 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8438
8439 cx.assert_editor_state(indoc! {"
8440 fn a() {
8441 «b();
8442 c();
8443 ˇ» d();
8444 }
8445 "});
8446
8447 // The comment prefix is inserted at the same column for every line in a
8448 // selection.
8449 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8450
8451 cx.assert_editor_state(indoc! {"
8452 fn a() {
8453 // «b();
8454 // c();
8455 ˇ»// d();
8456 }
8457 "});
8458
8459 // If a selection ends at the beginning of a line, that line is not toggled.
8460 cx.set_selections_state(indoc! {"
8461 fn a() {
8462 // b();
8463 «// c();
8464 ˇ» // d();
8465 }
8466 "});
8467
8468 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8469
8470 cx.assert_editor_state(indoc! {"
8471 fn a() {
8472 // b();
8473 «c();
8474 ˇ» // d();
8475 }
8476 "});
8477
8478 // If a selection span a single line and is empty, the line is toggled.
8479 cx.set_state(indoc! {"
8480 fn a() {
8481 a();
8482 b();
8483 ˇ
8484 }
8485 "});
8486
8487 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8488
8489 cx.assert_editor_state(indoc! {"
8490 fn a() {
8491 a();
8492 b();
8493 //•ˇ
8494 }
8495 "});
8496
8497 // If a selection span multiple lines, empty lines are not toggled.
8498 cx.set_state(indoc! {"
8499 fn a() {
8500 «a();
8501
8502 c();ˇ»
8503 }
8504 "});
8505
8506 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8507
8508 cx.assert_editor_state(indoc! {"
8509 fn a() {
8510 // «a();
8511
8512 // c();ˇ»
8513 }
8514 "});
8515
8516 // If a selection includes multiple comment prefixes, all lines are uncommented.
8517 cx.set_state(indoc! {"
8518 fn a() {
8519 «// a();
8520 /// b();
8521 //! c();ˇ»
8522 }
8523 "});
8524
8525 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8526
8527 cx.assert_editor_state(indoc! {"
8528 fn a() {
8529 «a();
8530 b();
8531 c();ˇ»
8532 }
8533 "});
8534}
8535
8536#[gpui::test]
8537async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8538 init_test(cx, |_| {});
8539
8540 let language = Arc::new(Language::new(
8541 LanguageConfig {
8542 line_comments: vec!["// ".into()],
8543 ..Default::default()
8544 },
8545 Some(tree_sitter_rust::LANGUAGE.into()),
8546 ));
8547
8548 let mut cx = EditorTestContext::new(cx).await;
8549
8550 cx.language_registry().add(language.clone());
8551 cx.update_buffer(|buffer, cx| {
8552 buffer.set_language(Some(language), cx);
8553 });
8554
8555 let toggle_comments = &ToggleComments {
8556 advance_downwards: true,
8557 };
8558
8559 // Single cursor on one line -> advance
8560 // Cursor moves horizontally 3 characters as well on non-blank line
8561 cx.set_state(indoc!(
8562 "fn a() {
8563 ˇdog();
8564 cat();
8565 }"
8566 ));
8567 cx.update_editor(|editor, cx| {
8568 editor.toggle_comments(toggle_comments, cx);
8569 });
8570 cx.assert_editor_state(indoc!(
8571 "fn a() {
8572 // dog();
8573 catˇ();
8574 }"
8575 ));
8576
8577 // Single selection on one line -> don't advance
8578 cx.set_state(indoc!(
8579 "fn a() {
8580 «dog()ˇ»;
8581 cat();
8582 }"
8583 ));
8584 cx.update_editor(|editor, cx| {
8585 editor.toggle_comments(toggle_comments, cx);
8586 });
8587 cx.assert_editor_state(indoc!(
8588 "fn a() {
8589 // «dog()ˇ»;
8590 cat();
8591 }"
8592 ));
8593
8594 // Multiple cursors on one line -> advance
8595 cx.set_state(indoc!(
8596 "fn a() {
8597 ˇdˇog();
8598 cat();
8599 }"
8600 ));
8601 cx.update_editor(|editor, cx| {
8602 editor.toggle_comments(toggle_comments, cx);
8603 });
8604 cx.assert_editor_state(indoc!(
8605 "fn a() {
8606 // dog();
8607 catˇ(ˇ);
8608 }"
8609 ));
8610
8611 // Multiple cursors on one line, with selection -> don't advance
8612 cx.set_state(indoc!(
8613 "fn a() {
8614 ˇdˇog«()ˇ»;
8615 cat();
8616 }"
8617 ));
8618 cx.update_editor(|editor, cx| {
8619 editor.toggle_comments(toggle_comments, cx);
8620 });
8621 cx.assert_editor_state(indoc!(
8622 "fn a() {
8623 // ˇdˇog«()ˇ»;
8624 cat();
8625 }"
8626 ));
8627
8628 // Single cursor on one line -> advance
8629 // Cursor moves to column 0 on blank line
8630 cx.set_state(indoc!(
8631 "fn a() {
8632 ˇdog();
8633
8634 cat();
8635 }"
8636 ));
8637 cx.update_editor(|editor, cx| {
8638 editor.toggle_comments(toggle_comments, cx);
8639 });
8640 cx.assert_editor_state(indoc!(
8641 "fn a() {
8642 // dog();
8643 ˇ
8644 cat();
8645 }"
8646 ));
8647
8648 // Single cursor on one line -> advance
8649 // Cursor starts and ends at column 0
8650 cx.set_state(indoc!(
8651 "fn a() {
8652 ˇ dog();
8653 cat();
8654 }"
8655 ));
8656 cx.update_editor(|editor, cx| {
8657 editor.toggle_comments(toggle_comments, cx);
8658 });
8659 cx.assert_editor_state(indoc!(
8660 "fn a() {
8661 // dog();
8662 ˇ cat();
8663 }"
8664 ));
8665}
8666
8667#[gpui::test]
8668async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8669 init_test(cx, |_| {});
8670
8671 let mut cx = EditorTestContext::new(cx).await;
8672
8673 let html_language = Arc::new(
8674 Language::new(
8675 LanguageConfig {
8676 name: "HTML".into(),
8677 block_comment: Some(("<!-- ".into(), " -->".into())),
8678 ..Default::default()
8679 },
8680 Some(tree_sitter_html::language()),
8681 )
8682 .with_injection_query(
8683 r#"
8684 (script_element
8685 (raw_text) @content
8686 (#set! "language" "javascript"))
8687 "#,
8688 )
8689 .unwrap(),
8690 );
8691
8692 let javascript_language = Arc::new(Language::new(
8693 LanguageConfig {
8694 name: "JavaScript".into(),
8695 line_comments: vec!["// ".into()],
8696 ..Default::default()
8697 },
8698 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8699 ));
8700
8701 cx.language_registry().add(html_language.clone());
8702 cx.language_registry().add(javascript_language.clone());
8703 cx.update_buffer(|buffer, cx| {
8704 buffer.set_language(Some(html_language), cx);
8705 });
8706
8707 // Toggle comments for empty selections
8708 cx.set_state(
8709 &r#"
8710 <p>A</p>ˇ
8711 <p>B</p>ˇ
8712 <p>C</p>ˇ
8713 "#
8714 .unindent(),
8715 );
8716 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8717 cx.assert_editor_state(
8718 &r#"
8719 <!-- <p>A</p>ˇ -->
8720 <!-- <p>B</p>ˇ -->
8721 <!-- <p>C</p>ˇ -->
8722 "#
8723 .unindent(),
8724 );
8725 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8726 cx.assert_editor_state(
8727 &r#"
8728 <p>A</p>ˇ
8729 <p>B</p>ˇ
8730 <p>C</p>ˇ
8731 "#
8732 .unindent(),
8733 );
8734
8735 // Toggle comments for mixture of empty and non-empty selections, where
8736 // multiple selections occupy a given line.
8737 cx.set_state(
8738 &r#"
8739 <p>A«</p>
8740 <p>ˇ»B</p>ˇ
8741 <p>C«</p>
8742 <p>ˇ»D</p>ˇ
8743 "#
8744 .unindent(),
8745 );
8746
8747 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8748 cx.assert_editor_state(
8749 &r#"
8750 <!-- <p>A«</p>
8751 <p>ˇ»B</p>ˇ -->
8752 <!-- <p>C«</p>
8753 <p>ˇ»D</p>ˇ -->
8754 "#
8755 .unindent(),
8756 );
8757 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8758 cx.assert_editor_state(
8759 &r#"
8760 <p>A«</p>
8761 <p>ˇ»B</p>ˇ
8762 <p>C«</p>
8763 <p>ˇ»D</p>ˇ
8764 "#
8765 .unindent(),
8766 );
8767
8768 // Toggle comments when different languages are active for different
8769 // selections.
8770 cx.set_state(
8771 &r#"
8772 ˇ<script>
8773 ˇvar x = new Y();
8774 ˇ</script>
8775 "#
8776 .unindent(),
8777 );
8778 cx.executor().run_until_parked();
8779 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8780 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8781 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8782 cx.assert_editor_state(
8783 &r#"
8784 <!-- ˇ<script> -->
8785 // ˇvar x = new Y();
8786 // ˇ</script>
8787 "#
8788 .unindent(),
8789 );
8790}
8791
8792#[gpui::test]
8793fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8794 init_test(cx, |_| {});
8795
8796 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8797 let multibuffer = cx.new_model(|cx| {
8798 let mut multibuffer = MultiBuffer::new(ReadWrite);
8799 multibuffer.push_excerpts(
8800 buffer.clone(),
8801 [
8802 ExcerptRange {
8803 context: Point::new(0, 0)..Point::new(0, 4),
8804 primary: None,
8805 },
8806 ExcerptRange {
8807 context: Point::new(1, 0)..Point::new(1, 4),
8808 primary: None,
8809 },
8810 ],
8811 cx,
8812 );
8813 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8814 multibuffer
8815 });
8816
8817 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8818 view.update(cx, |view, cx| {
8819 assert_eq!(view.text(cx), "aaaa\nbbbb");
8820 view.change_selections(None, cx, |s| {
8821 s.select_ranges([
8822 Point::new(0, 0)..Point::new(0, 0),
8823 Point::new(1, 0)..Point::new(1, 0),
8824 ])
8825 });
8826
8827 view.handle_input("X", cx);
8828 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8829 assert_eq!(
8830 view.selections.ranges(cx),
8831 [
8832 Point::new(0, 1)..Point::new(0, 1),
8833 Point::new(1, 1)..Point::new(1, 1),
8834 ]
8835 );
8836
8837 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8838 view.change_selections(None, cx, |s| {
8839 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8840 });
8841 view.backspace(&Default::default(), cx);
8842 assert_eq!(view.text(cx), "Xa\nbbb");
8843 assert_eq!(
8844 view.selections.ranges(cx),
8845 [Point::new(1, 0)..Point::new(1, 0)]
8846 );
8847
8848 view.change_selections(None, cx, |s| {
8849 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8850 });
8851 view.backspace(&Default::default(), cx);
8852 assert_eq!(view.text(cx), "X\nbb");
8853 assert_eq!(
8854 view.selections.ranges(cx),
8855 [Point::new(0, 1)..Point::new(0, 1)]
8856 );
8857 });
8858}
8859
8860#[gpui::test]
8861fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8862 init_test(cx, |_| {});
8863
8864 let markers = vec![('[', ']').into(), ('(', ')').into()];
8865 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8866 indoc! {"
8867 [aaaa
8868 (bbbb]
8869 cccc)",
8870 },
8871 markers.clone(),
8872 );
8873 let excerpt_ranges = markers.into_iter().map(|marker| {
8874 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8875 ExcerptRange {
8876 context,
8877 primary: None,
8878 }
8879 });
8880 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8881 let multibuffer = cx.new_model(|cx| {
8882 let mut multibuffer = MultiBuffer::new(ReadWrite);
8883 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8884 multibuffer
8885 });
8886
8887 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8888 view.update(cx, |view, cx| {
8889 let (expected_text, selection_ranges) = marked_text_ranges(
8890 indoc! {"
8891 aaaa
8892 bˇbbb
8893 bˇbbˇb
8894 cccc"
8895 },
8896 true,
8897 );
8898 assert_eq!(view.text(cx), expected_text);
8899 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8900
8901 view.handle_input("X", cx);
8902
8903 let (expected_text, expected_selections) = marked_text_ranges(
8904 indoc! {"
8905 aaaa
8906 bXˇbbXb
8907 bXˇbbXˇb
8908 cccc"
8909 },
8910 false,
8911 );
8912 assert_eq!(view.text(cx), expected_text);
8913 assert_eq!(view.selections.ranges(cx), expected_selections);
8914
8915 view.newline(&Newline, cx);
8916 let (expected_text, expected_selections) = marked_text_ranges(
8917 indoc! {"
8918 aaaa
8919 bX
8920 ˇbbX
8921 b
8922 bX
8923 ˇbbX
8924 ˇb
8925 cccc"
8926 },
8927 false,
8928 );
8929 assert_eq!(view.text(cx), expected_text);
8930 assert_eq!(view.selections.ranges(cx), expected_selections);
8931 });
8932}
8933
8934#[gpui::test]
8935fn test_refresh_selections(cx: &mut TestAppContext) {
8936 init_test(cx, |_| {});
8937
8938 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8939 let mut excerpt1_id = None;
8940 let multibuffer = cx.new_model(|cx| {
8941 let mut multibuffer = MultiBuffer::new(ReadWrite);
8942 excerpt1_id = multibuffer
8943 .push_excerpts(
8944 buffer.clone(),
8945 [
8946 ExcerptRange {
8947 context: Point::new(0, 0)..Point::new(1, 4),
8948 primary: None,
8949 },
8950 ExcerptRange {
8951 context: Point::new(1, 0)..Point::new(2, 4),
8952 primary: None,
8953 },
8954 ],
8955 cx,
8956 )
8957 .into_iter()
8958 .next();
8959 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8960 multibuffer
8961 });
8962
8963 let editor = cx.add_window(|cx| {
8964 let mut editor = build_editor(multibuffer.clone(), cx);
8965 let snapshot = editor.snapshot(cx);
8966 editor.change_selections(None, cx, |s| {
8967 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8968 });
8969 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8970 assert_eq!(
8971 editor.selections.ranges(cx),
8972 [
8973 Point::new(1, 3)..Point::new(1, 3),
8974 Point::new(2, 1)..Point::new(2, 1),
8975 ]
8976 );
8977 editor
8978 });
8979
8980 // Refreshing selections is a no-op when excerpts haven't changed.
8981 _ = editor.update(cx, |editor, cx| {
8982 editor.change_selections(None, cx, |s| s.refresh());
8983 assert_eq!(
8984 editor.selections.ranges(cx),
8985 [
8986 Point::new(1, 3)..Point::new(1, 3),
8987 Point::new(2, 1)..Point::new(2, 1),
8988 ]
8989 );
8990 });
8991
8992 multibuffer.update(cx, |multibuffer, cx| {
8993 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8994 });
8995 _ = editor.update(cx, |editor, cx| {
8996 // Removing an excerpt causes the first selection to become degenerate.
8997 assert_eq!(
8998 editor.selections.ranges(cx),
8999 [
9000 Point::new(0, 0)..Point::new(0, 0),
9001 Point::new(0, 1)..Point::new(0, 1)
9002 ]
9003 );
9004
9005 // Refreshing selections will relocate the first selection to the original buffer
9006 // location.
9007 editor.change_selections(None, cx, |s| s.refresh());
9008 assert_eq!(
9009 editor.selections.ranges(cx),
9010 [
9011 Point::new(0, 1)..Point::new(0, 1),
9012 Point::new(0, 3)..Point::new(0, 3)
9013 ]
9014 );
9015 assert!(editor.selections.pending_anchor().is_some());
9016 });
9017}
9018
9019#[gpui::test]
9020fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9021 init_test(cx, |_| {});
9022
9023 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9024 let mut excerpt1_id = None;
9025 let multibuffer = cx.new_model(|cx| {
9026 let mut multibuffer = MultiBuffer::new(ReadWrite);
9027 excerpt1_id = multibuffer
9028 .push_excerpts(
9029 buffer.clone(),
9030 [
9031 ExcerptRange {
9032 context: Point::new(0, 0)..Point::new(1, 4),
9033 primary: None,
9034 },
9035 ExcerptRange {
9036 context: Point::new(1, 0)..Point::new(2, 4),
9037 primary: None,
9038 },
9039 ],
9040 cx,
9041 )
9042 .into_iter()
9043 .next();
9044 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9045 multibuffer
9046 });
9047
9048 let editor = cx.add_window(|cx| {
9049 let mut editor = build_editor(multibuffer.clone(), cx);
9050 let snapshot = editor.snapshot(cx);
9051 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9052 assert_eq!(
9053 editor.selections.ranges(cx),
9054 [Point::new(1, 3)..Point::new(1, 3)]
9055 );
9056 editor
9057 });
9058
9059 multibuffer.update(cx, |multibuffer, cx| {
9060 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9061 });
9062 _ = editor.update(cx, |editor, cx| {
9063 assert_eq!(
9064 editor.selections.ranges(cx),
9065 [Point::new(0, 0)..Point::new(0, 0)]
9066 );
9067
9068 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9069 editor.change_selections(None, cx, |s| s.refresh());
9070 assert_eq!(
9071 editor.selections.ranges(cx),
9072 [Point::new(0, 3)..Point::new(0, 3)]
9073 );
9074 assert!(editor.selections.pending_anchor().is_some());
9075 });
9076}
9077
9078#[gpui::test]
9079async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9080 init_test(cx, |_| {});
9081
9082 let language = Arc::new(
9083 Language::new(
9084 LanguageConfig {
9085 brackets: BracketPairConfig {
9086 pairs: vec![
9087 BracketPair {
9088 start: "{".to_string(),
9089 end: "}".to_string(),
9090 close: true,
9091 surround: true,
9092 newline: true,
9093 },
9094 BracketPair {
9095 start: "/* ".to_string(),
9096 end: " */".to_string(),
9097 close: true,
9098 surround: true,
9099 newline: true,
9100 },
9101 ],
9102 ..Default::default()
9103 },
9104 ..Default::default()
9105 },
9106 Some(tree_sitter_rust::LANGUAGE.into()),
9107 )
9108 .with_indents_query("")
9109 .unwrap(),
9110 );
9111
9112 let text = concat!(
9113 "{ }\n", //
9114 " x\n", //
9115 " /* */\n", //
9116 "x\n", //
9117 "{{} }\n", //
9118 );
9119
9120 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9121 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9122 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9123 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9124 .await;
9125
9126 view.update(cx, |view, cx| {
9127 view.change_selections(None, cx, |s| {
9128 s.select_display_ranges([
9129 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9130 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9131 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9132 ])
9133 });
9134 view.newline(&Newline, cx);
9135
9136 assert_eq!(
9137 view.buffer().read(cx).read(cx).text(),
9138 concat!(
9139 "{ \n", // Suppress rustfmt
9140 "\n", //
9141 "}\n", //
9142 " x\n", //
9143 " /* \n", //
9144 " \n", //
9145 " */\n", //
9146 "x\n", //
9147 "{{} \n", //
9148 "}\n", //
9149 )
9150 );
9151 });
9152}
9153
9154#[gpui::test]
9155fn test_highlighted_ranges(cx: &mut TestAppContext) {
9156 init_test(cx, |_| {});
9157
9158 let editor = cx.add_window(|cx| {
9159 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9160 build_editor(buffer.clone(), cx)
9161 });
9162
9163 _ = editor.update(cx, |editor, cx| {
9164 struct Type1;
9165 struct Type2;
9166
9167 let buffer = editor.buffer.read(cx).snapshot(cx);
9168
9169 let anchor_range =
9170 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9171
9172 editor.highlight_background::<Type1>(
9173 &[
9174 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9175 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9176 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9177 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9178 ],
9179 |_| Hsla::red(),
9180 cx,
9181 );
9182 editor.highlight_background::<Type2>(
9183 &[
9184 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9185 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9186 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9187 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9188 ],
9189 |_| Hsla::green(),
9190 cx,
9191 );
9192
9193 let snapshot = editor.snapshot(cx);
9194 let mut highlighted_ranges = editor.background_highlights_in_range(
9195 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9196 &snapshot,
9197 cx.theme().colors(),
9198 );
9199 // Enforce a consistent ordering based on color without relying on the ordering of the
9200 // highlight's `TypeId` which is non-executor.
9201 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9202 assert_eq!(
9203 highlighted_ranges,
9204 &[
9205 (
9206 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9207 Hsla::red(),
9208 ),
9209 (
9210 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9211 Hsla::red(),
9212 ),
9213 (
9214 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9215 Hsla::green(),
9216 ),
9217 (
9218 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9219 Hsla::green(),
9220 ),
9221 ]
9222 );
9223 assert_eq!(
9224 editor.background_highlights_in_range(
9225 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9226 &snapshot,
9227 cx.theme().colors(),
9228 ),
9229 &[(
9230 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9231 Hsla::red(),
9232 )]
9233 );
9234 });
9235}
9236
9237#[gpui::test]
9238async fn test_following(cx: &mut gpui::TestAppContext) {
9239 init_test(cx, |_| {});
9240
9241 let fs = FakeFs::new(cx.executor());
9242 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9243
9244 let buffer = project.update(cx, |project, cx| {
9245 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9246 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9247 });
9248 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9249 let follower = cx.update(|cx| {
9250 cx.open_window(
9251 WindowOptions {
9252 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9253 gpui::Point::new(px(0.), px(0.)),
9254 gpui::Point::new(px(10.), px(80.)),
9255 ))),
9256 ..Default::default()
9257 },
9258 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9259 )
9260 .unwrap()
9261 });
9262
9263 let is_still_following = Rc::new(RefCell::new(true));
9264 let follower_edit_event_count = Rc::new(RefCell::new(0));
9265 let pending_update = Rc::new(RefCell::new(None));
9266 _ = follower.update(cx, {
9267 let update = pending_update.clone();
9268 let is_still_following = is_still_following.clone();
9269 let follower_edit_event_count = follower_edit_event_count.clone();
9270 |_, cx| {
9271 cx.subscribe(
9272 &leader.root_view(cx).unwrap(),
9273 move |_, leader, event, cx| {
9274 leader
9275 .read(cx)
9276 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9277 },
9278 )
9279 .detach();
9280
9281 cx.subscribe(
9282 &follower.root_view(cx).unwrap(),
9283 move |_, _, event: &EditorEvent, _cx| {
9284 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9285 *is_still_following.borrow_mut() = false;
9286 }
9287
9288 if let EditorEvent::BufferEdited = event {
9289 *follower_edit_event_count.borrow_mut() += 1;
9290 }
9291 },
9292 )
9293 .detach();
9294 }
9295 });
9296
9297 // Update the selections only
9298 _ = leader.update(cx, |leader, cx| {
9299 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9300 });
9301 follower
9302 .update(cx, |follower, cx| {
9303 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9304 })
9305 .unwrap()
9306 .await
9307 .unwrap();
9308 _ = follower.update(cx, |follower, cx| {
9309 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9310 });
9311 assert!(*is_still_following.borrow());
9312 assert_eq!(*follower_edit_event_count.borrow(), 0);
9313
9314 // Update the scroll position only
9315 _ = leader.update(cx, |leader, cx| {
9316 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9317 });
9318 follower
9319 .update(cx, |follower, cx| {
9320 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9321 })
9322 .unwrap()
9323 .await
9324 .unwrap();
9325 assert_eq!(
9326 follower
9327 .update(cx, |follower, cx| follower.scroll_position(cx))
9328 .unwrap(),
9329 gpui::Point::new(1.5, 3.5)
9330 );
9331 assert!(*is_still_following.borrow());
9332 assert_eq!(*follower_edit_event_count.borrow(), 0);
9333
9334 // Update the selections and scroll position. The follower's scroll position is updated
9335 // via autoscroll, not via the leader's exact scroll position.
9336 _ = leader.update(cx, |leader, cx| {
9337 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9338 leader.request_autoscroll(Autoscroll::newest(), cx);
9339 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9340 });
9341 follower
9342 .update(cx, |follower, cx| {
9343 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9344 })
9345 .unwrap()
9346 .await
9347 .unwrap();
9348 _ = follower.update(cx, |follower, cx| {
9349 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9350 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9351 });
9352 assert!(*is_still_following.borrow());
9353
9354 // Creating a pending selection that precedes another selection
9355 _ = leader.update(cx, |leader, cx| {
9356 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9357 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9358 });
9359 follower
9360 .update(cx, |follower, cx| {
9361 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9362 })
9363 .unwrap()
9364 .await
9365 .unwrap();
9366 _ = follower.update(cx, |follower, cx| {
9367 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9368 });
9369 assert!(*is_still_following.borrow());
9370
9371 // Extend the pending selection so that it surrounds another selection
9372 _ = leader.update(cx, |leader, cx| {
9373 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9374 });
9375 follower
9376 .update(cx, |follower, cx| {
9377 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9378 })
9379 .unwrap()
9380 .await
9381 .unwrap();
9382 _ = follower.update(cx, |follower, cx| {
9383 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9384 });
9385
9386 // Scrolling locally breaks the follow
9387 _ = follower.update(cx, |follower, cx| {
9388 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9389 follower.set_scroll_anchor(
9390 ScrollAnchor {
9391 anchor: top_anchor,
9392 offset: gpui::Point::new(0.0, 0.5),
9393 },
9394 cx,
9395 );
9396 });
9397 assert!(!(*is_still_following.borrow()));
9398}
9399
9400#[gpui::test]
9401async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9402 init_test(cx, |_| {});
9403
9404 let fs = FakeFs::new(cx.executor());
9405 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9406 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9407 let pane = workspace
9408 .update(cx, |workspace, _| workspace.active_pane().clone())
9409 .unwrap();
9410
9411 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9412
9413 let leader = pane.update(cx, |_, cx| {
9414 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9415 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9416 });
9417
9418 // Start following the editor when it has no excerpts.
9419 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9420 let follower_1 = cx
9421 .update_window(*workspace.deref(), |_, cx| {
9422 Editor::from_state_proto(
9423 workspace.root_view(cx).unwrap(),
9424 ViewId {
9425 creator: Default::default(),
9426 id: 0,
9427 },
9428 &mut state_message,
9429 cx,
9430 )
9431 })
9432 .unwrap()
9433 .unwrap()
9434 .await
9435 .unwrap();
9436
9437 let update_message = Rc::new(RefCell::new(None));
9438 follower_1.update(cx, {
9439 let update = update_message.clone();
9440 |_, cx| {
9441 cx.subscribe(&leader, move |_, leader, event, cx| {
9442 leader
9443 .read(cx)
9444 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9445 })
9446 .detach();
9447 }
9448 });
9449
9450 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9451 (
9452 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9453 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9454 )
9455 });
9456
9457 // Insert some excerpts.
9458 leader.update(cx, |leader, cx| {
9459 leader.buffer.update(cx, |multibuffer, cx| {
9460 let excerpt_ids = multibuffer.push_excerpts(
9461 buffer_1.clone(),
9462 [
9463 ExcerptRange {
9464 context: 1..6,
9465 primary: None,
9466 },
9467 ExcerptRange {
9468 context: 12..15,
9469 primary: None,
9470 },
9471 ExcerptRange {
9472 context: 0..3,
9473 primary: None,
9474 },
9475 ],
9476 cx,
9477 );
9478 multibuffer.insert_excerpts_after(
9479 excerpt_ids[0],
9480 buffer_2.clone(),
9481 [
9482 ExcerptRange {
9483 context: 8..12,
9484 primary: None,
9485 },
9486 ExcerptRange {
9487 context: 0..6,
9488 primary: None,
9489 },
9490 ],
9491 cx,
9492 );
9493 });
9494 });
9495
9496 // Apply the update of adding the excerpts.
9497 follower_1
9498 .update(cx, |follower, cx| {
9499 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9500 })
9501 .await
9502 .unwrap();
9503 assert_eq!(
9504 follower_1.update(cx, |editor, cx| editor.text(cx)),
9505 leader.update(cx, |editor, cx| editor.text(cx))
9506 );
9507 update_message.borrow_mut().take();
9508
9509 // Start following separately after it already has excerpts.
9510 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9511 let follower_2 = cx
9512 .update_window(*workspace.deref(), |_, cx| {
9513 Editor::from_state_proto(
9514 workspace.root_view(cx).unwrap().clone(),
9515 ViewId {
9516 creator: Default::default(),
9517 id: 0,
9518 },
9519 &mut state_message,
9520 cx,
9521 )
9522 })
9523 .unwrap()
9524 .unwrap()
9525 .await
9526 .unwrap();
9527 assert_eq!(
9528 follower_2.update(cx, |editor, cx| editor.text(cx)),
9529 leader.update(cx, |editor, cx| editor.text(cx))
9530 );
9531
9532 // Remove some excerpts.
9533 leader.update(cx, |leader, cx| {
9534 leader.buffer.update(cx, |multibuffer, cx| {
9535 let excerpt_ids = multibuffer.excerpt_ids();
9536 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9537 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9538 });
9539 });
9540
9541 // Apply the update of removing the excerpts.
9542 follower_1
9543 .update(cx, |follower, cx| {
9544 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9545 })
9546 .await
9547 .unwrap();
9548 follower_2
9549 .update(cx, |follower, cx| {
9550 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9551 })
9552 .await
9553 .unwrap();
9554 update_message.borrow_mut().take();
9555 assert_eq!(
9556 follower_1.update(cx, |editor, cx| editor.text(cx)),
9557 leader.update(cx, |editor, cx| editor.text(cx))
9558 );
9559}
9560
9561#[gpui::test]
9562async fn go_to_prev_overlapping_diagnostic(
9563 executor: BackgroundExecutor,
9564 cx: &mut gpui::TestAppContext,
9565) {
9566 init_test(cx, |_| {});
9567
9568 let mut cx = EditorTestContext::new(cx).await;
9569 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9570
9571 cx.set_state(indoc! {"
9572 ˇfn func(abc def: i32) -> u32 {
9573 }
9574 "});
9575
9576 cx.update(|cx| {
9577 project.update(cx, |project, cx| {
9578 project
9579 .update_diagnostics(
9580 LanguageServerId(0),
9581 lsp::PublishDiagnosticsParams {
9582 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9583 version: None,
9584 diagnostics: vec![
9585 lsp::Diagnostic {
9586 range: lsp::Range::new(
9587 lsp::Position::new(0, 11),
9588 lsp::Position::new(0, 12),
9589 ),
9590 severity: Some(lsp::DiagnosticSeverity::ERROR),
9591 ..Default::default()
9592 },
9593 lsp::Diagnostic {
9594 range: lsp::Range::new(
9595 lsp::Position::new(0, 12),
9596 lsp::Position::new(0, 15),
9597 ),
9598 severity: Some(lsp::DiagnosticSeverity::ERROR),
9599 ..Default::default()
9600 },
9601 lsp::Diagnostic {
9602 range: lsp::Range::new(
9603 lsp::Position::new(0, 25),
9604 lsp::Position::new(0, 28),
9605 ),
9606 severity: Some(lsp::DiagnosticSeverity::ERROR),
9607 ..Default::default()
9608 },
9609 ],
9610 },
9611 &[],
9612 cx,
9613 )
9614 .unwrap()
9615 });
9616 });
9617
9618 executor.run_until_parked();
9619
9620 cx.update_editor(|editor, cx| {
9621 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9622 });
9623
9624 cx.assert_editor_state(indoc! {"
9625 fn func(abc def: i32) -> ˇu32 {
9626 }
9627 "});
9628
9629 cx.update_editor(|editor, cx| {
9630 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9631 });
9632
9633 cx.assert_editor_state(indoc! {"
9634 fn func(abc ˇdef: i32) -> u32 {
9635 }
9636 "});
9637
9638 cx.update_editor(|editor, cx| {
9639 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9640 });
9641
9642 cx.assert_editor_state(indoc! {"
9643 fn func(abcˇ def: i32) -> u32 {
9644 }
9645 "});
9646
9647 cx.update_editor(|editor, cx| {
9648 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9649 });
9650
9651 cx.assert_editor_state(indoc! {"
9652 fn func(abc def: i32) -> ˇu32 {
9653 }
9654 "});
9655}
9656
9657#[gpui::test]
9658async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9659 init_test(cx, |_| {});
9660
9661 let mut cx = EditorTestContext::new(cx).await;
9662
9663 cx.set_state(indoc! {"
9664 fn func(abˇc def: i32) -> u32 {
9665 }
9666 "});
9667 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9668
9669 cx.update(|cx| {
9670 project.update(cx, |project, cx| {
9671 project.update_diagnostics(
9672 LanguageServerId(0),
9673 lsp::PublishDiagnosticsParams {
9674 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9675 version: None,
9676 diagnostics: vec![lsp::Diagnostic {
9677 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9678 severity: Some(lsp::DiagnosticSeverity::ERROR),
9679 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9680 ..Default::default()
9681 }],
9682 },
9683 &[],
9684 cx,
9685 )
9686 })
9687 }).unwrap();
9688 cx.run_until_parked();
9689 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9690 cx.run_until_parked();
9691 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9692}
9693
9694#[gpui::test]
9695async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9696 init_test(cx, |_| {});
9697
9698 let mut cx = EditorTestContext::new(cx).await;
9699
9700 let diff_base = r#"
9701 use some::mod;
9702
9703 const A: u32 = 42;
9704
9705 fn main() {
9706 println!("hello");
9707
9708 println!("world");
9709 }
9710 "#
9711 .unindent();
9712
9713 // Edits are modified, removed, modified, added
9714 cx.set_state(
9715 &r#"
9716 use some::modified;
9717
9718 ˇ
9719 fn main() {
9720 println!("hello there");
9721
9722 println!("around the");
9723 println!("world");
9724 }
9725 "#
9726 .unindent(),
9727 );
9728
9729 cx.set_diff_base(Some(&diff_base));
9730 executor.run_until_parked();
9731
9732 cx.update_editor(|editor, cx| {
9733 //Wrap around the bottom of the buffer
9734 for _ in 0..3 {
9735 editor.go_to_next_hunk(&GoToHunk, cx);
9736 }
9737 });
9738
9739 cx.assert_editor_state(
9740 &r#"
9741 ˇuse some::modified;
9742
9743
9744 fn main() {
9745 println!("hello there");
9746
9747 println!("around the");
9748 println!("world");
9749 }
9750 "#
9751 .unindent(),
9752 );
9753
9754 cx.update_editor(|editor, cx| {
9755 //Wrap around the top of the buffer
9756 for _ in 0..2 {
9757 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9758 }
9759 });
9760
9761 cx.assert_editor_state(
9762 &r#"
9763 use some::modified;
9764
9765
9766 fn main() {
9767 ˇ println!("hello there");
9768
9769 println!("around the");
9770 println!("world");
9771 }
9772 "#
9773 .unindent(),
9774 );
9775
9776 cx.update_editor(|editor, cx| {
9777 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9778 });
9779
9780 cx.assert_editor_state(
9781 &r#"
9782 use some::modified;
9783
9784 ˇ
9785 fn main() {
9786 println!("hello there");
9787
9788 println!("around the");
9789 println!("world");
9790 }
9791 "#
9792 .unindent(),
9793 );
9794
9795 cx.update_editor(|editor, cx| {
9796 for _ in 0..3 {
9797 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9798 }
9799 });
9800
9801 cx.assert_editor_state(
9802 &r#"
9803 use some::modified;
9804
9805
9806 fn main() {
9807 ˇ println!("hello there");
9808
9809 println!("around the");
9810 println!("world");
9811 }
9812 "#
9813 .unindent(),
9814 );
9815
9816 cx.update_editor(|editor, cx| {
9817 editor.fold(&Fold, cx);
9818
9819 //Make sure that the fold only gets one hunk
9820 for _ in 0..4 {
9821 editor.go_to_next_hunk(&GoToHunk, cx);
9822 }
9823 });
9824
9825 cx.assert_editor_state(
9826 &r#"
9827 ˇuse some::modified;
9828
9829
9830 fn main() {
9831 println!("hello there");
9832
9833 println!("around the");
9834 println!("world");
9835 }
9836 "#
9837 .unindent(),
9838 );
9839}
9840
9841#[test]
9842fn test_split_words() {
9843 fn split(text: &str) -> Vec<&str> {
9844 split_words(text).collect()
9845 }
9846
9847 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9848 assert_eq!(split("hello_world"), &["hello_", "world"]);
9849 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9850 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9851 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9852 assert_eq!(split("helloworld"), &["helloworld"]);
9853
9854 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9855}
9856
9857#[gpui::test]
9858async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9859 init_test(cx, |_| {});
9860
9861 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9862 let mut assert = |before, after| {
9863 let _state_context = cx.set_state(before);
9864 cx.update_editor(|editor, cx| {
9865 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9866 });
9867 cx.assert_editor_state(after);
9868 };
9869
9870 // Outside bracket jumps to outside of matching bracket
9871 assert("console.logˇ(var);", "console.log(var)ˇ;");
9872 assert("console.log(var)ˇ;", "console.logˇ(var);");
9873
9874 // Inside bracket jumps to inside of matching bracket
9875 assert("console.log(ˇvar);", "console.log(varˇ);");
9876 assert("console.log(varˇ);", "console.log(ˇvar);");
9877
9878 // When outside a bracket and inside, favor jumping to the inside bracket
9879 assert(
9880 "console.log('foo', [1, 2, 3]ˇ);",
9881 "console.log(ˇ'foo', [1, 2, 3]);",
9882 );
9883 assert(
9884 "console.log(ˇ'foo', [1, 2, 3]);",
9885 "console.log('foo', [1, 2, 3]ˇ);",
9886 );
9887
9888 // Bias forward if two options are equally likely
9889 assert(
9890 "let result = curried_fun()ˇ();",
9891 "let result = curried_fun()()ˇ;",
9892 );
9893
9894 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9895 assert(
9896 indoc! {"
9897 function test() {
9898 console.log('test')ˇ
9899 }"},
9900 indoc! {"
9901 function test() {
9902 console.logˇ('test')
9903 }"},
9904 );
9905}
9906
9907#[gpui::test]
9908async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9909 init_test(cx, |_| {});
9910
9911 let fs = FakeFs::new(cx.executor());
9912 fs.insert_tree(
9913 "/a",
9914 json!({
9915 "main.rs": "fn main() { let a = 5; }",
9916 "other.rs": "// Test file",
9917 }),
9918 )
9919 .await;
9920 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9921
9922 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9923 language_registry.add(Arc::new(Language::new(
9924 LanguageConfig {
9925 name: "Rust".into(),
9926 matcher: LanguageMatcher {
9927 path_suffixes: vec!["rs".to_string()],
9928 ..Default::default()
9929 },
9930 brackets: BracketPairConfig {
9931 pairs: vec![BracketPair {
9932 start: "{".to_string(),
9933 end: "}".to_string(),
9934 close: true,
9935 surround: true,
9936 newline: true,
9937 }],
9938 disabled_scopes_by_bracket_ix: Vec::new(),
9939 },
9940 ..Default::default()
9941 },
9942 Some(tree_sitter_rust::LANGUAGE.into()),
9943 )));
9944 let mut fake_servers = language_registry.register_fake_lsp(
9945 "Rust",
9946 FakeLspAdapter {
9947 capabilities: lsp::ServerCapabilities {
9948 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9949 first_trigger_character: "{".to_string(),
9950 more_trigger_character: None,
9951 }),
9952 ..Default::default()
9953 },
9954 ..Default::default()
9955 },
9956 );
9957
9958 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9959
9960 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9961
9962 let worktree_id = workspace
9963 .update(cx, |workspace, cx| {
9964 workspace.project().update(cx, |project, cx| {
9965 project.worktrees(cx).next().unwrap().read(cx).id()
9966 })
9967 })
9968 .unwrap();
9969
9970 let buffer = project
9971 .update(cx, |project, cx| {
9972 project.open_local_buffer("/a/main.rs", cx)
9973 })
9974 .await
9975 .unwrap();
9976 cx.executor().run_until_parked();
9977 cx.executor().start_waiting();
9978 let fake_server = fake_servers.next().await.unwrap();
9979 let editor_handle = workspace
9980 .update(cx, |workspace, cx| {
9981 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9982 })
9983 .unwrap()
9984 .await
9985 .unwrap()
9986 .downcast::<Editor>()
9987 .unwrap();
9988
9989 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9990 assert_eq!(
9991 params.text_document_position.text_document.uri,
9992 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9993 );
9994 assert_eq!(
9995 params.text_document_position.position,
9996 lsp::Position::new(0, 21),
9997 );
9998
9999 Ok(Some(vec![lsp::TextEdit {
10000 new_text: "]".to_string(),
10001 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10002 }]))
10003 });
10004
10005 editor_handle.update(cx, |editor, cx| {
10006 editor.focus(cx);
10007 editor.change_selections(None, cx, |s| {
10008 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10009 });
10010 editor.handle_input("{", cx);
10011 });
10012
10013 cx.executor().run_until_parked();
10014
10015 buffer.update(cx, |buffer, _| {
10016 assert_eq!(
10017 buffer.text(),
10018 "fn main() { let a = {5}; }",
10019 "No extra braces from on type formatting should appear in the buffer"
10020 )
10021 });
10022}
10023
10024#[gpui::test]
10025async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10026 init_test(cx, |_| {});
10027
10028 let fs = FakeFs::new(cx.executor());
10029 fs.insert_tree(
10030 "/a",
10031 json!({
10032 "main.rs": "fn main() { let a = 5; }",
10033 "other.rs": "// Test file",
10034 }),
10035 )
10036 .await;
10037
10038 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10039
10040 let server_restarts = Arc::new(AtomicUsize::new(0));
10041 let closure_restarts = Arc::clone(&server_restarts);
10042 let language_server_name = "test language server";
10043 let language_name: LanguageName = "Rust".into();
10044
10045 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10046 language_registry.add(Arc::new(Language::new(
10047 LanguageConfig {
10048 name: language_name.clone(),
10049 matcher: LanguageMatcher {
10050 path_suffixes: vec!["rs".to_string()],
10051 ..Default::default()
10052 },
10053 ..Default::default()
10054 },
10055 Some(tree_sitter_rust::LANGUAGE.into()),
10056 )));
10057 let mut fake_servers = language_registry.register_fake_lsp(
10058 "Rust",
10059 FakeLspAdapter {
10060 name: language_server_name,
10061 initialization_options: Some(json!({
10062 "testOptionValue": true
10063 })),
10064 initializer: Some(Box::new(move |fake_server| {
10065 let task_restarts = Arc::clone(&closure_restarts);
10066 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10067 task_restarts.fetch_add(1, atomic::Ordering::Release);
10068 futures::future::ready(Ok(()))
10069 });
10070 })),
10071 ..Default::default()
10072 },
10073 );
10074
10075 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10076 let _buffer = project
10077 .update(cx, |project, cx| {
10078 project.open_local_buffer("/a/main.rs", cx)
10079 })
10080 .await
10081 .unwrap();
10082 let _fake_server = fake_servers.next().await.unwrap();
10083 update_test_language_settings(cx, |language_settings| {
10084 language_settings.languages.insert(
10085 language_name.clone(),
10086 LanguageSettingsContent {
10087 tab_size: NonZeroU32::new(8),
10088 ..Default::default()
10089 },
10090 );
10091 });
10092 cx.executor().run_until_parked();
10093 assert_eq!(
10094 server_restarts.load(atomic::Ordering::Acquire),
10095 0,
10096 "Should not restart LSP server on an unrelated change"
10097 );
10098
10099 update_test_project_settings(cx, |project_settings| {
10100 project_settings.lsp.insert(
10101 "Some other server name".into(),
10102 LspSettings {
10103 binary: None,
10104 settings: None,
10105 initialization_options: Some(json!({
10106 "some other init value": false
10107 })),
10108 },
10109 );
10110 });
10111 cx.executor().run_until_parked();
10112 assert_eq!(
10113 server_restarts.load(atomic::Ordering::Acquire),
10114 0,
10115 "Should not restart LSP server on an unrelated LSP settings change"
10116 );
10117
10118 update_test_project_settings(cx, |project_settings| {
10119 project_settings.lsp.insert(
10120 language_server_name.into(),
10121 LspSettings {
10122 binary: None,
10123 settings: None,
10124 initialization_options: Some(json!({
10125 "anotherInitValue": false
10126 })),
10127 },
10128 );
10129 });
10130 cx.executor().run_until_parked();
10131 assert_eq!(
10132 server_restarts.load(atomic::Ordering::Acquire),
10133 1,
10134 "Should restart LSP server on a related LSP settings change"
10135 );
10136
10137 update_test_project_settings(cx, |project_settings| {
10138 project_settings.lsp.insert(
10139 language_server_name.into(),
10140 LspSettings {
10141 binary: None,
10142 settings: None,
10143 initialization_options: Some(json!({
10144 "anotherInitValue": false
10145 })),
10146 },
10147 );
10148 });
10149 cx.executor().run_until_parked();
10150 assert_eq!(
10151 server_restarts.load(atomic::Ordering::Acquire),
10152 1,
10153 "Should not restart LSP server on a related LSP settings change that is the same"
10154 );
10155
10156 update_test_project_settings(cx, |project_settings| {
10157 project_settings.lsp.insert(
10158 language_server_name.into(),
10159 LspSettings {
10160 binary: None,
10161 settings: None,
10162 initialization_options: None,
10163 },
10164 );
10165 });
10166 cx.executor().run_until_parked();
10167 assert_eq!(
10168 server_restarts.load(atomic::Ordering::Acquire),
10169 2,
10170 "Should restart LSP server on another related LSP settings change"
10171 );
10172}
10173
10174#[gpui::test]
10175async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10176 init_test(cx, |_| {});
10177
10178 let mut cx = EditorLspTestContext::new_rust(
10179 lsp::ServerCapabilities {
10180 completion_provider: Some(lsp::CompletionOptions {
10181 trigger_characters: Some(vec![".".to_string()]),
10182 resolve_provider: Some(true),
10183 ..Default::default()
10184 }),
10185 ..Default::default()
10186 },
10187 cx,
10188 )
10189 .await;
10190
10191 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10192 cx.simulate_keystroke(".");
10193 let completion_item = lsp::CompletionItem {
10194 label: "some".into(),
10195 kind: Some(lsp::CompletionItemKind::SNIPPET),
10196 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10197 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10198 kind: lsp::MarkupKind::Markdown,
10199 value: "```rust\nSome(2)\n```".to_string(),
10200 })),
10201 deprecated: Some(false),
10202 sort_text: Some("fffffff2".to_string()),
10203 filter_text: Some("some".to_string()),
10204 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10205 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10206 range: lsp::Range {
10207 start: lsp::Position {
10208 line: 0,
10209 character: 22,
10210 },
10211 end: lsp::Position {
10212 line: 0,
10213 character: 22,
10214 },
10215 },
10216 new_text: "Some(2)".to_string(),
10217 })),
10218 additional_text_edits: Some(vec![lsp::TextEdit {
10219 range: lsp::Range {
10220 start: lsp::Position {
10221 line: 0,
10222 character: 20,
10223 },
10224 end: lsp::Position {
10225 line: 0,
10226 character: 22,
10227 },
10228 },
10229 new_text: "".to_string(),
10230 }]),
10231 ..Default::default()
10232 };
10233
10234 let closure_completion_item = completion_item.clone();
10235 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10236 let task_completion_item = closure_completion_item.clone();
10237 async move {
10238 Ok(Some(lsp::CompletionResponse::Array(vec![
10239 task_completion_item,
10240 ])))
10241 }
10242 });
10243
10244 request.next().await;
10245
10246 cx.condition(|editor, _| editor.context_menu_visible())
10247 .await;
10248 let apply_additional_edits = cx.update_editor(|editor, cx| {
10249 editor
10250 .confirm_completion(&ConfirmCompletion::default(), cx)
10251 .unwrap()
10252 });
10253 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10254
10255 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10256 let task_completion_item = completion_item.clone();
10257 async move { Ok(task_completion_item) }
10258 })
10259 .next()
10260 .await
10261 .unwrap();
10262 apply_additional_edits.await.unwrap();
10263 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10264}
10265
10266#[gpui::test]
10267async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10268 init_test(cx, |_| {});
10269
10270 let mut cx = EditorLspTestContext::new(
10271 Language::new(
10272 LanguageConfig {
10273 matcher: LanguageMatcher {
10274 path_suffixes: vec!["jsx".into()],
10275 ..Default::default()
10276 },
10277 overrides: [(
10278 "element".into(),
10279 LanguageConfigOverride {
10280 word_characters: Override::Set(['-'].into_iter().collect()),
10281 ..Default::default()
10282 },
10283 )]
10284 .into_iter()
10285 .collect(),
10286 ..Default::default()
10287 },
10288 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10289 )
10290 .with_override_query("(jsx_self_closing_element) @element")
10291 .unwrap(),
10292 lsp::ServerCapabilities {
10293 completion_provider: Some(lsp::CompletionOptions {
10294 trigger_characters: Some(vec![":".to_string()]),
10295 ..Default::default()
10296 }),
10297 ..Default::default()
10298 },
10299 cx,
10300 )
10301 .await;
10302
10303 cx.lsp
10304 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10305 Ok(Some(lsp::CompletionResponse::Array(vec![
10306 lsp::CompletionItem {
10307 label: "bg-blue".into(),
10308 ..Default::default()
10309 },
10310 lsp::CompletionItem {
10311 label: "bg-red".into(),
10312 ..Default::default()
10313 },
10314 lsp::CompletionItem {
10315 label: "bg-yellow".into(),
10316 ..Default::default()
10317 },
10318 ])))
10319 });
10320
10321 cx.set_state(r#"<p class="bgˇ" />"#);
10322
10323 // Trigger completion when typing a dash, because the dash is an extra
10324 // word character in the 'element' scope, which contains the cursor.
10325 cx.simulate_keystroke("-");
10326 cx.executor().run_until_parked();
10327 cx.update_editor(|editor, _| {
10328 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10329 assert_eq!(
10330 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10331 &["bg-red", "bg-blue", "bg-yellow"]
10332 );
10333 } else {
10334 panic!("expected completion menu to be open");
10335 }
10336 });
10337
10338 cx.simulate_keystroke("l");
10339 cx.executor().run_until_parked();
10340 cx.update_editor(|editor, _| {
10341 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10342 assert_eq!(
10343 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10344 &["bg-blue", "bg-yellow"]
10345 );
10346 } else {
10347 panic!("expected completion menu to be open");
10348 }
10349 });
10350
10351 // When filtering completions, consider the character after the '-' to
10352 // be the start of a subword.
10353 cx.set_state(r#"<p class="yelˇ" />"#);
10354 cx.simulate_keystroke("l");
10355 cx.executor().run_until_parked();
10356 cx.update_editor(|editor, _| {
10357 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10358 assert_eq!(
10359 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10360 &["bg-yellow"]
10361 );
10362 } else {
10363 panic!("expected completion menu to be open");
10364 }
10365 });
10366}
10367
10368#[gpui::test]
10369async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10370 init_test(cx, |settings| {
10371 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10372 FormatterList(vec![Formatter::Prettier].into()),
10373 ))
10374 });
10375
10376 let fs = FakeFs::new(cx.executor());
10377 fs.insert_file("/file.ts", Default::default()).await;
10378
10379 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10380 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10381
10382 language_registry.add(Arc::new(Language::new(
10383 LanguageConfig {
10384 name: "TypeScript".into(),
10385 matcher: LanguageMatcher {
10386 path_suffixes: vec!["ts".to_string()],
10387 ..Default::default()
10388 },
10389 ..Default::default()
10390 },
10391 Some(tree_sitter_rust::LANGUAGE.into()),
10392 )));
10393 update_test_language_settings(cx, |settings| {
10394 settings.defaults.prettier = Some(PrettierSettings {
10395 allowed: true,
10396 ..PrettierSettings::default()
10397 });
10398 });
10399
10400 let test_plugin = "test_plugin";
10401 let _ = language_registry.register_fake_lsp(
10402 "TypeScript",
10403 FakeLspAdapter {
10404 prettier_plugins: vec![test_plugin],
10405 ..Default::default()
10406 },
10407 );
10408
10409 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10410 let buffer = project
10411 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10412 .await
10413 .unwrap();
10414
10415 let buffer_text = "one\ntwo\nthree\n";
10416 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10417 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10418 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10419
10420 editor
10421 .update(cx, |editor, cx| {
10422 editor.perform_format(
10423 project.clone(),
10424 FormatTrigger::Manual,
10425 FormatTarget::Buffer,
10426 cx,
10427 )
10428 })
10429 .unwrap()
10430 .await;
10431 assert_eq!(
10432 editor.update(cx, |editor, cx| editor.text(cx)),
10433 buffer_text.to_string() + prettier_format_suffix,
10434 "Test prettier formatting was not applied to the original buffer text",
10435 );
10436
10437 update_test_language_settings(cx, |settings| {
10438 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10439 });
10440 let format = editor.update(cx, |editor, cx| {
10441 editor.perform_format(
10442 project.clone(),
10443 FormatTrigger::Manual,
10444 FormatTarget::Buffer,
10445 cx,
10446 )
10447 });
10448 format.await.unwrap();
10449 assert_eq!(
10450 editor.update(cx, |editor, cx| editor.text(cx)),
10451 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10452 "Autoformatting (via test prettier) was not applied to the original buffer text",
10453 );
10454}
10455
10456#[gpui::test]
10457async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10458 init_test(cx, |_| {});
10459 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10460 let base_text = indoc! {r#"struct Row;
10461struct Row1;
10462struct Row2;
10463
10464struct Row4;
10465struct Row5;
10466struct Row6;
10467
10468struct Row8;
10469struct Row9;
10470struct Row10;"#};
10471
10472 // When addition hunks are not adjacent to carets, no hunk revert is performed
10473 assert_hunk_revert(
10474 indoc! {r#"struct Row;
10475 struct Row1;
10476 struct Row1.1;
10477 struct Row1.2;
10478 struct Row2;ˇ
10479
10480 struct Row4;
10481 struct Row5;
10482 struct Row6;
10483
10484 struct Row8;
10485 ˇstruct Row9;
10486 struct Row9.1;
10487 struct Row9.2;
10488 struct Row9.3;
10489 struct Row10;"#},
10490 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10491 indoc! {r#"struct Row;
10492 struct Row1;
10493 struct Row1.1;
10494 struct Row1.2;
10495 struct Row2;ˇ
10496
10497 struct Row4;
10498 struct Row5;
10499 struct Row6;
10500
10501 struct Row8;
10502 ˇstruct Row9;
10503 struct Row9.1;
10504 struct Row9.2;
10505 struct Row9.3;
10506 struct Row10;"#},
10507 base_text,
10508 &mut cx,
10509 );
10510 // Same for selections
10511 assert_hunk_revert(
10512 indoc! {r#"struct Row;
10513 struct Row1;
10514 struct Row2;
10515 struct Row2.1;
10516 struct Row2.2;
10517 «ˇ
10518 struct Row4;
10519 struct» Row5;
10520 «struct Row6;
10521 ˇ»
10522 struct Row9.1;
10523 struct Row9.2;
10524 struct Row9.3;
10525 struct Row8;
10526 struct Row9;
10527 struct Row10;"#},
10528 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10529 indoc! {r#"struct Row;
10530 struct Row1;
10531 struct Row2;
10532 struct Row2.1;
10533 struct Row2.2;
10534 «ˇ
10535 struct Row4;
10536 struct» Row5;
10537 «struct Row6;
10538 ˇ»
10539 struct Row9.1;
10540 struct Row9.2;
10541 struct Row9.3;
10542 struct Row8;
10543 struct Row9;
10544 struct Row10;"#},
10545 base_text,
10546 &mut cx,
10547 );
10548
10549 // When carets and selections intersect the addition hunks, those are reverted.
10550 // Adjacent carets got merged.
10551 assert_hunk_revert(
10552 indoc! {r#"struct Row;
10553 ˇ// something on the top
10554 struct Row1;
10555 struct Row2;
10556 struct Roˇw3.1;
10557 struct Row2.2;
10558 struct Row2.3;ˇ
10559
10560 struct Row4;
10561 struct ˇRow5.1;
10562 struct Row5.2;
10563 struct «Rowˇ»5.3;
10564 struct Row5;
10565 struct Row6;
10566 ˇ
10567 struct Row9.1;
10568 struct «Rowˇ»9.2;
10569 struct «ˇRow»9.3;
10570 struct Row8;
10571 struct Row9;
10572 «ˇ// something on bottom»
10573 struct Row10;"#},
10574 vec![
10575 DiffHunkStatus::Added,
10576 DiffHunkStatus::Added,
10577 DiffHunkStatus::Added,
10578 DiffHunkStatus::Added,
10579 DiffHunkStatus::Added,
10580 ],
10581 indoc! {r#"struct Row;
10582 ˇstruct Row1;
10583 struct Row2;
10584 ˇ
10585 struct Row4;
10586 ˇstruct Row5;
10587 struct Row6;
10588 ˇ
10589 ˇstruct Row8;
10590 struct Row9;
10591 ˇstruct Row10;"#},
10592 base_text,
10593 &mut cx,
10594 );
10595}
10596
10597#[gpui::test]
10598async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10599 init_test(cx, |_| {});
10600 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10601 let base_text = indoc! {r#"struct Row;
10602struct Row1;
10603struct Row2;
10604
10605struct Row4;
10606struct Row5;
10607struct Row6;
10608
10609struct Row8;
10610struct Row9;
10611struct Row10;"#};
10612
10613 // Modification hunks behave the same as the addition ones.
10614 assert_hunk_revert(
10615 indoc! {r#"struct Row;
10616 struct Row1;
10617 struct Row33;
10618 ˇ
10619 struct Row4;
10620 struct Row5;
10621 struct Row6;
10622 ˇ
10623 struct Row99;
10624 struct Row9;
10625 struct Row10;"#},
10626 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10627 indoc! {r#"struct Row;
10628 struct Row1;
10629 struct Row33;
10630 ˇ
10631 struct Row4;
10632 struct Row5;
10633 struct Row6;
10634 ˇ
10635 struct Row99;
10636 struct Row9;
10637 struct Row10;"#},
10638 base_text,
10639 &mut cx,
10640 );
10641 assert_hunk_revert(
10642 indoc! {r#"struct Row;
10643 struct Row1;
10644 struct Row33;
10645 «ˇ
10646 struct Row4;
10647 struct» Row5;
10648 «struct Row6;
10649 ˇ»
10650 struct Row99;
10651 struct Row9;
10652 struct Row10;"#},
10653 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10654 indoc! {r#"struct Row;
10655 struct Row1;
10656 struct Row33;
10657 «ˇ
10658 struct Row4;
10659 struct» Row5;
10660 «struct Row6;
10661 ˇ»
10662 struct Row99;
10663 struct Row9;
10664 struct Row10;"#},
10665 base_text,
10666 &mut cx,
10667 );
10668
10669 assert_hunk_revert(
10670 indoc! {r#"ˇstruct Row1.1;
10671 struct Row1;
10672 «ˇstr»uct Row22;
10673
10674 struct ˇRow44;
10675 struct Row5;
10676 struct «Rˇ»ow66;ˇ
10677
10678 «struˇ»ct Row88;
10679 struct Row9;
10680 struct Row1011;ˇ"#},
10681 vec![
10682 DiffHunkStatus::Modified,
10683 DiffHunkStatus::Modified,
10684 DiffHunkStatus::Modified,
10685 DiffHunkStatus::Modified,
10686 DiffHunkStatus::Modified,
10687 DiffHunkStatus::Modified,
10688 ],
10689 indoc! {r#"struct Row;
10690 ˇstruct Row1;
10691 struct Row2;
10692 ˇ
10693 struct Row4;
10694 ˇstruct Row5;
10695 struct Row6;
10696 ˇ
10697 struct Row8;
10698 ˇstruct Row9;
10699 struct Row10;ˇ"#},
10700 base_text,
10701 &mut cx,
10702 );
10703}
10704
10705#[gpui::test]
10706async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10707 init_test(cx, |_| {});
10708 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10709 let base_text = indoc! {r#"struct Row;
10710struct Row1;
10711struct Row2;
10712
10713struct Row4;
10714struct Row5;
10715struct Row6;
10716
10717struct Row8;
10718struct Row9;
10719struct Row10;"#};
10720
10721 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10722 assert_hunk_revert(
10723 indoc! {r#"struct Row;
10724 struct Row2;
10725
10726 ˇstruct Row4;
10727 struct Row5;
10728 struct Row6;
10729 ˇ
10730 struct Row8;
10731 struct Row10;"#},
10732 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10733 indoc! {r#"struct Row;
10734 struct Row2;
10735
10736 ˇstruct Row4;
10737 struct Row5;
10738 struct Row6;
10739 ˇ
10740 struct Row8;
10741 struct Row10;"#},
10742 base_text,
10743 &mut cx,
10744 );
10745 assert_hunk_revert(
10746 indoc! {r#"struct Row;
10747 struct Row2;
10748
10749 «ˇstruct Row4;
10750 struct» Row5;
10751 «struct Row6;
10752 ˇ»
10753 struct Row8;
10754 struct Row10;"#},
10755 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10756 indoc! {r#"struct Row;
10757 struct Row2;
10758
10759 «ˇstruct Row4;
10760 struct» Row5;
10761 «struct Row6;
10762 ˇ»
10763 struct Row8;
10764 struct Row10;"#},
10765 base_text,
10766 &mut cx,
10767 );
10768
10769 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10770 assert_hunk_revert(
10771 indoc! {r#"struct Row;
10772 ˇstruct Row2;
10773
10774 struct Row4;
10775 struct Row5;
10776 struct Row6;
10777
10778 struct Row8;ˇ
10779 struct Row10;"#},
10780 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10781 indoc! {r#"struct Row;
10782 struct Row1;
10783 ˇstruct Row2;
10784
10785 struct Row4;
10786 struct Row5;
10787 struct Row6;
10788
10789 struct Row8;ˇ
10790 struct Row9;
10791 struct Row10;"#},
10792 base_text,
10793 &mut cx,
10794 );
10795 assert_hunk_revert(
10796 indoc! {r#"struct Row;
10797 struct Row2«ˇ;
10798 struct Row4;
10799 struct» Row5;
10800 «struct Row6;
10801
10802 struct Row8;ˇ»
10803 struct Row10;"#},
10804 vec![
10805 DiffHunkStatus::Removed,
10806 DiffHunkStatus::Removed,
10807 DiffHunkStatus::Removed,
10808 ],
10809 indoc! {r#"struct Row;
10810 struct Row1;
10811 struct Row2«ˇ;
10812
10813 struct Row4;
10814 struct» Row5;
10815 «struct Row6;
10816
10817 struct Row8;ˇ»
10818 struct Row9;
10819 struct Row10;"#},
10820 base_text,
10821 &mut cx,
10822 );
10823}
10824
10825#[gpui::test]
10826async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10827 init_test(cx, |_| {});
10828
10829 let cols = 4;
10830 let rows = 10;
10831 let sample_text_1 = sample_text(rows, cols, 'a');
10832 assert_eq!(
10833 sample_text_1,
10834 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10835 );
10836 let sample_text_2 = sample_text(rows, cols, 'l');
10837 assert_eq!(
10838 sample_text_2,
10839 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10840 );
10841 let sample_text_3 = sample_text(rows, cols, 'v');
10842 assert_eq!(
10843 sample_text_3,
10844 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10845 );
10846
10847 fn diff_every_buffer_row(
10848 buffer: &Model<Buffer>,
10849 sample_text: String,
10850 cols: usize,
10851 cx: &mut gpui::TestAppContext,
10852 ) {
10853 // revert first character in each row, creating one large diff hunk per buffer
10854 let is_first_char = |offset: usize| offset % cols == 0;
10855 buffer.update(cx, |buffer, cx| {
10856 buffer.set_text(
10857 sample_text
10858 .chars()
10859 .enumerate()
10860 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10861 .collect::<String>(),
10862 cx,
10863 );
10864 buffer.set_diff_base(Some(sample_text), cx);
10865 });
10866 cx.executor().run_until_parked();
10867 }
10868
10869 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10870 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10871
10872 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10873 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10874
10875 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10876 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10877
10878 let multibuffer = cx.new_model(|cx| {
10879 let mut multibuffer = MultiBuffer::new(ReadWrite);
10880 multibuffer.push_excerpts(
10881 buffer_1.clone(),
10882 [
10883 ExcerptRange {
10884 context: Point::new(0, 0)..Point::new(3, 0),
10885 primary: None,
10886 },
10887 ExcerptRange {
10888 context: Point::new(5, 0)..Point::new(7, 0),
10889 primary: None,
10890 },
10891 ExcerptRange {
10892 context: Point::new(9, 0)..Point::new(10, 4),
10893 primary: None,
10894 },
10895 ],
10896 cx,
10897 );
10898 multibuffer.push_excerpts(
10899 buffer_2.clone(),
10900 [
10901 ExcerptRange {
10902 context: Point::new(0, 0)..Point::new(3, 0),
10903 primary: None,
10904 },
10905 ExcerptRange {
10906 context: Point::new(5, 0)..Point::new(7, 0),
10907 primary: None,
10908 },
10909 ExcerptRange {
10910 context: Point::new(9, 0)..Point::new(10, 4),
10911 primary: None,
10912 },
10913 ],
10914 cx,
10915 );
10916 multibuffer.push_excerpts(
10917 buffer_3.clone(),
10918 [
10919 ExcerptRange {
10920 context: Point::new(0, 0)..Point::new(3, 0),
10921 primary: None,
10922 },
10923 ExcerptRange {
10924 context: Point::new(5, 0)..Point::new(7, 0),
10925 primary: None,
10926 },
10927 ExcerptRange {
10928 context: Point::new(9, 0)..Point::new(10, 4),
10929 primary: None,
10930 },
10931 ],
10932 cx,
10933 );
10934 multibuffer
10935 });
10936
10937 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10938 editor.update(cx, |editor, cx| {
10939 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");
10940 editor.select_all(&SelectAll, cx);
10941 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10942 });
10943 cx.executor().run_until_parked();
10944 // When all ranges are selected, all buffer hunks are reverted.
10945 editor.update(cx, |editor, cx| {
10946 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");
10947 });
10948 buffer_1.update(cx, |buffer, _| {
10949 assert_eq!(buffer.text(), sample_text_1);
10950 });
10951 buffer_2.update(cx, |buffer, _| {
10952 assert_eq!(buffer.text(), sample_text_2);
10953 });
10954 buffer_3.update(cx, |buffer, _| {
10955 assert_eq!(buffer.text(), sample_text_3);
10956 });
10957
10958 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10959 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10960 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10961 editor.update(cx, |editor, cx| {
10962 editor.change_selections(None, cx, |s| {
10963 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10964 });
10965 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10966 });
10967 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10968 // but not affect buffer_2 and its related excerpts.
10969 editor.update(cx, |editor, cx| {
10970 assert_eq!(
10971 editor.text(cx),
10972 "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"
10973 );
10974 });
10975 buffer_1.update(cx, |buffer, _| {
10976 assert_eq!(buffer.text(), sample_text_1);
10977 });
10978 buffer_2.update(cx, |buffer, _| {
10979 assert_eq!(
10980 buffer.text(),
10981 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10982 );
10983 });
10984 buffer_3.update(cx, |buffer, _| {
10985 assert_eq!(
10986 buffer.text(),
10987 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10988 );
10989 });
10990}
10991
10992#[gpui::test]
10993async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10994 init_test(cx, |_| {});
10995
10996 let cols = 4;
10997 let rows = 10;
10998 let sample_text_1 = sample_text(rows, cols, 'a');
10999 assert_eq!(
11000 sample_text_1,
11001 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11002 );
11003 let sample_text_2 = sample_text(rows, cols, 'l');
11004 assert_eq!(
11005 sample_text_2,
11006 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11007 );
11008 let sample_text_3 = sample_text(rows, cols, 'v');
11009 assert_eq!(
11010 sample_text_3,
11011 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11012 );
11013
11014 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11015 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11016 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11017
11018 let multi_buffer = cx.new_model(|cx| {
11019 let mut multibuffer = MultiBuffer::new(ReadWrite);
11020 multibuffer.push_excerpts(
11021 buffer_1.clone(),
11022 [
11023 ExcerptRange {
11024 context: Point::new(0, 0)..Point::new(3, 0),
11025 primary: None,
11026 },
11027 ExcerptRange {
11028 context: Point::new(5, 0)..Point::new(7, 0),
11029 primary: None,
11030 },
11031 ExcerptRange {
11032 context: Point::new(9, 0)..Point::new(10, 4),
11033 primary: None,
11034 },
11035 ],
11036 cx,
11037 );
11038 multibuffer.push_excerpts(
11039 buffer_2.clone(),
11040 [
11041 ExcerptRange {
11042 context: Point::new(0, 0)..Point::new(3, 0),
11043 primary: None,
11044 },
11045 ExcerptRange {
11046 context: Point::new(5, 0)..Point::new(7, 0),
11047 primary: None,
11048 },
11049 ExcerptRange {
11050 context: Point::new(9, 0)..Point::new(10, 4),
11051 primary: None,
11052 },
11053 ],
11054 cx,
11055 );
11056 multibuffer.push_excerpts(
11057 buffer_3.clone(),
11058 [
11059 ExcerptRange {
11060 context: Point::new(0, 0)..Point::new(3, 0),
11061 primary: None,
11062 },
11063 ExcerptRange {
11064 context: Point::new(5, 0)..Point::new(7, 0),
11065 primary: None,
11066 },
11067 ExcerptRange {
11068 context: Point::new(9, 0)..Point::new(10, 4),
11069 primary: None,
11070 },
11071 ],
11072 cx,
11073 );
11074 multibuffer
11075 });
11076
11077 let fs = FakeFs::new(cx.executor());
11078 fs.insert_tree(
11079 "/a",
11080 json!({
11081 "main.rs": sample_text_1,
11082 "other.rs": sample_text_2,
11083 "lib.rs": sample_text_3,
11084 }),
11085 )
11086 .await;
11087 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11088 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11089 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11090 let multi_buffer_editor = cx.new_view(|cx| {
11091 Editor::new(
11092 EditorMode::Full,
11093 multi_buffer,
11094 Some(project.clone()),
11095 true,
11096 cx,
11097 )
11098 });
11099 let multibuffer_item_id = workspace
11100 .update(cx, |workspace, cx| {
11101 assert!(
11102 workspace.active_item(cx).is_none(),
11103 "active item should be None before the first item is added"
11104 );
11105 workspace.add_item_to_active_pane(
11106 Box::new(multi_buffer_editor.clone()),
11107 None,
11108 true,
11109 cx,
11110 );
11111 let active_item = workspace
11112 .active_item(cx)
11113 .expect("should have an active item after adding the multi buffer");
11114 assert!(
11115 !active_item.is_singleton(cx),
11116 "A multi buffer was expected to active after adding"
11117 );
11118 active_item.item_id()
11119 })
11120 .unwrap();
11121 cx.executor().run_until_parked();
11122
11123 multi_buffer_editor.update(cx, |editor, cx| {
11124 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11125 editor.open_excerpts(&OpenExcerpts, cx);
11126 });
11127 cx.executor().run_until_parked();
11128 let first_item_id = workspace
11129 .update(cx, |workspace, cx| {
11130 let active_item = workspace
11131 .active_item(cx)
11132 .expect("should have an active item after navigating into the 1st buffer");
11133 let first_item_id = active_item.item_id();
11134 assert_ne!(
11135 first_item_id, multibuffer_item_id,
11136 "Should navigate into the 1st buffer and activate it"
11137 );
11138 assert!(
11139 active_item.is_singleton(cx),
11140 "New active item should be a singleton buffer"
11141 );
11142 assert_eq!(
11143 active_item
11144 .act_as::<Editor>(cx)
11145 .expect("should have navigated into an editor for the 1st buffer")
11146 .read(cx)
11147 .text(cx),
11148 sample_text_1
11149 );
11150
11151 workspace
11152 .go_back(workspace.active_pane().downgrade(), cx)
11153 .detach_and_log_err(cx);
11154
11155 first_item_id
11156 })
11157 .unwrap();
11158 cx.executor().run_until_parked();
11159 workspace
11160 .update(cx, |workspace, cx| {
11161 let active_item = workspace
11162 .active_item(cx)
11163 .expect("should have an active item after navigating back");
11164 assert_eq!(
11165 active_item.item_id(),
11166 multibuffer_item_id,
11167 "Should navigate back to the multi buffer"
11168 );
11169 assert!(!active_item.is_singleton(cx));
11170 })
11171 .unwrap();
11172
11173 multi_buffer_editor.update(cx, |editor, cx| {
11174 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11175 s.select_ranges(Some(39..40))
11176 });
11177 editor.open_excerpts(&OpenExcerpts, cx);
11178 });
11179 cx.executor().run_until_parked();
11180 let second_item_id = workspace
11181 .update(cx, |workspace, cx| {
11182 let active_item = workspace
11183 .active_item(cx)
11184 .expect("should have an active item after navigating into the 2nd buffer");
11185 let second_item_id = active_item.item_id();
11186 assert_ne!(
11187 second_item_id, multibuffer_item_id,
11188 "Should navigate away from the multibuffer"
11189 );
11190 assert_ne!(
11191 second_item_id, first_item_id,
11192 "Should navigate into the 2nd buffer and activate it"
11193 );
11194 assert!(
11195 active_item.is_singleton(cx),
11196 "New active item should be a singleton buffer"
11197 );
11198 assert_eq!(
11199 active_item
11200 .act_as::<Editor>(cx)
11201 .expect("should have navigated into an editor")
11202 .read(cx)
11203 .text(cx),
11204 sample_text_2
11205 );
11206
11207 workspace
11208 .go_back(workspace.active_pane().downgrade(), cx)
11209 .detach_and_log_err(cx);
11210
11211 second_item_id
11212 })
11213 .unwrap();
11214 cx.executor().run_until_parked();
11215 workspace
11216 .update(cx, |workspace, cx| {
11217 let active_item = workspace
11218 .active_item(cx)
11219 .expect("should have an active item after navigating back from the 2nd buffer");
11220 assert_eq!(
11221 active_item.item_id(),
11222 multibuffer_item_id,
11223 "Should navigate back from the 2nd buffer to the multi buffer"
11224 );
11225 assert!(!active_item.is_singleton(cx));
11226 })
11227 .unwrap();
11228
11229 multi_buffer_editor.update(cx, |editor, cx| {
11230 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11231 s.select_ranges(Some(60..70))
11232 });
11233 editor.open_excerpts(&OpenExcerpts, cx);
11234 });
11235 cx.executor().run_until_parked();
11236 workspace
11237 .update(cx, |workspace, cx| {
11238 let active_item = workspace
11239 .active_item(cx)
11240 .expect("should have an active item after navigating into the 3rd buffer");
11241 let third_item_id = active_item.item_id();
11242 assert_ne!(
11243 third_item_id, multibuffer_item_id,
11244 "Should navigate into the 3rd buffer and activate it"
11245 );
11246 assert_ne!(third_item_id, first_item_id);
11247 assert_ne!(third_item_id, second_item_id);
11248 assert!(
11249 active_item.is_singleton(cx),
11250 "New active item should be a singleton buffer"
11251 );
11252 assert_eq!(
11253 active_item
11254 .act_as::<Editor>(cx)
11255 .expect("should have navigated into an editor")
11256 .read(cx)
11257 .text(cx),
11258 sample_text_3
11259 );
11260
11261 workspace
11262 .go_back(workspace.active_pane().downgrade(), cx)
11263 .detach_and_log_err(cx);
11264 })
11265 .unwrap();
11266 cx.executor().run_until_parked();
11267 workspace
11268 .update(cx, |workspace, cx| {
11269 let active_item = workspace
11270 .active_item(cx)
11271 .expect("should have an active item after navigating back from the 3rd buffer");
11272 assert_eq!(
11273 active_item.item_id(),
11274 multibuffer_item_id,
11275 "Should navigate back from the 3rd buffer to the multi buffer"
11276 );
11277 assert!(!active_item.is_singleton(cx));
11278 })
11279 .unwrap();
11280}
11281
11282#[gpui::test]
11283async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11284 init_test(cx, |_| {});
11285
11286 let mut cx = EditorTestContext::new(cx).await;
11287
11288 let diff_base = r#"
11289 use some::mod;
11290
11291 const A: u32 = 42;
11292
11293 fn main() {
11294 println!("hello");
11295
11296 println!("world");
11297 }
11298 "#
11299 .unindent();
11300
11301 cx.set_state(
11302 &r#"
11303 use some::modified;
11304
11305 ˇ
11306 fn main() {
11307 println!("hello there");
11308
11309 println!("around the");
11310 println!("world");
11311 }
11312 "#
11313 .unindent(),
11314 );
11315
11316 cx.set_diff_base(Some(&diff_base));
11317 executor.run_until_parked();
11318
11319 cx.update_editor(|editor, cx| {
11320 editor.go_to_next_hunk(&GoToHunk, cx);
11321 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11322 });
11323 executor.run_until_parked();
11324 cx.assert_diff_hunks(
11325 r#"
11326 use some::modified;
11327
11328
11329 fn main() {
11330 - println!("hello");
11331 + println!("hello there");
11332
11333 println!("around the");
11334 println!("world");
11335 }
11336 "#
11337 .unindent(),
11338 );
11339
11340 cx.update_editor(|editor, cx| {
11341 for _ in 0..3 {
11342 editor.go_to_next_hunk(&GoToHunk, cx);
11343 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11344 }
11345 });
11346 executor.run_until_parked();
11347 cx.assert_editor_state(
11348 &r#"
11349 use some::modified;
11350
11351 ˇ
11352 fn main() {
11353 println!("hello there");
11354
11355 println!("around the");
11356 println!("world");
11357 }
11358 "#
11359 .unindent(),
11360 );
11361
11362 cx.assert_diff_hunks(
11363 r#"
11364 - use some::mod;
11365 + use some::modified;
11366
11367 - const A: u32 = 42;
11368
11369 fn main() {
11370 - println!("hello");
11371 + println!("hello there");
11372
11373 + println!("around the");
11374 println!("world");
11375 }
11376 "#
11377 .unindent(),
11378 );
11379
11380 cx.update_editor(|editor, cx| {
11381 editor.cancel(&Cancel, cx);
11382 });
11383
11384 cx.assert_diff_hunks(
11385 r#"
11386 use some::modified;
11387
11388
11389 fn main() {
11390 println!("hello there");
11391
11392 println!("around the");
11393 println!("world");
11394 }
11395 "#
11396 .unindent(),
11397 );
11398}
11399
11400#[gpui::test]
11401async fn test_diff_base_change_with_expanded_diff_hunks(
11402 executor: BackgroundExecutor,
11403 cx: &mut gpui::TestAppContext,
11404) {
11405 init_test(cx, |_| {});
11406
11407 let mut cx = EditorTestContext::new(cx).await;
11408
11409 let diff_base = r#"
11410 use some::mod1;
11411 use some::mod2;
11412
11413 const A: u32 = 42;
11414 const B: u32 = 42;
11415 const C: u32 = 42;
11416
11417 fn main() {
11418 println!("hello");
11419
11420 println!("world");
11421 }
11422 "#
11423 .unindent();
11424
11425 cx.set_state(
11426 &r#"
11427 use some::mod2;
11428
11429 const A: u32 = 42;
11430 const C: u32 = 42;
11431
11432 fn main(ˇ) {
11433 //println!("hello");
11434
11435 println!("world");
11436 //
11437 //
11438 }
11439 "#
11440 .unindent(),
11441 );
11442
11443 cx.set_diff_base(Some(&diff_base));
11444 executor.run_until_parked();
11445
11446 cx.update_editor(|editor, cx| {
11447 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11448 });
11449 executor.run_until_parked();
11450 cx.assert_diff_hunks(
11451 r#"
11452 - use some::mod1;
11453 use some::mod2;
11454
11455 const A: u32 = 42;
11456 - const B: u32 = 42;
11457 const C: u32 = 42;
11458
11459 fn main() {
11460 - println!("hello");
11461 + //println!("hello");
11462
11463 println!("world");
11464 + //
11465 + //
11466 }
11467 "#
11468 .unindent(),
11469 );
11470
11471 cx.set_diff_base(Some("new diff base!"));
11472 executor.run_until_parked();
11473 cx.assert_diff_hunks(
11474 r#"
11475 use some::mod2;
11476
11477 const A: u32 = 42;
11478 const C: u32 = 42;
11479
11480 fn main() {
11481 //println!("hello");
11482
11483 println!("world");
11484 //
11485 //
11486 }
11487 "#
11488 .unindent(),
11489 );
11490
11491 cx.update_editor(|editor, cx| {
11492 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11493 });
11494 executor.run_until_parked();
11495 cx.assert_diff_hunks(
11496 r#"
11497 - new diff base!
11498 + use some::mod2;
11499 +
11500 + const A: u32 = 42;
11501 + const C: u32 = 42;
11502 +
11503 + fn main() {
11504 + //println!("hello");
11505 +
11506 + println!("world");
11507 + //
11508 + //
11509 + }
11510 "#
11511 .unindent(),
11512 );
11513}
11514
11515#[gpui::test]
11516async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11517 init_test(cx, |_| {});
11518
11519 let mut cx = EditorTestContext::new(cx).await;
11520
11521 let diff_base = r#"
11522 use some::mod1;
11523 use some::mod2;
11524
11525 const A: u32 = 42;
11526 const B: u32 = 42;
11527 const C: u32 = 42;
11528
11529 fn main() {
11530 println!("hello");
11531
11532 println!("world");
11533 }
11534
11535 fn another() {
11536 println!("another");
11537 }
11538
11539 fn another2() {
11540 println!("another2");
11541 }
11542 "#
11543 .unindent();
11544
11545 cx.set_state(
11546 &r#"
11547 «use some::mod2;
11548
11549 const A: u32 = 42;
11550 const C: u32 = 42;
11551
11552 fn main() {
11553 //println!("hello");
11554
11555 println!("world");
11556 //
11557 //ˇ»
11558 }
11559
11560 fn another() {
11561 println!("another");
11562 println!("another");
11563 }
11564
11565 println!("another2");
11566 }
11567 "#
11568 .unindent(),
11569 );
11570
11571 cx.set_diff_base(Some(&diff_base));
11572 executor.run_until_parked();
11573
11574 cx.update_editor(|editor, cx| {
11575 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11576 });
11577 executor.run_until_parked();
11578
11579 cx.assert_diff_hunks(
11580 r#"
11581 - use some::mod1;
11582 use some::mod2;
11583
11584 const A: u32 = 42;
11585 - const B: u32 = 42;
11586 const C: u32 = 42;
11587
11588 fn main() {
11589 - println!("hello");
11590 + //println!("hello");
11591
11592 println!("world");
11593 + //
11594 + //
11595 }
11596
11597 fn another() {
11598 println!("another");
11599 + println!("another");
11600 }
11601
11602 - fn another2() {
11603 println!("another2");
11604 }
11605 "#
11606 .unindent(),
11607 );
11608
11609 // Fold across some of the diff hunks. They should no longer appear expanded.
11610 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11611 cx.executor().run_until_parked();
11612
11613 // Hunks are not shown if their position is within a fold
11614 cx.assert_diff_hunks(
11615 r#"
11616 use some::mod2;
11617
11618 const A: u32 = 42;
11619 const C: u32 = 42;
11620
11621 fn main() {
11622 //println!("hello");
11623
11624 println!("world");
11625 //
11626 //
11627 }
11628
11629 fn another() {
11630 println!("another");
11631 + println!("another");
11632 }
11633
11634 - fn another2() {
11635 println!("another2");
11636 }
11637 "#
11638 .unindent(),
11639 );
11640
11641 cx.update_editor(|editor, cx| {
11642 editor.select_all(&SelectAll, cx);
11643 editor.unfold_lines(&UnfoldLines, cx);
11644 });
11645 cx.executor().run_until_parked();
11646
11647 // The deletions reappear when unfolding.
11648 cx.assert_diff_hunks(
11649 r#"
11650 - use some::mod1;
11651 use some::mod2;
11652
11653 const A: u32 = 42;
11654 - const B: u32 = 42;
11655 const C: u32 = 42;
11656
11657 fn main() {
11658 - println!("hello");
11659 + //println!("hello");
11660
11661 println!("world");
11662 + //
11663 + //
11664 }
11665
11666 fn another() {
11667 println!("another");
11668 + println!("another");
11669 }
11670
11671 - fn another2() {
11672 println!("another2");
11673 }
11674 "#
11675 .unindent(),
11676 );
11677}
11678
11679#[gpui::test]
11680async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11681 init_test(cx, |_| {});
11682
11683 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11684 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11685 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11686 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11687 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11688 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11689
11690 let buffer_1 = cx.new_model(|cx| {
11691 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11692 buffer.set_diff_base(Some(file_1_old.into()), cx);
11693 buffer
11694 });
11695 let buffer_2 = cx.new_model(|cx| {
11696 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11697 buffer.set_diff_base(Some(file_2_old.into()), cx);
11698 buffer
11699 });
11700 let buffer_3 = cx.new_model(|cx| {
11701 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11702 buffer.set_diff_base(Some(file_3_old.into()), cx);
11703 buffer
11704 });
11705
11706 let multi_buffer = cx.new_model(|cx| {
11707 let mut multibuffer = MultiBuffer::new(ReadWrite);
11708 multibuffer.push_excerpts(
11709 buffer_1.clone(),
11710 [
11711 ExcerptRange {
11712 context: Point::new(0, 0)..Point::new(3, 0),
11713 primary: None,
11714 },
11715 ExcerptRange {
11716 context: Point::new(5, 0)..Point::new(7, 0),
11717 primary: None,
11718 },
11719 ExcerptRange {
11720 context: Point::new(9, 0)..Point::new(10, 3),
11721 primary: None,
11722 },
11723 ],
11724 cx,
11725 );
11726 multibuffer.push_excerpts(
11727 buffer_2.clone(),
11728 [
11729 ExcerptRange {
11730 context: Point::new(0, 0)..Point::new(3, 0),
11731 primary: None,
11732 },
11733 ExcerptRange {
11734 context: Point::new(5, 0)..Point::new(7, 0),
11735 primary: None,
11736 },
11737 ExcerptRange {
11738 context: Point::new(9, 0)..Point::new(10, 3),
11739 primary: None,
11740 },
11741 ],
11742 cx,
11743 );
11744 multibuffer.push_excerpts(
11745 buffer_3.clone(),
11746 [
11747 ExcerptRange {
11748 context: Point::new(0, 0)..Point::new(3, 0),
11749 primary: None,
11750 },
11751 ExcerptRange {
11752 context: Point::new(5, 0)..Point::new(7, 0),
11753 primary: None,
11754 },
11755 ExcerptRange {
11756 context: Point::new(9, 0)..Point::new(10, 3),
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.assert_editor_state(
11770 &"
11771 ˇaaa
11772 ccc
11773 ddd
11774
11775 ggg
11776 hhh
11777
11778
11779 lll
11780 mmm
11781 NNN
11782
11783 qqq
11784 rrr
11785
11786 uuu
11787 111
11788 222
11789 333
11790
11791 666
11792 777
11793
11794 000
11795 !!!"
11796 .unindent(),
11797 );
11798
11799 cx.update_editor(|editor, cx| {
11800 editor.select_all(&SelectAll, cx);
11801 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11802 });
11803 cx.executor().run_until_parked();
11804
11805 cx.assert_diff_hunks(
11806 "
11807 aaa
11808 - bbb
11809 ccc
11810 ddd
11811
11812 ggg
11813 hhh
11814
11815
11816 lll
11817 mmm
11818 - nnn
11819 + NNN
11820
11821 qqq
11822 rrr
11823
11824 uuu
11825 111
11826 222
11827 333
11828
11829 + 666
11830 777
11831
11832 000
11833 !!!"
11834 .unindent(),
11835 );
11836}
11837
11838#[gpui::test]
11839async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
11840 init_test(cx, |_| {});
11841
11842 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
11843 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
11844
11845 let buffer = cx.new_model(|cx| {
11846 let mut buffer = Buffer::local(text.to_string(), cx);
11847 buffer.set_diff_base(Some(base.into()), cx);
11848 buffer
11849 });
11850
11851 let multi_buffer = cx.new_model(|cx| {
11852 let mut multibuffer = MultiBuffer::new(ReadWrite);
11853 multibuffer.push_excerpts(
11854 buffer.clone(),
11855 [
11856 ExcerptRange {
11857 context: Point::new(0, 0)..Point::new(2, 0),
11858 primary: None,
11859 },
11860 ExcerptRange {
11861 context: Point::new(5, 0)..Point::new(7, 0),
11862 primary: None,
11863 },
11864 ],
11865 cx,
11866 );
11867 multibuffer
11868 });
11869
11870 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11871 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11872 cx.run_until_parked();
11873
11874 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
11875 cx.executor().run_until_parked();
11876
11877 cx.assert_diff_hunks(
11878 "
11879 aaa
11880 - bbb
11881 + BBB
11882
11883 - ddd
11884 - eee
11885 + EEE
11886 fff
11887 "
11888 .unindent(),
11889 );
11890}
11891
11892#[gpui::test]
11893async fn test_edits_around_expanded_insertion_hunks(
11894 executor: BackgroundExecutor,
11895 cx: &mut gpui::TestAppContext,
11896) {
11897 init_test(cx, |_| {});
11898
11899 let mut cx = EditorTestContext::new(cx).await;
11900
11901 let diff_base = r#"
11902 use some::mod1;
11903 use some::mod2;
11904
11905 const A: u32 = 42;
11906
11907 fn main() {
11908 println!("hello");
11909
11910 println!("world");
11911 }
11912 "#
11913 .unindent();
11914 executor.run_until_parked();
11915 cx.set_state(
11916 &r#"
11917 use some::mod1;
11918 use some::mod2;
11919
11920 const A: u32 = 42;
11921 const B: u32 = 42;
11922 const C: u32 = 42;
11923 ˇ
11924
11925 fn main() {
11926 println!("hello");
11927
11928 println!("world");
11929 }
11930 "#
11931 .unindent(),
11932 );
11933
11934 cx.set_diff_base(Some(&diff_base));
11935 executor.run_until_parked();
11936
11937 cx.update_editor(|editor, cx| {
11938 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11939 });
11940 executor.run_until_parked();
11941
11942 cx.assert_diff_hunks(
11943 r#"
11944 use some::mod1;
11945 use some::mod2;
11946
11947 const A: u32 = 42;
11948 + const B: u32 = 42;
11949 + const C: u32 = 42;
11950 +
11951
11952 fn main() {
11953 println!("hello");
11954
11955 println!("world");
11956 }
11957 "#
11958 .unindent(),
11959 );
11960
11961 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11962 executor.run_until_parked();
11963
11964 cx.assert_diff_hunks(
11965 r#"
11966 use some::mod1;
11967 use some::mod2;
11968
11969 const A: u32 = 42;
11970 + const B: u32 = 42;
11971 + const C: u32 = 42;
11972 + const D: u32 = 42;
11973 +
11974
11975 fn main() {
11976 println!("hello");
11977
11978 println!("world");
11979 }
11980 "#
11981 .unindent(),
11982 );
11983
11984 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11985 executor.run_until_parked();
11986
11987 cx.assert_diff_hunks(
11988 r#"
11989 use some::mod1;
11990 use some::mod2;
11991
11992 const A: u32 = 42;
11993 + const B: u32 = 42;
11994 + const C: u32 = 42;
11995 + const D: u32 = 42;
11996 + const E: u32 = 42;
11997 +
11998
11999 fn main() {
12000 println!("hello");
12001
12002 println!("world");
12003 }
12004 "#
12005 .unindent(),
12006 );
12007
12008 cx.update_editor(|editor, cx| {
12009 editor.delete_line(&DeleteLine, cx);
12010 });
12011 executor.run_until_parked();
12012
12013 cx.assert_diff_hunks(
12014 r#"
12015 use some::mod1;
12016 use some::mod2;
12017
12018 const A: u32 = 42;
12019 + const B: u32 = 42;
12020 + const C: u32 = 42;
12021 + const D: u32 = 42;
12022 + const E: u32 = 42;
12023
12024 fn main() {
12025 println!("hello");
12026
12027 println!("world");
12028 }
12029 "#
12030 .unindent(),
12031 );
12032
12033 cx.update_editor(|editor, cx| {
12034 editor.move_up(&MoveUp, cx);
12035 editor.delete_line(&DeleteLine, cx);
12036 editor.move_up(&MoveUp, cx);
12037 editor.delete_line(&DeleteLine, cx);
12038 editor.move_up(&MoveUp, cx);
12039 editor.delete_line(&DeleteLine, cx);
12040 });
12041 executor.run_until_parked();
12042 cx.assert_editor_state(
12043 &r#"
12044 use some::mod1;
12045 use some::mod2;
12046
12047 const A: u32 = 42;
12048 const B: u32 = 42;
12049 ˇ
12050 fn main() {
12051 println!("hello");
12052
12053 println!("world");
12054 }
12055 "#
12056 .unindent(),
12057 );
12058
12059 cx.assert_diff_hunks(
12060 r#"
12061 use some::mod1;
12062 use some::mod2;
12063
12064 const A: u32 = 42;
12065 + const B: u32 = 42;
12066
12067 fn main() {
12068 println!("hello");
12069
12070 println!("world");
12071 }
12072 "#
12073 .unindent(),
12074 );
12075
12076 cx.update_editor(|editor, cx| {
12077 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12078 editor.delete_line(&DeleteLine, cx);
12079 });
12080 executor.run_until_parked();
12081 cx.assert_diff_hunks(
12082 r#"
12083 use some::mod1;
12084 - use some::mod2;
12085 -
12086 - const A: u32 = 42;
12087
12088 fn main() {
12089 println!("hello");
12090
12091 println!("world");
12092 }
12093 "#
12094 .unindent(),
12095 );
12096}
12097
12098#[gpui::test]
12099async fn test_edits_around_expanded_deletion_hunks(
12100 executor: BackgroundExecutor,
12101 cx: &mut gpui::TestAppContext,
12102) {
12103 init_test(cx, |_| {});
12104
12105 let mut cx = EditorTestContext::new(cx).await;
12106
12107 let diff_base = r#"
12108 use some::mod1;
12109 use some::mod2;
12110
12111 const A: u32 = 42;
12112 const B: u32 = 42;
12113 const C: u32 = 42;
12114
12115
12116 fn main() {
12117 println!("hello");
12118
12119 println!("world");
12120 }
12121 "#
12122 .unindent();
12123 executor.run_until_parked();
12124 cx.set_state(
12125 &r#"
12126 use some::mod1;
12127 use some::mod2;
12128
12129 ˇconst B: u32 = 42;
12130 const C: u32 = 42;
12131
12132
12133 fn main() {
12134 println!("hello");
12135
12136 println!("world");
12137 }
12138 "#
12139 .unindent(),
12140 );
12141
12142 cx.set_diff_base(Some(&diff_base));
12143 executor.run_until_parked();
12144
12145 cx.update_editor(|editor, cx| {
12146 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12147 });
12148 executor.run_until_parked();
12149
12150 cx.assert_diff_hunks(
12151 r#"
12152 use some::mod1;
12153 use some::mod2;
12154
12155 - const A: u32 = 42;
12156 const B: u32 = 42;
12157 const C: u32 = 42;
12158
12159
12160 fn main() {
12161 println!("hello");
12162
12163 println!("world");
12164 }
12165 "#
12166 .unindent(),
12167 );
12168
12169 cx.update_editor(|editor, cx| {
12170 editor.delete_line(&DeleteLine, cx);
12171 });
12172 executor.run_until_parked();
12173 cx.assert_editor_state(
12174 &r#"
12175 use some::mod1;
12176 use some::mod2;
12177
12178 ˇconst C: u32 = 42;
12179
12180
12181 fn main() {
12182 println!("hello");
12183
12184 println!("world");
12185 }
12186 "#
12187 .unindent(),
12188 );
12189 cx.assert_diff_hunks(
12190 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
12198
12199 fn main() {
12200 println!("hello");
12201
12202 println!("world");
12203 }
12204 "#
12205 .unindent(),
12206 );
12207
12208 cx.update_editor(|editor, cx| {
12209 editor.delete_line(&DeleteLine, cx);
12210 });
12211 executor.run_until_parked();
12212 cx.assert_editor_state(
12213 &r#"
12214 use some::mod1;
12215 use some::mod2;
12216
12217 ˇ
12218
12219 fn main() {
12220 println!("hello");
12221
12222 println!("world");
12223 }
12224 "#
12225 .unindent(),
12226 );
12227 cx.assert_diff_hunks(
12228 r#"
12229 use some::mod1;
12230 use some::mod2;
12231
12232 - const A: u32 = 42;
12233 - const B: u32 = 42;
12234 - const C: u32 = 42;
12235
12236
12237 fn main() {
12238 println!("hello");
12239
12240 println!("world");
12241 }
12242 "#
12243 .unindent(),
12244 );
12245
12246 cx.update_editor(|editor, cx| {
12247 editor.handle_input("replacement", cx);
12248 });
12249 executor.run_until_parked();
12250 cx.assert_editor_state(
12251 &r#"
12252 use some::mod1;
12253 use some::mod2;
12254
12255 replacementˇ
12256
12257 fn main() {
12258 println!("hello");
12259
12260 println!("world");
12261 }
12262 "#
12263 .unindent(),
12264 );
12265 cx.assert_diff_hunks(
12266 r#"
12267 use some::mod1;
12268 use some::mod2;
12269
12270 - const A: u32 = 42;
12271 - const B: u32 = 42;
12272 - const C: u32 = 42;
12273 -
12274 + replacement
12275
12276 fn main() {
12277 println!("hello");
12278
12279 println!("world");
12280 }
12281 "#
12282 .unindent(),
12283 );
12284}
12285
12286#[gpui::test]
12287async fn test_edit_after_expanded_modification_hunk(
12288 executor: BackgroundExecutor,
12289 cx: &mut gpui::TestAppContext,
12290) {
12291 init_test(cx, |_| {});
12292
12293 let mut cx = EditorTestContext::new(cx).await;
12294
12295 let diff_base = r#"
12296 use some::mod1;
12297 use some::mod2;
12298
12299 const A: u32 = 42;
12300 const B: u32 = 42;
12301 const C: u32 = 42;
12302 const D: u32 = 42;
12303
12304
12305 fn main() {
12306 println!("hello");
12307
12308 println!("world");
12309 }"#
12310 .unindent();
12311
12312 cx.set_state(
12313 &r#"
12314 use some::mod1;
12315 use some::mod2;
12316
12317 const A: u32 = 42;
12318 const B: u32 = 42;
12319 const C: u32 = 43ˇ
12320 const D: u32 = 42;
12321
12322
12323 fn main() {
12324 println!("hello");
12325
12326 println!("world");
12327 }"#
12328 .unindent(),
12329 );
12330
12331 cx.set_diff_base(Some(&diff_base));
12332 executor.run_until_parked();
12333 cx.update_editor(|editor, cx| {
12334 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12335 });
12336 executor.run_until_parked();
12337
12338 cx.assert_diff_hunks(
12339 r#"
12340 use some::mod1;
12341 use some::mod2;
12342
12343 const A: u32 = 42;
12344 const B: u32 = 42;
12345 - const C: u32 = 42;
12346 + const C: u32 = 43
12347 const D: u32 = 42;
12348
12349
12350 fn main() {
12351 println!("hello");
12352
12353 println!("world");
12354 }"#
12355 .unindent(),
12356 );
12357
12358 cx.update_editor(|editor, cx| {
12359 editor.handle_input("\nnew_line\n", cx);
12360 });
12361 executor.run_until_parked();
12362
12363 cx.assert_diff_hunks(
12364 r#"
12365 use some::mod1;
12366 use some::mod2;
12367
12368 const A: u32 = 42;
12369 const B: u32 = 42;
12370 - const C: u32 = 42;
12371 + const C: u32 = 43
12372 + new_line
12373 +
12374 const D: u32 = 42;
12375
12376
12377 fn main() {
12378 println!("hello");
12379
12380 println!("world");
12381 }"#
12382 .unindent(),
12383 );
12384}
12385
12386async fn setup_indent_guides_editor(
12387 text: &str,
12388 cx: &mut gpui::TestAppContext,
12389) -> (BufferId, EditorTestContext) {
12390 init_test(cx, |_| {});
12391
12392 let mut cx = EditorTestContext::new(cx).await;
12393
12394 let buffer_id = cx.update_editor(|editor, cx| {
12395 editor.set_text(text, cx);
12396 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12397
12398 buffer_ids[0]
12399 });
12400
12401 (buffer_id, cx)
12402}
12403
12404fn assert_indent_guides(
12405 range: Range<u32>,
12406 expected: Vec<IndentGuide>,
12407 active_indices: Option<Vec<usize>>,
12408 cx: &mut EditorTestContext,
12409) {
12410 let indent_guides = cx.update_editor(|editor, cx| {
12411 let snapshot = editor.snapshot(cx).display_snapshot;
12412 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12413 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12414 true,
12415 &snapshot,
12416 cx,
12417 );
12418
12419 indent_guides.sort_by(|a, b| {
12420 a.depth.cmp(&b.depth).then(
12421 a.start_row
12422 .cmp(&b.start_row)
12423 .then(a.end_row.cmp(&b.end_row)),
12424 )
12425 });
12426 indent_guides
12427 });
12428
12429 if let Some(expected) = active_indices {
12430 let active_indices = cx.update_editor(|editor, cx| {
12431 let snapshot = editor.snapshot(cx).display_snapshot;
12432 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12433 });
12434
12435 assert_eq!(
12436 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12437 expected,
12438 "Active indent guide indices do not match"
12439 );
12440 }
12441
12442 let expected: Vec<_> = expected
12443 .into_iter()
12444 .map(|guide| MultiBufferIndentGuide {
12445 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12446 buffer: guide,
12447 })
12448 .collect();
12449
12450 assert_eq!(indent_guides, expected, "Indent guides do not match");
12451}
12452
12453fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12454 IndentGuide {
12455 buffer_id,
12456 start_row,
12457 end_row,
12458 depth,
12459 tab_size: 4,
12460 settings: IndentGuideSettings {
12461 enabled: true,
12462 line_width: 1,
12463 active_line_width: 1,
12464 ..Default::default()
12465 },
12466 }
12467}
12468
12469#[gpui::test]
12470async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12471 let (buffer_id, mut cx) = setup_indent_guides_editor(
12472 &"
12473 fn main() {
12474 let a = 1;
12475 }"
12476 .unindent(),
12477 cx,
12478 )
12479 .await;
12480
12481 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12482}
12483
12484#[gpui::test]
12485async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12486 let (buffer_id, mut cx) = setup_indent_guides_editor(
12487 &"
12488 fn main() {
12489 let a = 1;
12490 let b = 2;
12491 }"
12492 .unindent(),
12493 cx,
12494 )
12495 .await;
12496
12497 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12498}
12499
12500#[gpui::test]
12501async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12502 let (buffer_id, mut cx) = setup_indent_guides_editor(
12503 &"
12504 fn main() {
12505 let a = 1;
12506 if a == 3 {
12507 let b = 2;
12508 } else {
12509 let c = 3;
12510 }
12511 }"
12512 .unindent(),
12513 cx,
12514 )
12515 .await;
12516
12517 assert_indent_guides(
12518 0..8,
12519 vec![
12520 indent_guide(buffer_id, 1, 6, 0),
12521 indent_guide(buffer_id, 3, 3, 1),
12522 indent_guide(buffer_id, 5, 5, 1),
12523 ],
12524 None,
12525 &mut cx,
12526 );
12527}
12528
12529#[gpui::test]
12530async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12531 let (buffer_id, mut cx) = setup_indent_guides_editor(
12532 &"
12533 fn main() {
12534 let a = 1;
12535 let b = 2;
12536 let c = 3;
12537 }"
12538 .unindent(),
12539 cx,
12540 )
12541 .await;
12542
12543 assert_indent_guides(
12544 0..5,
12545 vec![
12546 indent_guide(buffer_id, 1, 3, 0),
12547 indent_guide(buffer_id, 2, 2, 1),
12548 ],
12549 None,
12550 &mut cx,
12551 );
12552}
12553
12554#[gpui::test]
12555async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12556 let (buffer_id, mut cx) = setup_indent_guides_editor(
12557 &"
12558 fn main() {
12559 let a = 1;
12560
12561 let c = 3;
12562 }"
12563 .unindent(),
12564 cx,
12565 )
12566 .await;
12567
12568 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12569}
12570
12571#[gpui::test]
12572async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12573 let (buffer_id, mut cx) = setup_indent_guides_editor(
12574 &"
12575 fn main() {
12576 let a = 1;
12577
12578 let c = 3;
12579
12580 if a == 3 {
12581 let b = 2;
12582 } else {
12583 let c = 3;
12584 }
12585 }"
12586 .unindent(),
12587 cx,
12588 )
12589 .await;
12590
12591 assert_indent_guides(
12592 0..11,
12593 vec![
12594 indent_guide(buffer_id, 1, 9, 0),
12595 indent_guide(buffer_id, 6, 6, 1),
12596 indent_guide(buffer_id, 8, 8, 1),
12597 ],
12598 None,
12599 &mut cx,
12600 );
12601}
12602
12603#[gpui::test]
12604async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12605 let (buffer_id, mut cx) = setup_indent_guides_editor(
12606 &"
12607 fn main() {
12608 let a = 1;
12609
12610 let c = 3;
12611
12612 if a == 3 {
12613 let b = 2;
12614 } else {
12615 let c = 3;
12616 }
12617 }"
12618 .unindent(),
12619 cx,
12620 )
12621 .await;
12622
12623 assert_indent_guides(
12624 1..11,
12625 vec![
12626 indent_guide(buffer_id, 1, 9, 0),
12627 indent_guide(buffer_id, 6, 6, 1),
12628 indent_guide(buffer_id, 8, 8, 1),
12629 ],
12630 None,
12631 &mut cx,
12632 );
12633}
12634
12635#[gpui::test]
12636async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12637 let (buffer_id, mut cx) = setup_indent_guides_editor(
12638 &"
12639 fn main() {
12640 let a = 1;
12641
12642 let c = 3;
12643
12644 if a == 3 {
12645 let b = 2;
12646 } else {
12647 let c = 3;
12648 }
12649 }"
12650 .unindent(),
12651 cx,
12652 )
12653 .await;
12654
12655 assert_indent_guides(
12656 1..10,
12657 vec![
12658 indent_guide(buffer_id, 1, 9, 0),
12659 indent_guide(buffer_id, 6, 6, 1),
12660 indent_guide(buffer_id, 8, 8, 1),
12661 ],
12662 None,
12663 &mut cx,
12664 );
12665}
12666
12667#[gpui::test]
12668async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12669 let (buffer_id, mut cx) = setup_indent_guides_editor(
12670 &"
12671 block1
12672 block2
12673 block3
12674 block4
12675 block2
12676 block1
12677 block1"
12678 .unindent(),
12679 cx,
12680 )
12681 .await;
12682
12683 assert_indent_guides(
12684 1..10,
12685 vec![
12686 indent_guide(buffer_id, 1, 4, 0),
12687 indent_guide(buffer_id, 2, 3, 1),
12688 indent_guide(buffer_id, 3, 3, 2),
12689 ],
12690 None,
12691 &mut cx,
12692 );
12693}
12694
12695#[gpui::test]
12696async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12697 let (buffer_id, mut cx) = setup_indent_guides_editor(
12698 &"
12699 block1
12700 block2
12701 block3
12702
12703 block1
12704 block1"
12705 .unindent(),
12706 cx,
12707 )
12708 .await;
12709
12710 assert_indent_guides(
12711 0..6,
12712 vec![
12713 indent_guide(buffer_id, 1, 2, 0),
12714 indent_guide(buffer_id, 2, 2, 1),
12715 ],
12716 None,
12717 &mut cx,
12718 );
12719}
12720
12721#[gpui::test]
12722async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12723 let (buffer_id, mut cx) = setup_indent_guides_editor(
12724 &"
12725 block1
12726
12727
12728
12729 block2
12730 "
12731 .unindent(),
12732 cx,
12733 )
12734 .await;
12735
12736 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12737}
12738
12739#[gpui::test]
12740async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12741 let (buffer_id, mut cx) = setup_indent_guides_editor(
12742 &"
12743 def a:
12744 \tb = 3
12745 \tif True:
12746 \t\tc = 4
12747 \t\td = 5
12748 \tprint(b)
12749 "
12750 .unindent(),
12751 cx,
12752 )
12753 .await;
12754
12755 assert_indent_guides(
12756 0..6,
12757 vec![
12758 indent_guide(buffer_id, 1, 6, 0),
12759 indent_guide(buffer_id, 3, 4, 1),
12760 ],
12761 None,
12762 &mut cx,
12763 );
12764}
12765
12766#[gpui::test]
12767async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12768 let (buffer_id, mut cx) = setup_indent_guides_editor(
12769 &"
12770 fn main() {
12771 let a = 1;
12772 }"
12773 .unindent(),
12774 cx,
12775 )
12776 .await;
12777
12778 cx.update_editor(|editor, cx| {
12779 editor.change_selections(None, cx, |s| {
12780 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12781 });
12782 });
12783
12784 assert_indent_guides(
12785 0..3,
12786 vec![indent_guide(buffer_id, 1, 1, 0)],
12787 Some(vec![0]),
12788 &mut cx,
12789 );
12790}
12791
12792#[gpui::test]
12793async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12794 let (buffer_id, mut cx) = setup_indent_guides_editor(
12795 &"
12796 fn main() {
12797 if 1 == 2 {
12798 let a = 1;
12799 }
12800 }"
12801 .unindent(),
12802 cx,
12803 )
12804 .await;
12805
12806 cx.update_editor(|editor, cx| {
12807 editor.change_selections(None, cx, |s| {
12808 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12809 });
12810 });
12811
12812 assert_indent_guides(
12813 0..4,
12814 vec![
12815 indent_guide(buffer_id, 1, 3, 0),
12816 indent_guide(buffer_id, 2, 2, 1),
12817 ],
12818 Some(vec![1]),
12819 &mut cx,
12820 );
12821
12822 cx.update_editor(|editor, cx| {
12823 editor.change_selections(None, cx, |s| {
12824 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12825 });
12826 });
12827
12828 assert_indent_guides(
12829 0..4,
12830 vec![
12831 indent_guide(buffer_id, 1, 3, 0),
12832 indent_guide(buffer_id, 2, 2, 1),
12833 ],
12834 Some(vec![1]),
12835 &mut cx,
12836 );
12837
12838 cx.update_editor(|editor, cx| {
12839 editor.change_selections(None, cx, |s| {
12840 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12841 });
12842 });
12843
12844 assert_indent_guides(
12845 0..4,
12846 vec![
12847 indent_guide(buffer_id, 1, 3, 0),
12848 indent_guide(buffer_id, 2, 2, 1),
12849 ],
12850 Some(vec![0]),
12851 &mut cx,
12852 );
12853}
12854
12855#[gpui::test]
12856async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12857 let (buffer_id, mut cx) = setup_indent_guides_editor(
12858 &"
12859 fn main() {
12860 let a = 1;
12861
12862 let b = 2;
12863 }"
12864 .unindent(),
12865 cx,
12866 )
12867 .await;
12868
12869 cx.update_editor(|editor, cx| {
12870 editor.change_selections(None, cx, |s| {
12871 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12872 });
12873 });
12874
12875 assert_indent_guides(
12876 0..5,
12877 vec![indent_guide(buffer_id, 1, 3, 0)],
12878 Some(vec![0]),
12879 &mut cx,
12880 );
12881}
12882
12883#[gpui::test]
12884async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12885 let (buffer_id, mut cx) = setup_indent_guides_editor(
12886 &"
12887 def m:
12888 a = 1
12889 pass"
12890 .unindent(),
12891 cx,
12892 )
12893 .await;
12894
12895 cx.update_editor(|editor, cx| {
12896 editor.change_selections(None, cx, |s| {
12897 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12898 });
12899 });
12900
12901 assert_indent_guides(
12902 0..3,
12903 vec![indent_guide(buffer_id, 1, 2, 0)],
12904 Some(vec![0]),
12905 &mut cx,
12906 );
12907}
12908
12909#[gpui::test]
12910fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12911 init_test(cx, |_| {});
12912
12913 let editor = cx.add_window(|cx| {
12914 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12915 build_editor(buffer, cx)
12916 });
12917
12918 let render_args = Arc::new(Mutex::new(None));
12919 let snapshot = editor
12920 .update(cx, |editor, cx| {
12921 let snapshot = editor.buffer().read(cx).snapshot(cx);
12922 let range =
12923 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12924
12925 struct RenderArgs {
12926 row: MultiBufferRow,
12927 folded: bool,
12928 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12929 }
12930
12931 let crease = Crease::new(
12932 range,
12933 FoldPlaceholder::test(),
12934 {
12935 let toggle_callback = render_args.clone();
12936 move |row, folded, callback, _cx| {
12937 *toggle_callback.lock() = Some(RenderArgs {
12938 row,
12939 folded,
12940 callback,
12941 });
12942 div()
12943 }
12944 },
12945 |_row, _folded, _cx| div(),
12946 );
12947
12948 editor.insert_creases(Some(crease), cx);
12949 let snapshot = editor.snapshot(cx);
12950 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12951 snapshot
12952 })
12953 .unwrap();
12954
12955 let render_args = render_args.lock().take().unwrap();
12956 assert_eq!(render_args.row, MultiBufferRow(1));
12957 assert!(!render_args.folded);
12958 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12959
12960 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12961 .unwrap();
12962 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12963 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12964
12965 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12966 .unwrap();
12967 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12968 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12969}
12970
12971#[gpui::test]
12972async fn test_input_text(cx: &mut gpui::TestAppContext) {
12973 init_test(cx, |_| {});
12974 let mut cx = EditorTestContext::new(cx).await;
12975
12976 cx.set_state(
12977 &r#"ˇone
12978 two
12979
12980 three
12981 fourˇ
12982 five
12983
12984 siˇx"#
12985 .unindent(),
12986 );
12987
12988 cx.dispatch_action(HandleInput(String::new()));
12989 cx.assert_editor_state(
12990 &r#"ˇone
12991 two
12992
12993 three
12994 fourˇ
12995 five
12996
12997 siˇx"#
12998 .unindent(),
12999 );
13000
13001 cx.dispatch_action(HandleInput("AAAA".to_string()));
13002 cx.assert_editor_state(
13003 &r#"AAAAˇone
13004 two
13005
13006 three
13007 fourAAAAˇ
13008 five
13009
13010 siAAAAˇx"#
13011 .unindent(),
13012 );
13013}
13014
13015#[gpui::test]
13016async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13017 init_test(cx, |_| {});
13018
13019 let mut cx = EditorTestContext::new(cx).await;
13020 cx.set_state(
13021 r#"let foo = 1;
13022let foo = 2;
13023let foo = 3;
13024let fooˇ = 4;
13025let foo = 5;
13026let foo = 6;
13027let foo = 7;
13028let foo = 8;
13029let foo = 9;
13030let foo = 10;
13031let foo = 11;
13032let foo = 12;
13033let foo = 13;
13034let foo = 14;
13035let foo = 15;"#,
13036 );
13037
13038 cx.update_editor(|e, cx| {
13039 assert_eq!(
13040 e.next_scroll_position,
13041 NextScrollCursorCenterTopBottom::Center,
13042 "Default next scroll direction is center",
13043 );
13044
13045 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13046 assert_eq!(
13047 e.next_scroll_position,
13048 NextScrollCursorCenterTopBottom::Top,
13049 "After center, next scroll direction should be top",
13050 );
13051
13052 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13053 assert_eq!(
13054 e.next_scroll_position,
13055 NextScrollCursorCenterTopBottom::Bottom,
13056 "After top, next scroll direction should be bottom",
13057 );
13058
13059 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13060 assert_eq!(
13061 e.next_scroll_position,
13062 NextScrollCursorCenterTopBottom::Center,
13063 "After bottom, scrolling should start over",
13064 );
13065
13066 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13067 assert_eq!(
13068 e.next_scroll_position,
13069 NextScrollCursorCenterTopBottom::Top,
13070 "Scrolling continues if retriggered fast enough"
13071 );
13072 });
13073
13074 cx.executor()
13075 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13076 cx.executor().run_until_parked();
13077 cx.update_editor(|e, _| {
13078 assert_eq!(
13079 e.next_scroll_position,
13080 NextScrollCursorCenterTopBottom::Center,
13081 "If scrolling is not triggered fast enough, it should reset"
13082 );
13083 });
13084}
13085
13086#[gpui::test]
13087async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13088 init_test(cx, |_| {});
13089 let mut cx = EditorLspTestContext::new_rust(
13090 lsp::ServerCapabilities {
13091 definition_provider: Some(lsp::OneOf::Left(true)),
13092 references_provider: Some(lsp::OneOf::Left(true)),
13093 ..lsp::ServerCapabilities::default()
13094 },
13095 cx,
13096 )
13097 .await;
13098
13099 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13100 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13101 move |params, _| async move {
13102 if empty_go_to_definition {
13103 Ok(None)
13104 } else {
13105 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13106 uri: params.text_document_position_params.text_document.uri,
13107 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13108 })))
13109 }
13110 },
13111 );
13112 let references =
13113 cx.lsp
13114 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13115 Ok(Some(vec![lsp::Location {
13116 uri: params.text_document_position.text_document.uri,
13117 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13118 }]))
13119 });
13120 (go_to_definition, references)
13121 };
13122
13123 cx.set_state(
13124 &r#"fn one() {
13125 let mut a = ˇtwo();
13126 }
13127
13128 fn two() {}"#
13129 .unindent(),
13130 );
13131 set_up_lsp_handlers(false, &mut cx);
13132 let navigated = cx
13133 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13134 .await
13135 .expect("Failed to navigate to definition");
13136 assert_eq!(
13137 navigated,
13138 Navigated::Yes,
13139 "Should have navigated to definition from the GetDefinition response"
13140 );
13141 cx.assert_editor_state(
13142 &r#"fn one() {
13143 let mut a = two();
13144 }
13145
13146 fn «twoˇ»() {}"#
13147 .unindent(),
13148 );
13149
13150 let editors = cx.update_workspace(|workspace, cx| {
13151 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13152 });
13153 cx.update_editor(|_, test_editor_cx| {
13154 assert_eq!(
13155 editors.len(),
13156 1,
13157 "Initially, only one, test, editor should be open in the workspace"
13158 );
13159 assert_eq!(
13160 test_editor_cx.view(),
13161 editors.last().expect("Asserted len is 1")
13162 );
13163 });
13164
13165 set_up_lsp_handlers(true, &mut cx);
13166 let navigated = cx
13167 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13168 .await
13169 .expect("Failed to navigate to lookup references");
13170 assert_eq!(
13171 navigated,
13172 Navigated::Yes,
13173 "Should have navigated to references as a fallback after empty GoToDefinition response"
13174 );
13175 // We should not change the selections in the existing file,
13176 // if opening another milti buffer with the references
13177 cx.assert_editor_state(
13178 &r#"fn one() {
13179 let mut a = two();
13180 }
13181
13182 fn «twoˇ»() {}"#
13183 .unindent(),
13184 );
13185 let editors = cx.update_workspace(|workspace, cx| {
13186 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13187 });
13188 cx.update_editor(|_, test_editor_cx| {
13189 assert_eq!(
13190 editors.len(),
13191 2,
13192 "After falling back to references search, we open a new editor with the results"
13193 );
13194 let references_fallback_text = editors
13195 .into_iter()
13196 .find(|new_editor| new_editor != test_editor_cx.view())
13197 .expect("Should have one non-test editor now")
13198 .read(test_editor_cx)
13199 .text(test_editor_cx);
13200 assert_eq!(
13201 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13202 "Should use the range from the references response and not the GoToDefinition one"
13203 );
13204 });
13205}
13206
13207fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13208 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13209 point..point
13210}
13211
13212fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13213 let (text, ranges) = marked_text_ranges(marked_text, true);
13214 assert_eq!(view.text(cx), text);
13215 assert_eq!(
13216 view.selections.ranges(cx),
13217 ranges,
13218 "Assert selections are {}",
13219 marked_text
13220 );
13221}
13222
13223pub fn handle_signature_help_request(
13224 cx: &mut EditorLspTestContext,
13225 mocked_response: lsp::SignatureHelp,
13226) -> impl Future<Output = ()> {
13227 let mut request =
13228 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13229 let mocked_response = mocked_response.clone();
13230 async move { Ok(Some(mocked_response)) }
13231 });
13232
13233 async move {
13234 request.next().await;
13235 }
13236}
13237
13238/// Handle completion request passing a marked string specifying where the completion
13239/// should be triggered from using '|' character, what range should be replaced, and what completions
13240/// should be returned using '<' and '>' to delimit the range
13241pub fn handle_completion_request(
13242 cx: &mut EditorLspTestContext,
13243 marked_string: &str,
13244 completions: Vec<&'static str>,
13245 counter: Arc<AtomicUsize>,
13246) -> impl Future<Output = ()> {
13247 let complete_from_marker: TextRangeMarker = '|'.into();
13248 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13249 let (_, mut marked_ranges) = marked_text_ranges_by(
13250 marked_string,
13251 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13252 );
13253
13254 let complete_from_position =
13255 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13256 let replace_range =
13257 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13258
13259 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13260 let completions = completions.clone();
13261 counter.fetch_add(1, atomic::Ordering::Release);
13262 async move {
13263 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13264 assert_eq!(
13265 params.text_document_position.position,
13266 complete_from_position
13267 );
13268 Ok(Some(lsp::CompletionResponse::Array(
13269 completions
13270 .iter()
13271 .map(|completion_text| lsp::CompletionItem {
13272 label: completion_text.to_string(),
13273 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13274 range: replace_range,
13275 new_text: completion_text.to_string(),
13276 })),
13277 ..Default::default()
13278 })
13279 .collect(),
13280 )))
13281 }
13282 });
13283
13284 async move {
13285 request.next().await;
13286 }
13287}
13288
13289fn handle_resolve_completion_request(
13290 cx: &mut EditorLspTestContext,
13291 edits: Option<Vec<(&'static str, &'static str)>>,
13292) -> impl Future<Output = ()> {
13293 let edits = edits.map(|edits| {
13294 edits
13295 .iter()
13296 .map(|(marked_string, new_text)| {
13297 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13298 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13299 lsp::TextEdit::new(replace_range, new_text.to_string())
13300 })
13301 .collect::<Vec<_>>()
13302 });
13303
13304 let mut request =
13305 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13306 let edits = edits.clone();
13307 async move {
13308 Ok(lsp::CompletionItem {
13309 additional_text_edits: edits,
13310 ..Default::default()
13311 })
13312 }
13313 });
13314
13315 async move {
13316 request.next().await;
13317 }
13318}
13319
13320pub(crate) fn update_test_language_settings(
13321 cx: &mut TestAppContext,
13322 f: impl Fn(&mut AllLanguageSettingsContent),
13323) {
13324 cx.update(|cx| {
13325 SettingsStore::update_global(cx, |store, cx| {
13326 store.update_user_settings::<AllLanguageSettings>(cx, f);
13327 });
13328 });
13329}
13330
13331pub(crate) fn update_test_project_settings(
13332 cx: &mut TestAppContext,
13333 f: impl Fn(&mut ProjectSettings),
13334) {
13335 cx.update(|cx| {
13336 SettingsStore::update_global(cx, |store, cx| {
13337 store.update_user_settings::<ProjectSettings>(cx, f);
13338 });
13339 });
13340}
13341
13342pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13343 cx.update(|cx| {
13344 assets::Assets.load_test_fonts(cx);
13345 let store = SettingsStore::test(cx);
13346 cx.set_global(store);
13347 theme::init(theme::LoadThemes::JustBase, cx);
13348 release_channel::init(SemanticVersion::default(), cx);
13349 client::init_settings(cx);
13350 language::init(cx);
13351 Project::init_settings(cx);
13352 workspace::init_settings(cx);
13353 crate::init(cx);
13354 });
13355
13356 update_test_language_settings(cx, f);
13357}
13358
13359pub(crate) fn rust_lang() -> Arc<Language> {
13360 Arc::new(Language::new(
13361 LanguageConfig {
13362 name: "Rust".into(),
13363 matcher: LanguageMatcher {
13364 path_suffixes: vec!["rs".to_string()],
13365 ..Default::default()
13366 },
13367 ..Default::default()
13368 },
13369 Some(tree_sitter_rust::LANGUAGE.into()),
13370 ))
13371}
13372
13373#[track_caller]
13374fn assert_hunk_revert(
13375 not_reverted_text_with_selections: &str,
13376 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13377 expected_reverted_text_with_selections: &str,
13378 base_text: &str,
13379 cx: &mut EditorLspTestContext,
13380) {
13381 cx.set_state(not_reverted_text_with_selections);
13382 cx.update_editor(|editor, cx| {
13383 editor
13384 .buffer()
13385 .read(cx)
13386 .as_singleton()
13387 .unwrap()
13388 .update(cx, |buffer, cx| {
13389 buffer.set_diff_base(Some(base_text.into()), cx);
13390 });
13391 });
13392 cx.executor().run_until_parked();
13393
13394 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13395 let snapshot = editor.buffer().read(cx).snapshot(cx);
13396 let reverted_hunk_statuses = snapshot
13397 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13398 .map(|hunk| hunk_status(&hunk))
13399 .collect::<Vec<_>>();
13400
13401 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13402 reverted_hunk_statuses
13403 });
13404 cx.executor().run_until_parked();
13405 cx.assert_editor_state(expected_reverted_text_with_selections);
13406 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13407}