1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic;
35use std::sync::atomic::AtomicUsize;
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
173 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
174 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
175 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
176
177 _ = editor.update(cx, |editor, cx| {
178 editor.start_transaction_at(now, cx);
179 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
180
181 editor.insert("cd", cx);
182 editor.end_transaction_at(now, cx);
183 assert_eq!(editor.text(cx), "12cd56");
184 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
185
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
188 editor.insert("e", cx);
189 editor.end_transaction_at(now, cx);
190 assert_eq!(editor.text(cx), "12cde6");
191 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
192
193 now += group_interval + Duration::from_millis(1);
194 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
195
196 // Simulate an edit in another editor
197 buffer.update(cx, |buffer, cx| {
198 buffer.start_transaction_at(now, cx);
199 buffer.edit([(0..1, "a")], None, cx);
200 buffer.edit([(1..1, "b")], None, cx);
201 buffer.end_transaction_at(now, cx);
202 });
203
204 assert_eq!(editor.text(cx), "ab2cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
206
207 // Last transaction happened past the group interval in a different editor.
208 // Undo it individually and don't restore selections.
209 editor.undo(&Undo, cx);
210 assert_eq!(editor.text(cx), "12cde6");
211 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
212
213 // First two transactions happened within the group interval in this editor.
214 // Undo them together and restore selections.
215 editor.undo(&Undo, cx);
216 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
217 assert_eq!(editor.text(cx), "123456");
218 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
219
220 // Redo the first two transactions together.
221 editor.redo(&Redo, cx);
222 assert_eq!(editor.text(cx), "12cde6");
223 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
224
225 // Redo the last transaction on its own.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "ab2cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
229
230 // Test empty transactions.
231 editor.start_transaction_at(now, cx);
232 editor.end_transaction_at(now, cx);
233 editor.undo(&Undo, cx);
234 assert_eq!(editor.text(cx), "12cde6");
235 });
236}
237
238#[gpui::test]
239fn test_ime_composition(cx: &mut TestAppContext) {
240 init_test(cx, |_| {});
241
242 let buffer = cx.new_model(|cx| {
243 let mut buffer = language::Buffer::local("abcde", cx);
244 // Ensure automatic grouping doesn't occur.
245 buffer.set_group_interval(Duration::ZERO);
246 buffer
247 });
248
249 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
250 cx.add_window(|cx| {
251 let mut editor = build_editor(buffer.clone(), cx);
252
253 // Start a new IME composition.
254 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
255 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
257 assert_eq!(editor.text(cx), "äbcde");
258 assert_eq!(
259 editor.marked_text_ranges(cx),
260 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
261 );
262
263 // Finalize IME composition.
264 editor.replace_text_in_range(None, "ā", cx);
265 assert_eq!(editor.text(cx), "ābcde");
266 assert_eq!(editor.marked_text_ranges(cx), None);
267
268 // IME composition edits are grouped and are undone/redone at once.
269 editor.undo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "abcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272 editor.redo(&Default::default(), cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // Start a new IME composition.
277 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
281 );
282
283 // Undoing during an IME composition cancels it.
284 editor.undo(&Default::default(), cx);
285 assert_eq!(editor.text(cx), "ābcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
289 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
290 assert_eq!(editor.text(cx), "ābcdè");
291 assert_eq!(
292 editor.marked_text_ranges(cx),
293 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
294 );
295
296 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
297 editor.replace_text_in_range(Some(4..999), "ę", cx);
298 assert_eq!(editor.text(cx), "ābcdę");
299 assert_eq!(editor.marked_text_ranges(cx), None);
300
301 // Start a new IME composition with multiple cursors.
302 editor.change_selections(None, cx, |s| {
303 s.select_ranges([
304 OffsetUtf16(1)..OffsetUtf16(1),
305 OffsetUtf16(3)..OffsetUtf16(3),
306 OffsetUtf16(5)..OffsetUtf16(5),
307 ])
308 });
309 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
310 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
311 assert_eq!(
312 editor.marked_text_ranges(cx),
313 Some(vec![
314 OffsetUtf16(0)..OffsetUtf16(3),
315 OffsetUtf16(4)..OffsetUtf16(7),
316 OffsetUtf16(8)..OffsetUtf16(11)
317 ])
318 );
319
320 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
321 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
322 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
323 assert_eq!(
324 editor.marked_text_ranges(cx),
325 Some(vec![
326 OffsetUtf16(1)..OffsetUtf16(2),
327 OffsetUtf16(5)..OffsetUtf16(6),
328 OffsetUtf16(9)..OffsetUtf16(10)
329 ])
330 );
331
332 // Finalize IME composition with multiple cursors.
333 editor.replace_text_in_range(Some(9..10), "2", cx);
334 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
335 assert_eq!(editor.marked_text_ranges(cx), None);
336
337 editor
338 });
339}
340
341#[gpui::test]
342fn test_selection_with_mouse(cx: &mut TestAppContext) {
343 init_test(cx, |_| {});
344
345 let editor = cx.add_window(|cx| {
346 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
347 build_editor(buffer, cx)
348 });
349
350 _ = editor.update(cx, |view, cx| {
351 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
352 });
353 assert_eq!(
354 editor
355 .update(cx, |view, cx| view.selections.display_ranges(cx))
356 .unwrap(),
357 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
358 );
359
360 _ = editor.update(cx, |view, cx| {
361 view.update_selection(
362 DisplayPoint::new(DisplayRow(3), 3),
363 0,
364 gpui::Point::<f32>::default(),
365 cx,
366 );
367 });
368
369 assert_eq!(
370 editor
371 .update(cx, |view, cx| view.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
374 );
375
376 _ = editor.update(cx, |view, cx| {
377 view.update_selection(
378 DisplayPoint::new(DisplayRow(1), 1),
379 0,
380 gpui::Point::<f32>::default(),
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |view, cx| view.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
390 );
391
392 _ = editor.update(cx, |view, cx| {
393 view.end_selection(cx);
394 view.update_selection(
395 DisplayPoint::new(DisplayRow(3), 3),
396 0,
397 gpui::Point::<f32>::default(),
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |view, cx| view.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |view, cx| {
410 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
411 view.update_selection(
412 DisplayPoint::new(DisplayRow(0), 0),
413 0,
414 gpui::Point::<f32>::default(),
415 cx,
416 );
417 });
418
419 assert_eq!(
420 editor
421 .update(cx, |view, cx| view.selections.display_ranges(cx))
422 .unwrap(),
423 [
424 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
425 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
426 ]
427 );
428
429 _ = editor.update(cx, |view, cx| {
430 view.end_selection(cx);
431 });
432
433 assert_eq!(
434 editor
435 .update(cx, |view, cx| view.selections.display_ranges(cx))
436 .unwrap(),
437 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
438 );
439}
440
441#[gpui::test]
442fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
443 init_test(cx, |_| {});
444
445 let editor = cx.add_window(|cx| {
446 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
447 build_editor(buffer, cx)
448 });
449
450 _ = editor.update(cx, |view, cx| {
451 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.end_selection(cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 assert_eq!(
467 editor
468 .update(cx, |view, cx| view.selections.display_ranges(cx))
469 .unwrap(),
470 [
471 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
472 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
473 ]
474 );
475
476 _ = editor.update(cx, |view, cx| {
477 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
478 });
479
480 _ = editor.update(cx, |view, cx| {
481 view.end_selection(cx);
482 });
483
484 assert_eq!(
485 editor
486 .update(cx, |view, cx| view.selections.display_ranges(cx))
487 .unwrap(),
488 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
489 );
490}
491
492#[gpui::test]
493fn test_canceling_pending_selection(cx: &mut TestAppContext) {
494 init_test(cx, |_| {});
495
496 let view = cx.add_window(|cx| {
497 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
498 build_editor(buffer, cx)
499 });
500
501 _ = view.update(cx, |view, cx| {
502 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
503 assert_eq!(
504 view.selections.display_ranges(cx),
505 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
506 );
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.update_selection(
511 DisplayPoint::new(DisplayRow(3), 3),
512 0,
513 gpui::Point::<f32>::default(),
514 cx,
515 );
516 assert_eq!(
517 view.selections.display_ranges(cx),
518 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
519 );
520 });
521
522 _ = view.update(cx, |view, cx| {
523 view.cancel(&Cancel, cx);
524 view.update_selection(
525 DisplayPoint::new(DisplayRow(1), 1),
526 0,
527 gpui::Point::<f32>::default(),
528 cx,
529 );
530 assert_eq!(
531 view.selections.display_ranges(cx),
532 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
533 );
534 });
535}
536
537#[gpui::test]
538fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
539 init_test(cx, |_| {});
540
541 let view = cx.add_window(|cx| {
542 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
543 build_editor(buffer, cx)
544 });
545
546 _ = view.update(cx, |view, cx| {
547 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
548 assert_eq!(
549 view.selections.display_ranges(cx),
550 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
551 );
552
553 view.move_down(&Default::default(), cx);
554 assert_eq!(
555 view.selections.display_ranges(cx),
556 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
557 );
558
559 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
560 assert_eq!(
561 view.selections.display_ranges(cx),
562 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
563 );
564
565 view.move_up(&Default::default(), cx);
566 assert_eq!(
567 view.selections.display_ranges(cx),
568 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
569 );
570 });
571}
572
573#[gpui::test]
574fn test_clone(cx: &mut TestAppContext) {
575 init_test(cx, |_| {});
576
577 let (text, selection_ranges) = marked_text_ranges(
578 indoc! {"
579 one
580 two
581 threeˇ
582 four
583 fiveˇ
584 "},
585 true,
586 );
587
588 let editor = cx.add_window(|cx| {
589 let buffer = MultiBuffer::build_simple(&text, cx);
590 build_editor(buffer, cx)
591 });
592
593 _ = editor.update(cx, |editor, cx| {
594 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
595 editor.fold_ranges(
596 [
597 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
598 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
599 ],
600 true,
601 cx,
602 );
603 });
604
605 let cloned_editor = editor
606 .update(cx, |editor, cx| {
607 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
608 })
609 .unwrap()
610 .unwrap();
611
612 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
613 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614
615 assert_eq!(
616 cloned_editor
617 .update(cx, |e, cx| e.display_text(cx))
618 .unwrap(),
619 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
620 );
621 assert_eq!(
622 cloned_snapshot
623 .folds_in_range(0..text.len())
624 .collect::<Vec<_>>(),
625 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
626 );
627 assert_set_eq!(
628 cloned_editor
629 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
630 .unwrap(),
631 editor
632 .update(cx, |editor, cx| editor.selections.ranges(cx))
633 .unwrap()
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |e, cx| e.selections.display_ranges(cx))
638 .unwrap(),
639 editor
640 .update(cx, |e, cx| e.selections.display_ranges(cx))
641 .unwrap()
642 );
643}
644
645#[gpui::test]
646async fn test_navigation_history(cx: &mut TestAppContext) {
647 init_test(cx, |_| {});
648
649 use workspace::item::Item;
650
651 let fs = FakeFs::new(cx.executor());
652 let project = Project::test(fs, [], cx).await;
653 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
654 let pane = workspace
655 .update(cx, |workspace, _| workspace.active_pane().clone())
656 .unwrap();
657
658 _ = workspace.update(cx, |_v, cx| {
659 cx.new_view(|cx| {
660 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
661 let mut editor = build_editor(buffer.clone(), cx);
662 let handle = cx.view();
663 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
664
665 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
666 editor.nav_history.as_mut().unwrap().pop_backward(cx)
667 }
668
669 // Move the cursor a small distance.
670 // Nothing is added to the navigation history.
671 editor.change_selections(None, cx, |s| {
672 s.select_display_ranges([
673 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
674 ])
675 });
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
679 ])
680 });
681 assert!(pop_history(&mut editor, cx).is_none());
682
683 // Move the cursor a large distance.
684 // The history can jump back to the previous position.
685 editor.change_selections(None, cx, |s| {
686 s.select_display_ranges([
687 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
688 ])
689 });
690 let nav_entry = pop_history(&mut editor, cx).unwrap();
691 editor.navigate(nav_entry.data.unwrap(), cx);
692 assert_eq!(nav_entry.item.id(), cx.entity_id());
693 assert_eq!(
694 editor.selections.display_ranges(cx),
695 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
696 );
697 assert!(pop_history(&mut editor, cx).is_none());
698
699 // Move the cursor a small distance via the mouse.
700 // Nothing is added to the navigation history.
701 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
702 editor.end_selection(cx);
703 assert_eq!(
704 editor.selections.display_ranges(cx),
705 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
706 );
707 assert!(pop_history(&mut editor, cx).is_none());
708
709 // Move the cursor a large distance via the mouse.
710 // The history can jump back to the previous position.
711 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
712 editor.end_selection(cx);
713 assert_eq!(
714 editor.selections.display_ranges(cx),
715 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
716 );
717 let nav_entry = pop_history(&mut editor, cx).unwrap();
718 editor.navigate(nav_entry.data.unwrap(), cx);
719 assert_eq!(nav_entry.item.id(), cx.entity_id());
720 assert_eq!(
721 editor.selections.display_ranges(cx),
722 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
723 );
724 assert!(pop_history(&mut editor, cx).is_none());
725
726 // Set scroll position to check later
727 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
728 let original_scroll_position = editor.scroll_manager.anchor();
729
730 // Jump to the end of the document and adjust scroll
731 editor.move_to_end(&MoveToEnd, cx);
732 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
733 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
734
735 let nav_entry = pop_history(&mut editor, cx).unwrap();
736 editor.navigate(nav_entry.data.unwrap(), cx);
737 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
740 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
741 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
742 let invalid_point = Point::new(9999, 0);
743 editor.navigate(
744 Box::new(NavigationData {
745 cursor_anchor: invalid_anchor,
746 cursor_position: invalid_point,
747 scroll_anchor: ScrollAnchor {
748 anchor: invalid_anchor,
749 offset: Default::default(),
750 },
751 scroll_top_row: invalid_point.row,
752 }),
753 cx,
754 );
755 assert_eq!(
756 editor.selections.display_ranges(cx),
757 &[editor.max_point(cx)..editor.max_point(cx)]
758 );
759 assert_eq!(
760 editor.scroll_position(cx),
761 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
762 );
763
764 editor
765 })
766 });
767}
768
769#[gpui::test]
770fn test_cancel(cx: &mut TestAppContext) {
771 init_test(cx, |_| {});
772
773 let view = cx.add_window(|cx| {
774 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
775 build_editor(buffer, cx)
776 });
777
778 _ = view.update(cx, |view, cx| {
779 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
780 view.update_selection(
781 DisplayPoint::new(DisplayRow(1), 1),
782 0,
783 gpui::Point::<f32>::default(),
784 cx,
785 );
786 view.end_selection(cx);
787
788 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
789 view.update_selection(
790 DisplayPoint::new(DisplayRow(0), 3),
791 0,
792 gpui::Point::<f32>::default(),
793 cx,
794 );
795 view.end_selection(cx);
796 assert_eq!(
797 view.selections.display_ranges(cx),
798 [
799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
800 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
801 ]
802 );
803 });
804
805 _ = view.update(cx, |view, cx| {
806 view.cancel(&Cancel, cx);
807 assert_eq!(
808 view.selections.display_ranges(cx),
809 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820}
821
822#[gpui::test]
823fn test_fold_action(cx: &mut TestAppContext) {
824 init_test(cx, |_| {});
825
826 let view = cx.add_window(|cx| {
827 let buffer = MultiBuffer::build_simple(
828 &"
829 impl Foo {
830 // Hello!
831
832 fn a() {
833 1
834 }
835
836 fn b() {
837 2
838 }
839
840 fn c() {
841 3
842 }
843 }
844 "
845 .unindent(),
846 cx,
847 );
848 build_editor(buffer.clone(), cx)
849 });
850
851 _ = view.update(cx, |view, cx| {
852 view.change_selections(None, cx, |s| {
853 s.select_display_ranges([
854 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
855 ]);
856 });
857 view.fold(&Fold, cx);
858 assert_eq!(
859 view.display_text(cx),
860 "
861 impl Foo {
862 // Hello!
863
864 fn a() {
865 1
866 }
867
868 fn b() {⋯
869 }
870
871 fn c() {⋯
872 }
873 }
874 "
875 .unindent(),
876 );
877
878 view.fold(&Fold, cx);
879 assert_eq!(
880 view.display_text(cx),
881 "
882 impl Foo {⋯
883 }
884 "
885 .unindent(),
886 );
887
888 view.unfold_lines(&UnfoldLines, cx);
889 assert_eq!(
890 view.display_text(cx),
891 "
892 impl Foo {
893 // Hello!
894
895 fn a() {
896 1
897 }
898
899 fn b() {⋯
900 }
901
902 fn c() {⋯
903 }
904 }
905 "
906 .unindent(),
907 );
908
909 view.unfold_lines(&UnfoldLines, cx);
910 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
911 });
912}
913
914#[gpui::test]
915fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
916 init_test(cx, |_| {});
917
918 let view = cx.add_window(|cx| {
919 let buffer = MultiBuffer::build_simple(
920 &"
921 class Foo:
922 # Hello!
923
924 def a():
925 print(1)
926
927 def b():
928 print(2)
929
930 def c():
931 print(3)
932 "
933 .unindent(),
934 cx,
935 );
936 build_editor(buffer.clone(), cx)
937 });
938
939 _ = view.update(cx, |view, cx| {
940 view.change_selections(None, cx, |s| {
941 s.select_display_ranges([
942 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
943 ]);
944 });
945 view.fold(&Fold, cx);
946 assert_eq!(
947 view.display_text(cx),
948 "
949 class Foo:
950 # Hello!
951
952 def a():
953 print(1)
954
955 def b():⋯
956
957 def c():⋯
958 "
959 .unindent(),
960 );
961
962 view.fold(&Fold, cx);
963 assert_eq!(
964 view.display_text(cx),
965 "
966 class Foo:⋯
967 "
968 .unindent(),
969 );
970
971 view.unfold_lines(&UnfoldLines, cx);
972 assert_eq!(
973 view.display_text(cx),
974 "
975 class Foo:
976 # Hello!
977
978 def a():
979 print(1)
980
981 def b():⋯
982
983 def c():⋯
984 "
985 .unindent(),
986 );
987
988 view.unfold_lines(&UnfoldLines, cx);
989 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
990 });
991}
992
993#[gpui::test]
994fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
995 init_test(cx, |_| {});
996
997 let view = cx.add_window(|cx| {
998 let buffer = MultiBuffer::build_simple(
999 &"
1000 class Foo:
1001 # Hello!
1002
1003 def a():
1004 print(1)
1005
1006 def b():
1007 print(2)
1008
1009
1010 def c():
1011 print(3)
1012
1013
1014 "
1015 .unindent(),
1016 cx,
1017 );
1018 build_editor(buffer.clone(), cx)
1019 });
1020
1021 _ = view.update(cx, |view, cx| {
1022 view.change_selections(None, cx, |s| {
1023 s.select_display_ranges([
1024 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1025 ]);
1026 });
1027 view.fold(&Fold, cx);
1028 assert_eq!(
1029 view.display_text(cx),
1030 "
1031 class Foo:
1032 # Hello!
1033
1034 def a():
1035 print(1)
1036
1037 def b():⋯
1038
1039
1040 def c():⋯
1041
1042
1043 "
1044 .unindent(),
1045 );
1046
1047 view.fold(&Fold, cx);
1048 assert_eq!(
1049 view.display_text(cx),
1050 "
1051 class Foo:⋯
1052
1053
1054 "
1055 .unindent(),
1056 );
1057
1058 view.unfold_lines(&UnfoldLines, cx);
1059 assert_eq!(
1060 view.display_text(cx),
1061 "
1062 class Foo:
1063 # Hello!
1064
1065 def a():
1066 print(1)
1067
1068 def b():⋯
1069
1070
1071 def c():⋯
1072
1073
1074 "
1075 .unindent(),
1076 );
1077
1078 view.unfold_lines(&UnfoldLines, cx);
1079 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1080 });
1081}
1082
1083#[gpui::test]
1084fn test_move_cursor(cx: &mut TestAppContext) {
1085 init_test(cx, |_| {});
1086
1087 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1088 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1089
1090 buffer.update(cx, |buffer, cx| {
1091 buffer.edit(
1092 vec![
1093 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1094 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1095 ],
1096 None,
1097 cx,
1098 );
1099 });
1100 _ = view.update(cx, |view, cx| {
1101 assert_eq!(
1102 view.selections.display_ranges(cx),
1103 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1104 );
1105
1106 view.move_down(&MoveDown, cx);
1107 assert_eq!(
1108 view.selections.display_ranges(cx),
1109 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1110 );
1111
1112 view.move_right(&MoveRight, cx);
1113 assert_eq!(
1114 view.selections.display_ranges(cx),
1115 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1116 );
1117
1118 view.move_left(&MoveLeft, cx);
1119 assert_eq!(
1120 view.selections.display_ranges(cx),
1121 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1122 );
1123
1124 view.move_up(&MoveUp, cx);
1125 assert_eq!(
1126 view.selections.display_ranges(cx),
1127 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1128 );
1129
1130 view.move_to_end(&MoveToEnd, cx);
1131 assert_eq!(
1132 view.selections.display_ranges(cx),
1133 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1134 );
1135
1136 view.move_to_beginning(&MoveToBeginning, cx);
1137 assert_eq!(
1138 view.selections.display_ranges(cx),
1139 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1140 );
1141
1142 view.change_selections(None, cx, |s| {
1143 s.select_display_ranges([
1144 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1145 ]);
1146 });
1147 view.select_to_beginning(&SelectToBeginning, cx);
1148 assert_eq!(
1149 view.selections.display_ranges(cx),
1150 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1151 );
1152
1153 view.select_to_end(&SelectToEnd, cx);
1154 assert_eq!(
1155 view.selections.display_ranges(cx),
1156 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1157 );
1158 });
1159}
1160
1161// TODO: Re-enable this test
1162#[cfg(target_os = "macos")]
1163#[gpui::test]
1164fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1165 init_test(cx, |_| {});
1166
1167 let view = cx.add_window(|cx| {
1168 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1169 build_editor(buffer.clone(), cx)
1170 });
1171
1172 assert_eq!('ⓐ'.len_utf8(), 3);
1173 assert_eq!('α'.len_utf8(), 2);
1174
1175 _ = view.update(cx, |view, cx| {
1176 view.fold_ranges(
1177 vec![
1178 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1179 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1180 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1181 ],
1182 true,
1183 cx,
1184 );
1185 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1186
1187 view.move_right(&MoveRight, cx);
1188 assert_eq!(
1189 view.selections.display_ranges(cx),
1190 &[empty_range(0, "ⓐ".len())]
1191 );
1192 view.move_right(&MoveRight, cx);
1193 assert_eq!(
1194 view.selections.display_ranges(cx),
1195 &[empty_range(0, "ⓐⓑ".len())]
1196 );
1197 view.move_right(&MoveRight, cx);
1198 assert_eq!(
1199 view.selections.display_ranges(cx),
1200 &[empty_range(0, "ⓐⓑ⋯".len())]
1201 );
1202
1203 view.move_down(&MoveDown, cx);
1204 assert_eq!(
1205 view.selections.display_ranges(cx),
1206 &[empty_range(1, "ab⋯e".len())]
1207 );
1208 view.move_left(&MoveLeft, cx);
1209 assert_eq!(
1210 view.selections.display_ranges(cx),
1211 &[empty_range(1, "ab⋯".len())]
1212 );
1213 view.move_left(&MoveLeft, cx);
1214 assert_eq!(
1215 view.selections.display_ranges(cx),
1216 &[empty_range(1, "ab".len())]
1217 );
1218 view.move_left(&MoveLeft, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[empty_range(1, "a".len())]
1222 );
1223
1224 view.move_down(&MoveDown, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[empty_range(2, "α".len())]
1228 );
1229 view.move_right(&MoveRight, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[empty_range(2, "αβ".len())]
1233 );
1234 view.move_right(&MoveRight, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[empty_range(2, "αβ⋯".len())]
1238 );
1239 view.move_right(&MoveRight, cx);
1240 assert_eq!(
1241 view.selections.display_ranges(cx),
1242 &[empty_range(2, "αβ⋯ε".len())]
1243 );
1244
1245 view.move_up(&MoveUp, cx);
1246 assert_eq!(
1247 view.selections.display_ranges(cx),
1248 &[empty_range(1, "ab⋯e".len())]
1249 );
1250 view.move_down(&MoveDown, cx);
1251 assert_eq!(
1252 view.selections.display_ranges(cx),
1253 &[empty_range(2, "αβ⋯ε".len())]
1254 );
1255 view.move_up(&MoveUp, cx);
1256 assert_eq!(
1257 view.selections.display_ranges(cx),
1258 &[empty_range(1, "ab⋯e".len())]
1259 );
1260
1261 view.move_up(&MoveUp, cx);
1262 assert_eq!(
1263 view.selections.display_ranges(cx),
1264 &[empty_range(0, "ⓐⓑ".len())]
1265 );
1266 view.move_left(&MoveLeft, cx);
1267 assert_eq!(
1268 view.selections.display_ranges(cx),
1269 &[empty_range(0, "ⓐ".len())]
1270 );
1271 view.move_left(&MoveLeft, cx);
1272 assert_eq!(
1273 view.selections.display_ranges(cx),
1274 &[empty_range(0, "".len())]
1275 );
1276 });
1277}
1278
1279#[gpui::test]
1280fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1281 init_test(cx, |_| {});
1282
1283 let view = cx.add_window(|cx| {
1284 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1285 build_editor(buffer.clone(), cx)
1286 });
1287 _ = view.update(cx, |view, cx| {
1288 view.change_selections(None, cx, |s| {
1289 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1290 });
1291 view.move_down(&MoveDown, cx);
1292 assert_eq!(
1293 view.selections.display_ranges(cx),
1294 &[empty_range(1, "abcd".len())]
1295 );
1296
1297 view.move_down(&MoveDown, cx);
1298 assert_eq!(
1299 view.selections.display_ranges(cx),
1300 &[empty_range(2, "αβγ".len())]
1301 );
1302
1303 view.move_down(&MoveDown, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(3, "abcd".len())]
1307 );
1308
1309 view.move_down(&MoveDown, cx);
1310 assert_eq!(
1311 view.selections.display_ranges(cx),
1312 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1313 );
1314
1315 view.move_up(&MoveUp, cx);
1316 assert_eq!(
1317 view.selections.display_ranges(cx),
1318 &[empty_range(3, "abcd".len())]
1319 );
1320
1321 view.move_up(&MoveUp, cx);
1322 assert_eq!(
1323 view.selections.display_ranges(cx),
1324 &[empty_range(2, "αβγ".len())]
1325 );
1326 });
1327}
1328
1329#[gpui::test]
1330fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1331 init_test(cx, |_| {});
1332 let move_to_beg = MoveToBeginningOfLine {
1333 stop_at_soft_wraps: true,
1334 };
1335
1336 let move_to_end = MoveToEndOfLine {
1337 stop_at_soft_wraps: true,
1338 };
1339
1340 let view = cx.add_window(|cx| {
1341 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1342 build_editor(buffer, cx)
1343 });
1344 _ = view.update(cx, |view, cx| {
1345 view.change_selections(None, cx, |s| {
1346 s.select_display_ranges([
1347 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1348 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1349 ]);
1350 });
1351 });
1352
1353 _ = view.update(cx, |view, cx| {
1354 view.move_to_beginning_of_line(&move_to_beg, cx);
1355 assert_eq!(
1356 view.selections.display_ranges(cx),
1357 &[
1358 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1359 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1360 ]
1361 );
1362 });
1363
1364 _ = view.update(cx, |view, cx| {
1365 view.move_to_beginning_of_line(&move_to_beg, cx);
1366 assert_eq!(
1367 view.selections.display_ranges(cx),
1368 &[
1369 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1370 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1371 ]
1372 );
1373 });
1374
1375 _ = view.update(cx, |view, cx| {
1376 view.move_to_beginning_of_line(&move_to_beg, cx);
1377 assert_eq!(
1378 view.selections.display_ranges(cx),
1379 &[
1380 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1381 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1382 ]
1383 );
1384 });
1385
1386 _ = view.update(cx, |view, cx| {
1387 view.move_to_end_of_line(&move_to_end, cx);
1388 assert_eq!(
1389 view.selections.display_ranges(cx),
1390 &[
1391 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1392 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1393 ]
1394 );
1395 });
1396
1397 // Moving to the end of line again is a no-op.
1398 _ = view.update(cx, |view, cx| {
1399 view.move_to_end_of_line(&move_to_end, cx);
1400 assert_eq!(
1401 view.selections.display_ranges(cx),
1402 &[
1403 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1404 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1405 ]
1406 );
1407 });
1408
1409 _ = view.update(cx, |view, cx| {
1410 view.move_left(&MoveLeft, cx);
1411 view.select_to_beginning_of_line(
1412 &SelectToBeginningOfLine {
1413 stop_at_soft_wraps: true,
1414 },
1415 cx,
1416 );
1417 assert_eq!(
1418 view.selections.display_ranges(cx),
1419 &[
1420 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1421 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1422 ]
1423 );
1424 });
1425
1426 _ = view.update(cx, |view, cx| {
1427 view.select_to_beginning_of_line(
1428 &SelectToBeginningOfLine {
1429 stop_at_soft_wraps: true,
1430 },
1431 cx,
1432 );
1433 assert_eq!(
1434 view.selections.display_ranges(cx),
1435 &[
1436 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1437 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1438 ]
1439 );
1440 });
1441
1442 _ = view.update(cx, |view, cx| {
1443 view.select_to_beginning_of_line(
1444 &SelectToBeginningOfLine {
1445 stop_at_soft_wraps: true,
1446 },
1447 cx,
1448 );
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[
1452 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1453 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1454 ]
1455 );
1456 });
1457
1458 _ = view.update(cx, |view, cx| {
1459 view.select_to_end_of_line(
1460 &SelectToEndOfLine {
1461 stop_at_soft_wraps: true,
1462 },
1463 cx,
1464 );
1465 assert_eq!(
1466 view.selections.display_ranges(cx),
1467 &[
1468 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1469 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1470 ]
1471 );
1472 });
1473
1474 _ = view.update(cx, |view, cx| {
1475 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1476 assert_eq!(view.display_text(cx), "ab\n de");
1477 assert_eq!(
1478 view.selections.display_ranges(cx),
1479 &[
1480 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1481 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1482 ]
1483 );
1484 });
1485
1486 _ = view.update(cx, |view, cx| {
1487 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1488 assert_eq!(view.display_text(cx), "\n");
1489 assert_eq!(
1490 view.selections.display_ranges(cx),
1491 &[
1492 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1493 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1494 ]
1495 );
1496 });
1497}
1498
1499#[gpui::test]
1500fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1501 init_test(cx, |_| {});
1502 let move_to_beg = MoveToBeginningOfLine {
1503 stop_at_soft_wraps: false,
1504 };
1505
1506 let move_to_end = MoveToEndOfLine {
1507 stop_at_soft_wraps: false,
1508 };
1509
1510 let view = cx.add_window(|cx| {
1511 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1512 build_editor(buffer, cx)
1513 });
1514
1515 _ = view.update(cx, |view, cx| {
1516 view.set_wrap_width(Some(140.0.into()), cx);
1517
1518 // We expect the following lines after wrapping
1519 // ```
1520 // thequickbrownfox
1521 // jumpedoverthelazydo
1522 // gs
1523 // ```
1524 // The final `gs` was soft-wrapped onto a new line.
1525 assert_eq!(
1526 "thequickbrownfox\njumpedoverthelaz\nydogs",
1527 view.display_text(cx),
1528 );
1529
1530 // First, let's assert behavior on the first line, that was not soft-wrapped.
1531 // Start the cursor at the `k` on the first line
1532 view.change_selections(None, cx, |s| {
1533 s.select_display_ranges([
1534 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1535 ]);
1536 });
1537
1538 // Moving to the beginning of the line should put us at the beginning of the line.
1539 view.move_to_beginning_of_line(&move_to_beg, cx);
1540 assert_eq!(
1541 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1542 view.selections.display_ranges(cx)
1543 );
1544
1545 // Moving to the end of the line should put us at the end of the line.
1546 view.move_to_end_of_line(&move_to_end, cx);
1547 assert_eq!(
1548 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1549 view.selections.display_ranges(cx)
1550 );
1551
1552 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1553 // Start the cursor at the last line (`y` that was wrapped to a new line)
1554 view.change_selections(None, cx, |s| {
1555 s.select_display_ranges([
1556 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1557 ]);
1558 });
1559
1560 // Moving to the beginning of the line should put us at the start of the second line of
1561 // display text, i.e., the `j`.
1562 view.move_to_beginning_of_line(&move_to_beg, cx);
1563 assert_eq!(
1564 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1565 view.selections.display_ranges(cx)
1566 );
1567
1568 // Moving to the beginning of the line again should be a no-op.
1569 view.move_to_beginning_of_line(&move_to_beg, cx);
1570 assert_eq!(
1571 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1572 view.selections.display_ranges(cx)
1573 );
1574
1575 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1576 // next display line.
1577 view.move_to_end_of_line(&move_to_end, cx);
1578 assert_eq!(
1579 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1580 view.selections.display_ranges(cx)
1581 );
1582
1583 // Moving to the end of the line again should be a no-op.
1584 view.move_to_end_of_line(&move_to_end, cx);
1585 assert_eq!(
1586 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1587 view.selections.display_ranges(cx)
1588 );
1589 });
1590}
1591
1592#[gpui::test]
1593fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1594 init_test(cx, |_| {});
1595
1596 let view = cx.add_window(|cx| {
1597 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1598 build_editor(buffer, cx)
1599 });
1600 _ = view.update(cx, |view, cx| {
1601 view.change_selections(None, cx, |s| {
1602 s.select_display_ranges([
1603 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1604 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1605 ])
1606 });
1607
1608 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1609 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1610
1611 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1612 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1613
1614 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1615 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1616
1617 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1618 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1619
1620 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1621 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1622
1623 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1624 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1625
1626 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1627 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1628
1629 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1630 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1631
1632 view.move_right(&MoveRight, cx);
1633 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1634 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1635
1636 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1637 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1638
1639 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1640 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1641 });
1642}
1643
1644#[gpui::test]
1645fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1646 init_test(cx, |_| {});
1647
1648 let view = cx.add_window(|cx| {
1649 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1650 build_editor(buffer, cx)
1651 });
1652
1653 _ = view.update(cx, |view, cx| {
1654 view.set_wrap_width(Some(140.0.into()), cx);
1655 assert_eq!(
1656 view.display_text(cx),
1657 "use one::{\n two::three::\n four::five\n};"
1658 );
1659
1660 view.change_selections(None, cx, |s| {
1661 s.select_display_ranges([
1662 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1663 ]);
1664 });
1665
1666 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1667 assert_eq!(
1668 view.selections.display_ranges(cx),
1669 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1670 );
1671
1672 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1673 assert_eq!(
1674 view.selections.display_ranges(cx),
1675 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1676 );
1677
1678 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1679 assert_eq!(
1680 view.selections.display_ranges(cx),
1681 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1682 );
1683
1684 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1685 assert_eq!(
1686 view.selections.display_ranges(cx),
1687 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1688 );
1689
1690 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1691 assert_eq!(
1692 view.selections.display_ranges(cx),
1693 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1694 );
1695
1696 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1697 assert_eq!(
1698 view.selections.display_ranges(cx),
1699 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1700 );
1701 });
1702}
1703
1704#[gpui::test]
1705async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1706 init_test(cx, |_| {});
1707 let mut cx = EditorTestContext::new(cx).await;
1708
1709 let line_height = cx.editor(|editor, cx| {
1710 editor
1711 .style()
1712 .unwrap()
1713 .text
1714 .line_height_in_pixels(cx.rem_size())
1715 });
1716 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1717
1718 cx.set_state(
1719 &r#"ˇone
1720 two
1721
1722 three
1723 fourˇ
1724 five
1725
1726 six"#
1727 .unindent(),
1728 );
1729
1730 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1731 cx.assert_editor_state(
1732 &r#"one
1733 two
1734 ˇ
1735 three
1736 four
1737 five
1738 ˇ
1739 six"#
1740 .unindent(),
1741 );
1742
1743 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1744 cx.assert_editor_state(
1745 &r#"one
1746 two
1747
1748 three
1749 four
1750 five
1751 ˇ
1752 sixˇ"#
1753 .unindent(),
1754 );
1755
1756 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1757 cx.assert_editor_state(
1758 &r#"one
1759 two
1760
1761 three
1762 four
1763 five
1764
1765 sixˇ"#
1766 .unindent(),
1767 );
1768
1769 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1770 cx.assert_editor_state(
1771 &r#"one
1772 two
1773
1774 three
1775 four
1776 five
1777 ˇ
1778 six"#
1779 .unindent(),
1780 );
1781
1782 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1783 cx.assert_editor_state(
1784 &r#"one
1785 two
1786 ˇ
1787 three
1788 four
1789 five
1790
1791 six"#
1792 .unindent(),
1793 );
1794
1795 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1796 cx.assert_editor_state(
1797 &r#"ˇone
1798 two
1799
1800 three
1801 four
1802 five
1803
1804 six"#
1805 .unindent(),
1806 );
1807}
1808
1809#[gpui::test]
1810async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1811 init_test(cx, |_| {});
1812 let mut cx = EditorTestContext::new(cx).await;
1813 let line_height = cx.editor(|editor, cx| {
1814 editor
1815 .style()
1816 .unwrap()
1817 .text
1818 .line_height_in_pixels(cx.rem_size())
1819 });
1820 let window = cx.window;
1821 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1822
1823 cx.set_state(
1824 r#"ˇone
1825 two
1826 three
1827 four
1828 five
1829 six
1830 seven
1831 eight
1832 nine
1833 ten
1834 "#,
1835 );
1836
1837 cx.update_editor(|editor, cx| {
1838 assert_eq!(
1839 editor.snapshot(cx).scroll_position(),
1840 gpui::Point::new(0., 0.)
1841 );
1842 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1843 assert_eq!(
1844 editor.snapshot(cx).scroll_position(),
1845 gpui::Point::new(0., 3.)
1846 );
1847 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1848 assert_eq!(
1849 editor.snapshot(cx).scroll_position(),
1850 gpui::Point::new(0., 6.)
1851 );
1852 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1853 assert_eq!(
1854 editor.snapshot(cx).scroll_position(),
1855 gpui::Point::new(0., 3.)
1856 );
1857
1858 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1859 assert_eq!(
1860 editor.snapshot(cx).scroll_position(),
1861 gpui::Point::new(0., 1.)
1862 );
1863 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1864 assert_eq!(
1865 editor.snapshot(cx).scroll_position(),
1866 gpui::Point::new(0., 3.)
1867 );
1868 });
1869}
1870
1871#[gpui::test]
1872async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1873 init_test(cx, |_| {});
1874 let mut cx = EditorTestContext::new(cx).await;
1875
1876 let line_height = cx.update_editor(|editor, cx| {
1877 editor.set_vertical_scroll_margin(2, cx);
1878 editor
1879 .style()
1880 .unwrap()
1881 .text
1882 .line_height_in_pixels(cx.rem_size())
1883 });
1884 let window = cx.window;
1885 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1886
1887 cx.set_state(
1888 r#"ˇone
1889 two
1890 three
1891 four
1892 five
1893 six
1894 seven
1895 eight
1896 nine
1897 ten
1898 "#,
1899 );
1900 cx.update_editor(|editor, cx| {
1901 assert_eq!(
1902 editor.snapshot(cx).scroll_position(),
1903 gpui::Point::new(0., 0.0)
1904 );
1905 });
1906
1907 // Add a cursor below the visible area. Since both cursors cannot fit
1908 // on screen, the editor autoscrolls to reveal the newest cursor, and
1909 // allows the vertical scroll margin below that cursor.
1910 cx.update_editor(|editor, cx| {
1911 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1912 selections.select_ranges([
1913 Point::new(0, 0)..Point::new(0, 0),
1914 Point::new(6, 0)..Point::new(6, 0),
1915 ]);
1916 })
1917 });
1918 cx.update_editor(|editor, cx| {
1919 assert_eq!(
1920 editor.snapshot(cx).scroll_position(),
1921 gpui::Point::new(0., 3.0)
1922 );
1923 });
1924
1925 // Move down. The editor cursor scrolls down to track the newest cursor.
1926 cx.update_editor(|editor, cx| {
1927 editor.move_down(&Default::default(), cx);
1928 });
1929 cx.update_editor(|editor, cx| {
1930 assert_eq!(
1931 editor.snapshot(cx).scroll_position(),
1932 gpui::Point::new(0., 4.0)
1933 );
1934 });
1935
1936 // Add a cursor above the visible area. Since both cursors fit on screen,
1937 // the editor scrolls to show both.
1938 cx.update_editor(|editor, cx| {
1939 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1940 selections.select_ranges([
1941 Point::new(1, 0)..Point::new(1, 0),
1942 Point::new(6, 0)..Point::new(6, 0),
1943 ]);
1944 })
1945 });
1946 cx.update_editor(|editor, cx| {
1947 assert_eq!(
1948 editor.snapshot(cx).scroll_position(),
1949 gpui::Point::new(0., 1.0)
1950 );
1951 });
1952}
1953
1954#[gpui::test]
1955async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1956 init_test(cx, |_| {});
1957 let mut cx = EditorTestContext::new(cx).await;
1958
1959 let line_height = cx.editor(|editor, cx| {
1960 editor
1961 .style()
1962 .unwrap()
1963 .text
1964 .line_height_in_pixels(cx.rem_size())
1965 });
1966 let window = cx.window;
1967 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1968 cx.set_state(
1969 &r#"
1970 ˇone
1971 two
1972 threeˇ
1973 four
1974 five
1975 six
1976 seven
1977 eight
1978 nine
1979 ten
1980 "#
1981 .unindent(),
1982 );
1983
1984 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1985 cx.assert_editor_state(
1986 &r#"
1987 one
1988 two
1989 three
1990 ˇfour
1991 five
1992 sixˇ
1993 seven
1994 eight
1995 nine
1996 ten
1997 "#
1998 .unindent(),
1999 );
2000
2001 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2002 cx.assert_editor_state(
2003 &r#"
2004 one
2005 two
2006 three
2007 four
2008 five
2009 six
2010 ˇseven
2011 eight
2012 nineˇ
2013 ten
2014 "#
2015 .unindent(),
2016 );
2017
2018 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2019 cx.assert_editor_state(
2020 &r#"
2021 one
2022 two
2023 three
2024 ˇfour
2025 five
2026 sixˇ
2027 seven
2028 eight
2029 nine
2030 ten
2031 "#
2032 .unindent(),
2033 );
2034
2035 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2036 cx.assert_editor_state(
2037 &r#"
2038 ˇone
2039 two
2040 threeˇ
2041 four
2042 five
2043 six
2044 seven
2045 eight
2046 nine
2047 ten
2048 "#
2049 .unindent(),
2050 );
2051
2052 // Test select collapsing
2053 cx.update_editor(|editor, cx| {
2054 editor.move_page_down(&MovePageDown::default(), cx);
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 });
2058 cx.assert_editor_state(
2059 &r#"
2060 one
2061 two
2062 three
2063 four
2064 five
2065 six
2066 seven
2067 eight
2068 nine
2069 ˇten
2070 ˇ"#
2071 .unindent(),
2072 );
2073}
2074
2075#[gpui::test]
2076async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2077 init_test(cx, |_| {});
2078 let mut cx = EditorTestContext::new(cx).await;
2079 cx.set_state("one «two threeˇ» four");
2080 cx.update_editor(|editor, cx| {
2081 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2082 assert_eq!(editor.text(cx), " four");
2083 });
2084}
2085
2086#[gpui::test]
2087fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2088 init_test(cx, |_| {});
2089
2090 let view = cx.add_window(|cx| {
2091 let buffer = MultiBuffer::build_simple("one two three four", cx);
2092 build_editor(buffer.clone(), cx)
2093 });
2094
2095 _ = view.update(cx, |view, cx| {
2096 view.change_selections(None, cx, |s| {
2097 s.select_display_ranges([
2098 // an empty selection - the preceding word fragment is deleted
2099 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2100 // characters selected - they are deleted
2101 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2102 ])
2103 });
2104 view.delete_to_previous_word_start(
2105 &DeleteToPreviousWordStart {
2106 ignore_newlines: false,
2107 },
2108 cx,
2109 );
2110 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2111 });
2112
2113 _ = view.update(cx, |view, cx| {
2114 view.change_selections(None, cx, |s| {
2115 s.select_display_ranges([
2116 // an empty selection - the following word fragment is deleted
2117 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2118 // characters selected - they are deleted
2119 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2120 ])
2121 });
2122 view.delete_to_next_word_end(
2123 &DeleteToNextWordEnd {
2124 ignore_newlines: false,
2125 },
2126 cx,
2127 );
2128 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2129 });
2130}
2131
2132#[gpui::test]
2133fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2134 init_test(cx, |_| {});
2135
2136 let view = cx.add_window(|cx| {
2137 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2138 build_editor(buffer.clone(), cx)
2139 });
2140 let del_to_prev_word_start = DeleteToPreviousWordStart {
2141 ignore_newlines: false,
2142 };
2143 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2144 ignore_newlines: true,
2145 };
2146
2147 _ = view.update(cx, |view, cx| {
2148 view.change_selections(None, cx, |s| {
2149 s.select_display_ranges([
2150 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2151 ])
2152 });
2153 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2154 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2155 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2156 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2157 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2158 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2159 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2160 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2161 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2162 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2163 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2164 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2165 });
2166}
2167
2168#[gpui::test]
2169fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2170 init_test(cx, |_| {});
2171
2172 let view = cx.add_window(|cx| {
2173 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2174 build_editor(buffer.clone(), cx)
2175 });
2176 let del_to_next_word_end = DeleteToNextWordEnd {
2177 ignore_newlines: false,
2178 };
2179 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2180 ignore_newlines: true,
2181 };
2182
2183 _ = view.update(cx, |view, cx| {
2184 view.change_selections(None, cx, |s| {
2185 s.select_display_ranges([
2186 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2187 ])
2188 });
2189 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2190 assert_eq!(
2191 view.buffer.read(cx).read(cx).text(),
2192 "one\n two\nthree\n four"
2193 );
2194 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2195 assert_eq!(
2196 view.buffer.read(cx).read(cx).text(),
2197 "\n two\nthree\n four"
2198 );
2199 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2200 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2201 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2202 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2203 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2204 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2205 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2206 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2207 });
2208}
2209
2210#[gpui::test]
2211fn test_newline(cx: &mut TestAppContext) {
2212 init_test(cx, |_| {});
2213
2214 let view = cx.add_window(|cx| {
2215 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2216 build_editor(buffer.clone(), cx)
2217 });
2218
2219 _ = view.update(cx, |view, cx| {
2220 view.change_selections(None, cx, |s| {
2221 s.select_display_ranges([
2222 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2223 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2224 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2225 ])
2226 });
2227
2228 view.newline(&Newline, cx);
2229 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2230 });
2231}
2232
2233#[gpui::test]
2234fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2235 init_test(cx, |_| {});
2236
2237 let editor = cx.add_window(|cx| {
2238 let buffer = MultiBuffer::build_simple(
2239 "
2240 a
2241 b(
2242 X
2243 )
2244 c(
2245 X
2246 )
2247 "
2248 .unindent()
2249 .as_str(),
2250 cx,
2251 );
2252 let mut editor = build_editor(buffer.clone(), cx);
2253 editor.change_selections(None, cx, |s| {
2254 s.select_ranges([
2255 Point::new(2, 4)..Point::new(2, 5),
2256 Point::new(5, 4)..Point::new(5, 5),
2257 ])
2258 });
2259 editor
2260 });
2261
2262 _ = editor.update(cx, |editor, cx| {
2263 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2264 editor.buffer.update(cx, |buffer, cx| {
2265 buffer.edit(
2266 [
2267 (Point::new(1, 2)..Point::new(3, 0), ""),
2268 (Point::new(4, 2)..Point::new(6, 0), ""),
2269 ],
2270 None,
2271 cx,
2272 );
2273 assert_eq!(
2274 buffer.read(cx).text(),
2275 "
2276 a
2277 b()
2278 c()
2279 "
2280 .unindent()
2281 );
2282 });
2283 assert_eq!(
2284 editor.selections.ranges(cx),
2285 &[
2286 Point::new(1, 2)..Point::new(1, 2),
2287 Point::new(2, 2)..Point::new(2, 2),
2288 ],
2289 );
2290
2291 editor.newline(&Newline, cx);
2292 assert_eq!(
2293 editor.text(cx),
2294 "
2295 a
2296 b(
2297 )
2298 c(
2299 )
2300 "
2301 .unindent()
2302 );
2303
2304 // The selections are moved after the inserted newlines
2305 assert_eq!(
2306 editor.selections.ranges(cx),
2307 &[
2308 Point::new(2, 0)..Point::new(2, 0),
2309 Point::new(4, 0)..Point::new(4, 0),
2310 ],
2311 );
2312 });
2313}
2314
2315#[gpui::test]
2316async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2317 init_test(cx, |settings| {
2318 settings.defaults.tab_size = NonZeroU32::new(4)
2319 });
2320
2321 let language = Arc::new(
2322 Language::new(
2323 LanguageConfig::default(),
2324 Some(tree_sitter_rust::LANGUAGE.into()),
2325 )
2326 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2327 .unwrap(),
2328 );
2329
2330 let mut cx = EditorTestContext::new(cx).await;
2331 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2332 cx.set_state(indoc! {"
2333 const a: ˇA = (
2334 (ˇ
2335 «const_functionˇ»(ˇ),
2336 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2337 )ˇ
2338 ˇ);ˇ
2339 "});
2340
2341 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2342 cx.assert_editor_state(indoc! {"
2343 ˇ
2344 const a: A = (
2345 ˇ
2346 (
2347 ˇ
2348 ˇ
2349 const_function(),
2350 ˇ
2351 ˇ
2352 ˇ
2353 ˇ
2354 something_else,
2355 ˇ
2356 )
2357 ˇ
2358 ˇ
2359 );
2360 "});
2361}
2362
2363#[gpui::test]
2364async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2365 init_test(cx, |settings| {
2366 settings.defaults.tab_size = NonZeroU32::new(4)
2367 });
2368
2369 let language = Arc::new(
2370 Language::new(
2371 LanguageConfig::default(),
2372 Some(tree_sitter_rust::LANGUAGE.into()),
2373 )
2374 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2375 .unwrap(),
2376 );
2377
2378 let mut cx = EditorTestContext::new(cx).await;
2379 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2380 cx.set_state(indoc! {"
2381 const a: ˇA = (
2382 (ˇ
2383 «const_functionˇ»(ˇ),
2384 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2385 )ˇ
2386 ˇ);ˇ
2387 "});
2388
2389 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2390 cx.assert_editor_state(indoc! {"
2391 const a: A = (
2392 ˇ
2393 (
2394 ˇ
2395 const_function(),
2396 ˇ
2397 ˇ
2398 something_else,
2399 ˇ
2400 ˇ
2401 ˇ
2402 ˇ
2403 )
2404 ˇ
2405 );
2406 ˇ
2407 ˇ
2408 "});
2409}
2410
2411#[gpui::test]
2412async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2413 init_test(cx, |settings| {
2414 settings.defaults.tab_size = NonZeroU32::new(4)
2415 });
2416
2417 let language = Arc::new(Language::new(
2418 LanguageConfig {
2419 line_comments: vec!["//".into()],
2420 ..LanguageConfig::default()
2421 },
2422 None,
2423 ));
2424 {
2425 let mut cx = EditorTestContext::new(cx).await;
2426 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2427 cx.set_state(indoc! {"
2428 // Fooˇ
2429 "});
2430
2431 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2432 cx.assert_editor_state(indoc! {"
2433 // Foo
2434 //ˇ
2435 "});
2436 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2437 cx.set_state(indoc! {"
2438 ˇ// Foo
2439 "});
2440 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2441 cx.assert_editor_state(indoc! {"
2442
2443 ˇ// Foo
2444 "});
2445 }
2446 // Ensure that comment continuations can be disabled.
2447 update_test_language_settings(cx, |settings| {
2448 settings.defaults.extend_comment_on_newline = Some(false);
2449 });
2450 let mut cx = EditorTestContext::new(cx).await;
2451 cx.set_state(indoc! {"
2452 // Fooˇ
2453 "});
2454 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2455 cx.assert_editor_state(indoc! {"
2456 // Foo
2457 ˇ
2458 "});
2459}
2460
2461#[gpui::test]
2462fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2463 init_test(cx, |_| {});
2464
2465 let editor = cx.add_window(|cx| {
2466 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2467 let mut editor = build_editor(buffer.clone(), cx);
2468 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2469 editor
2470 });
2471
2472 _ = editor.update(cx, |editor, cx| {
2473 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2474 editor.buffer.update(cx, |buffer, cx| {
2475 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2476 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2477 });
2478 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2479
2480 editor.insert("Z", cx);
2481 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2482
2483 // The selections are moved after the inserted characters
2484 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2485 });
2486}
2487
2488#[gpui::test]
2489async fn test_tab(cx: &mut gpui::TestAppContext) {
2490 init_test(cx, |settings| {
2491 settings.defaults.tab_size = NonZeroU32::new(3)
2492 });
2493
2494 let mut cx = EditorTestContext::new(cx).await;
2495 cx.set_state(indoc! {"
2496 ˇabˇc
2497 ˇ🏀ˇ🏀ˇefg
2498 dˇ
2499 "});
2500 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2501 cx.assert_editor_state(indoc! {"
2502 ˇab ˇc
2503 ˇ🏀 ˇ🏀 ˇefg
2504 d ˇ
2505 "});
2506
2507 cx.set_state(indoc! {"
2508 a
2509 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2510 "});
2511 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2512 cx.assert_editor_state(indoc! {"
2513 a
2514 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2515 "});
2516}
2517
2518#[gpui::test]
2519async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2520 init_test(cx, |_| {});
2521
2522 let mut cx = EditorTestContext::new(cx).await;
2523 let language = Arc::new(
2524 Language::new(
2525 LanguageConfig::default(),
2526 Some(tree_sitter_rust::LANGUAGE.into()),
2527 )
2528 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2529 .unwrap(),
2530 );
2531 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2532
2533 // cursors that are already at the suggested indent level insert
2534 // a soft tab. cursors that are to the left of the suggested indent
2535 // auto-indent their line.
2536 cx.set_state(indoc! {"
2537 ˇ
2538 const a: B = (
2539 c(
2540 d(
2541 ˇ
2542 )
2543 ˇ
2544 ˇ )
2545 );
2546 "});
2547 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2548 cx.assert_editor_state(indoc! {"
2549 ˇ
2550 const a: B = (
2551 c(
2552 d(
2553 ˇ
2554 )
2555 ˇ
2556 ˇ)
2557 );
2558 "});
2559
2560 // handle auto-indent when there are multiple cursors on the same line
2561 cx.set_state(indoc! {"
2562 const a: B = (
2563 c(
2564 ˇ ˇ
2565 ˇ )
2566 );
2567 "});
2568 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2569 cx.assert_editor_state(indoc! {"
2570 const a: B = (
2571 c(
2572 ˇ
2573 ˇ)
2574 );
2575 "});
2576}
2577
2578#[gpui::test]
2579async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2580 init_test(cx, |settings| {
2581 settings.defaults.tab_size = NonZeroU32::new(4)
2582 });
2583
2584 let language = Arc::new(
2585 Language::new(
2586 LanguageConfig::default(),
2587 Some(tree_sitter_rust::LANGUAGE.into()),
2588 )
2589 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2590 .unwrap(),
2591 );
2592
2593 let mut cx = EditorTestContext::new(cx).await;
2594 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2595 cx.set_state(indoc! {"
2596 fn a() {
2597 if b {
2598 \t ˇc
2599 }
2600 }
2601 "});
2602
2603 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2604 cx.assert_editor_state(indoc! {"
2605 fn a() {
2606 if b {
2607 ˇc
2608 }
2609 }
2610 "});
2611}
2612
2613#[gpui::test]
2614async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2615 init_test(cx, |settings| {
2616 settings.defaults.tab_size = NonZeroU32::new(4);
2617 });
2618
2619 let mut cx = EditorTestContext::new(cx).await;
2620
2621 cx.set_state(indoc! {"
2622 «oneˇ» «twoˇ»
2623 three
2624 four
2625 "});
2626 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2627 cx.assert_editor_state(indoc! {"
2628 «oneˇ» «twoˇ»
2629 three
2630 four
2631 "});
2632
2633 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2634 cx.assert_editor_state(indoc! {"
2635 «oneˇ» «twoˇ»
2636 three
2637 four
2638 "});
2639
2640 // select across line ending
2641 cx.set_state(indoc! {"
2642 one two
2643 t«hree
2644 ˇ» four
2645 "});
2646 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2647 cx.assert_editor_state(indoc! {"
2648 one two
2649 t«hree
2650 ˇ» four
2651 "});
2652
2653 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2654 cx.assert_editor_state(indoc! {"
2655 one two
2656 t«hree
2657 ˇ» four
2658 "});
2659
2660 // Ensure that indenting/outdenting works when the cursor is at column 0.
2661 cx.set_state(indoc! {"
2662 one two
2663 ˇthree
2664 four
2665 "});
2666 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2667 cx.assert_editor_state(indoc! {"
2668 one two
2669 ˇthree
2670 four
2671 "});
2672
2673 cx.set_state(indoc! {"
2674 one two
2675 ˇ three
2676 four
2677 "});
2678 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2679 cx.assert_editor_state(indoc! {"
2680 one two
2681 ˇthree
2682 four
2683 "});
2684}
2685
2686#[gpui::test]
2687async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2688 init_test(cx, |settings| {
2689 settings.defaults.hard_tabs = Some(true);
2690 });
2691
2692 let mut cx = EditorTestContext::new(cx).await;
2693
2694 // select two ranges on one line
2695 cx.set_state(indoc! {"
2696 «oneˇ» «twoˇ»
2697 three
2698 four
2699 "});
2700 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2701 cx.assert_editor_state(indoc! {"
2702 \t«oneˇ» «twoˇ»
2703 three
2704 four
2705 "});
2706 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2707 cx.assert_editor_state(indoc! {"
2708 \t\t«oneˇ» «twoˇ»
2709 three
2710 four
2711 "});
2712 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2713 cx.assert_editor_state(indoc! {"
2714 \t«oneˇ» «twoˇ»
2715 three
2716 four
2717 "});
2718 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2719 cx.assert_editor_state(indoc! {"
2720 «oneˇ» «twoˇ»
2721 three
2722 four
2723 "});
2724
2725 // select across a line ending
2726 cx.set_state(indoc! {"
2727 one two
2728 t«hree
2729 ˇ»four
2730 "});
2731 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2732 cx.assert_editor_state(indoc! {"
2733 one two
2734 \tt«hree
2735 ˇ»four
2736 "});
2737 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2738 cx.assert_editor_state(indoc! {"
2739 one two
2740 \t\tt«hree
2741 ˇ»four
2742 "});
2743 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2744 cx.assert_editor_state(indoc! {"
2745 one two
2746 \tt«hree
2747 ˇ»four
2748 "});
2749 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2750 cx.assert_editor_state(indoc! {"
2751 one two
2752 t«hree
2753 ˇ»four
2754 "});
2755
2756 // Ensure that indenting/outdenting works when the cursor is at column 0.
2757 cx.set_state(indoc! {"
2758 one two
2759 ˇthree
2760 four
2761 "});
2762 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2763 cx.assert_editor_state(indoc! {"
2764 one two
2765 ˇthree
2766 four
2767 "});
2768 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2769 cx.assert_editor_state(indoc! {"
2770 one two
2771 \tˇthree
2772 four
2773 "});
2774 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2775 cx.assert_editor_state(indoc! {"
2776 one two
2777 ˇthree
2778 four
2779 "});
2780}
2781
2782#[gpui::test]
2783fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2784 init_test(cx, |settings| {
2785 settings.languages.extend([
2786 (
2787 "TOML".into(),
2788 LanguageSettingsContent {
2789 tab_size: NonZeroU32::new(2),
2790 ..Default::default()
2791 },
2792 ),
2793 (
2794 "Rust".into(),
2795 LanguageSettingsContent {
2796 tab_size: NonZeroU32::new(4),
2797 ..Default::default()
2798 },
2799 ),
2800 ]);
2801 });
2802
2803 let toml_language = Arc::new(Language::new(
2804 LanguageConfig {
2805 name: "TOML".into(),
2806 ..Default::default()
2807 },
2808 None,
2809 ));
2810 let rust_language = Arc::new(Language::new(
2811 LanguageConfig {
2812 name: "Rust".into(),
2813 ..Default::default()
2814 },
2815 None,
2816 ));
2817
2818 let toml_buffer =
2819 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2820 let rust_buffer = cx.new_model(|cx| {
2821 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2822 });
2823 let multibuffer = cx.new_model(|cx| {
2824 let mut multibuffer = MultiBuffer::new(ReadWrite);
2825 multibuffer.push_excerpts(
2826 toml_buffer.clone(),
2827 [ExcerptRange {
2828 context: Point::new(0, 0)..Point::new(2, 0),
2829 primary: None,
2830 }],
2831 cx,
2832 );
2833 multibuffer.push_excerpts(
2834 rust_buffer.clone(),
2835 [ExcerptRange {
2836 context: Point::new(0, 0)..Point::new(1, 0),
2837 primary: None,
2838 }],
2839 cx,
2840 );
2841 multibuffer
2842 });
2843
2844 cx.add_window(|cx| {
2845 let mut editor = build_editor(multibuffer, cx);
2846
2847 assert_eq!(
2848 editor.text(cx),
2849 indoc! {"
2850 a = 1
2851 b = 2
2852
2853 const c: usize = 3;
2854 "}
2855 );
2856
2857 select_ranges(
2858 &mut editor,
2859 indoc! {"
2860 «aˇ» = 1
2861 b = 2
2862
2863 «const c:ˇ» usize = 3;
2864 "},
2865 cx,
2866 );
2867
2868 editor.tab(&Tab, cx);
2869 assert_text_with_selections(
2870 &mut editor,
2871 indoc! {"
2872 «aˇ» = 1
2873 b = 2
2874
2875 «const c:ˇ» usize = 3;
2876 "},
2877 cx,
2878 );
2879 editor.tab_prev(&TabPrev, cx);
2880 assert_text_with_selections(
2881 &mut editor,
2882 indoc! {"
2883 «aˇ» = 1
2884 b = 2
2885
2886 «const c:ˇ» usize = 3;
2887 "},
2888 cx,
2889 );
2890
2891 editor
2892 });
2893}
2894
2895#[gpui::test]
2896async fn test_backspace(cx: &mut gpui::TestAppContext) {
2897 init_test(cx, |_| {});
2898
2899 let mut cx = EditorTestContext::new(cx).await;
2900
2901 // Basic backspace
2902 cx.set_state(indoc! {"
2903 onˇe two three
2904 fou«rˇ» five six
2905 seven «ˇeight nine
2906 »ten
2907 "});
2908 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2909 cx.assert_editor_state(indoc! {"
2910 oˇe two three
2911 fouˇ five six
2912 seven ˇten
2913 "});
2914
2915 // Test backspace inside and around indents
2916 cx.set_state(indoc! {"
2917 zero
2918 ˇone
2919 ˇtwo
2920 ˇ ˇ ˇ three
2921 ˇ ˇ four
2922 "});
2923 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2924 cx.assert_editor_state(indoc! {"
2925 zero
2926 ˇone
2927 ˇtwo
2928 ˇ threeˇ four
2929 "});
2930
2931 // Test backspace with line_mode set to true
2932 cx.update_editor(|e, _| e.selections.line_mode = true);
2933 cx.set_state(indoc! {"
2934 The ˇquick ˇbrown
2935 fox jumps over
2936 the lazy dog
2937 ˇThe qu«ick bˇ»rown"});
2938 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2939 cx.assert_editor_state(indoc! {"
2940 ˇfox jumps over
2941 the lazy dogˇ"});
2942}
2943
2944#[gpui::test]
2945async fn test_delete(cx: &mut gpui::TestAppContext) {
2946 init_test(cx, |_| {});
2947
2948 let mut cx = EditorTestContext::new(cx).await;
2949 cx.set_state(indoc! {"
2950 onˇe two three
2951 fou«rˇ» five six
2952 seven «ˇeight nine
2953 »ten
2954 "});
2955 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2956 cx.assert_editor_state(indoc! {"
2957 onˇ two three
2958 fouˇ five six
2959 seven ˇten
2960 "});
2961
2962 // Test backspace with line_mode set to true
2963 cx.update_editor(|e, _| e.selections.line_mode = true);
2964 cx.set_state(indoc! {"
2965 The ˇquick ˇbrown
2966 fox «ˇjum»ps over
2967 the lazy dog
2968 ˇThe qu«ick bˇ»rown"});
2969 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2970 cx.assert_editor_state("ˇthe lazy dogˇ");
2971}
2972
2973#[gpui::test]
2974fn test_delete_line(cx: &mut TestAppContext) {
2975 init_test(cx, |_| {});
2976
2977 let view = cx.add_window(|cx| {
2978 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2979 build_editor(buffer, cx)
2980 });
2981 _ = view.update(cx, |view, cx| {
2982 view.change_selections(None, cx, |s| {
2983 s.select_display_ranges([
2984 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2985 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2986 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2987 ])
2988 });
2989 view.delete_line(&DeleteLine, cx);
2990 assert_eq!(view.display_text(cx), "ghi");
2991 assert_eq!(
2992 view.selections.display_ranges(cx),
2993 vec![
2994 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2995 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2996 ]
2997 );
2998 });
2999
3000 let view = cx.add_window(|cx| {
3001 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3002 build_editor(buffer, cx)
3003 });
3004 _ = view.update(cx, |view, cx| {
3005 view.change_selections(None, cx, |s| {
3006 s.select_display_ranges([
3007 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3008 ])
3009 });
3010 view.delete_line(&DeleteLine, cx);
3011 assert_eq!(view.display_text(cx), "ghi\n");
3012 assert_eq!(
3013 view.selections.display_ranges(cx),
3014 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3015 );
3016 });
3017}
3018
3019#[gpui::test]
3020fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3021 init_test(cx, |_| {});
3022
3023 cx.add_window(|cx| {
3024 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3025 let mut editor = build_editor(buffer.clone(), cx);
3026 let buffer = buffer.read(cx).as_singleton().unwrap();
3027
3028 assert_eq!(
3029 editor.selections.ranges::<Point>(cx),
3030 &[Point::new(0, 0)..Point::new(0, 0)]
3031 );
3032
3033 // When on single line, replace newline at end by space
3034 editor.join_lines(&JoinLines, cx);
3035 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3036 assert_eq!(
3037 editor.selections.ranges::<Point>(cx),
3038 &[Point::new(0, 3)..Point::new(0, 3)]
3039 );
3040
3041 // When multiple lines are selected, remove newlines that are spanned by the selection
3042 editor.change_selections(None, cx, |s| {
3043 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3044 });
3045 editor.join_lines(&JoinLines, cx);
3046 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3047 assert_eq!(
3048 editor.selections.ranges::<Point>(cx),
3049 &[Point::new(0, 11)..Point::new(0, 11)]
3050 );
3051
3052 // Undo should be transactional
3053 editor.undo(&Undo, cx);
3054 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3055 assert_eq!(
3056 editor.selections.ranges::<Point>(cx),
3057 &[Point::new(0, 5)..Point::new(2, 2)]
3058 );
3059
3060 // When joining an empty line don't insert a space
3061 editor.change_selections(None, cx, |s| {
3062 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3063 });
3064 editor.join_lines(&JoinLines, cx);
3065 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3066 assert_eq!(
3067 editor.selections.ranges::<Point>(cx),
3068 [Point::new(2, 3)..Point::new(2, 3)]
3069 );
3070
3071 // We can remove trailing newlines
3072 editor.join_lines(&JoinLines, cx);
3073 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3074 assert_eq!(
3075 editor.selections.ranges::<Point>(cx),
3076 [Point::new(2, 3)..Point::new(2, 3)]
3077 );
3078
3079 // We don't blow up on the last line
3080 editor.join_lines(&JoinLines, cx);
3081 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3082 assert_eq!(
3083 editor.selections.ranges::<Point>(cx),
3084 [Point::new(2, 3)..Point::new(2, 3)]
3085 );
3086
3087 // reset to test indentation
3088 editor.buffer.update(cx, |buffer, cx| {
3089 buffer.edit(
3090 [
3091 (Point::new(1, 0)..Point::new(1, 2), " "),
3092 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3093 ],
3094 None,
3095 cx,
3096 )
3097 });
3098
3099 // We remove any leading spaces
3100 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3101 editor.change_selections(None, cx, |s| {
3102 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3103 });
3104 editor.join_lines(&JoinLines, cx);
3105 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3106
3107 // We don't insert a space for a line containing only spaces
3108 editor.join_lines(&JoinLines, cx);
3109 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3110
3111 // We ignore any leading tabs
3112 editor.join_lines(&JoinLines, cx);
3113 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3114
3115 editor
3116 });
3117}
3118
3119#[gpui::test]
3120fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3121 init_test(cx, |_| {});
3122
3123 cx.add_window(|cx| {
3124 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3125 let mut editor = build_editor(buffer.clone(), cx);
3126 let buffer = buffer.read(cx).as_singleton().unwrap();
3127
3128 editor.change_selections(None, cx, |s| {
3129 s.select_ranges([
3130 Point::new(0, 2)..Point::new(1, 1),
3131 Point::new(1, 2)..Point::new(1, 2),
3132 Point::new(3, 1)..Point::new(3, 2),
3133 ])
3134 });
3135
3136 editor.join_lines(&JoinLines, cx);
3137 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3138
3139 assert_eq!(
3140 editor.selections.ranges::<Point>(cx),
3141 [
3142 Point::new(0, 7)..Point::new(0, 7),
3143 Point::new(1, 3)..Point::new(1, 3)
3144 ]
3145 );
3146 editor
3147 });
3148}
3149
3150#[gpui::test]
3151async fn test_join_lines_with_git_diff_base(
3152 executor: BackgroundExecutor,
3153 cx: &mut gpui::TestAppContext,
3154) {
3155 init_test(cx, |_| {});
3156
3157 let mut cx = EditorTestContext::new(cx).await;
3158
3159 let diff_base = r#"
3160 Line 0
3161 Line 1
3162 Line 2
3163 Line 3
3164 "#
3165 .unindent();
3166
3167 cx.set_state(
3168 &r#"
3169 ˇLine 0
3170 Line 1
3171 Line 2
3172 Line 3
3173 "#
3174 .unindent(),
3175 );
3176
3177 cx.set_diff_base(Some(&diff_base));
3178 executor.run_until_parked();
3179
3180 // Join lines
3181 cx.update_editor(|editor, cx| {
3182 editor.join_lines(&JoinLines, cx);
3183 });
3184 executor.run_until_parked();
3185
3186 cx.assert_editor_state(
3187 &r#"
3188 Line 0ˇ Line 1
3189 Line 2
3190 Line 3
3191 "#
3192 .unindent(),
3193 );
3194 // Join again
3195 cx.update_editor(|editor, cx| {
3196 editor.join_lines(&JoinLines, cx);
3197 });
3198 executor.run_until_parked();
3199
3200 cx.assert_editor_state(
3201 &r#"
3202 Line 0 Line 1ˇ Line 2
3203 Line 3
3204 "#
3205 .unindent(),
3206 );
3207}
3208
3209#[gpui::test]
3210async fn test_custom_newlines_cause_no_false_positive_diffs(
3211 executor: BackgroundExecutor,
3212 cx: &mut gpui::TestAppContext,
3213) {
3214 init_test(cx, |_| {});
3215 let mut cx = EditorTestContext::new(cx).await;
3216 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3217 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3218 executor.run_until_parked();
3219
3220 cx.update_editor(|editor, cx| {
3221 assert_eq!(
3222 editor
3223 .buffer()
3224 .read(cx)
3225 .snapshot(cx)
3226 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3227 .collect::<Vec<_>>(),
3228 Vec::new(),
3229 "Should not have any diffs for files with custom newlines"
3230 );
3231 });
3232}
3233
3234#[gpui::test]
3235async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3236 init_test(cx, |_| {});
3237
3238 let mut cx = EditorTestContext::new(cx).await;
3239
3240 // Test sort_lines_case_insensitive()
3241 cx.set_state(indoc! {"
3242 «z
3243 y
3244 x
3245 Z
3246 Y
3247 Xˇ»
3248 "});
3249 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3250 cx.assert_editor_state(indoc! {"
3251 «x
3252 X
3253 y
3254 Y
3255 z
3256 Zˇ»
3257 "});
3258
3259 // Test reverse_lines()
3260 cx.set_state(indoc! {"
3261 «5
3262 4
3263 3
3264 2
3265 1ˇ»
3266 "});
3267 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3268 cx.assert_editor_state(indoc! {"
3269 «1
3270 2
3271 3
3272 4
3273 5ˇ»
3274 "});
3275
3276 // Skip testing shuffle_line()
3277
3278 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3279 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3280
3281 // Don't manipulate when cursor is on single line, but expand the selection
3282 cx.set_state(indoc! {"
3283 ddˇdd
3284 ccc
3285 bb
3286 a
3287 "});
3288 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3289 cx.assert_editor_state(indoc! {"
3290 «ddddˇ»
3291 ccc
3292 bb
3293 a
3294 "});
3295
3296 // Basic manipulate case
3297 // Start selection moves to column 0
3298 // End of selection shrinks to fit shorter line
3299 cx.set_state(indoc! {"
3300 dd«d
3301 ccc
3302 bb
3303 aaaaaˇ»
3304 "});
3305 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3306 cx.assert_editor_state(indoc! {"
3307 «aaaaa
3308 bb
3309 ccc
3310 dddˇ»
3311 "});
3312
3313 // Manipulate case with newlines
3314 cx.set_state(indoc! {"
3315 dd«d
3316 ccc
3317
3318 bb
3319 aaaaa
3320
3321 ˇ»
3322 "});
3323 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3324 cx.assert_editor_state(indoc! {"
3325 «
3326
3327 aaaaa
3328 bb
3329 ccc
3330 dddˇ»
3331
3332 "});
3333
3334 // Adding new line
3335 cx.set_state(indoc! {"
3336 aa«a
3337 bbˇ»b
3338 "});
3339 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3340 cx.assert_editor_state(indoc! {"
3341 «aaa
3342 bbb
3343 added_lineˇ»
3344 "});
3345
3346 // Removing line
3347 cx.set_state(indoc! {"
3348 aa«a
3349 bbbˇ»
3350 "});
3351 cx.update_editor(|e, cx| {
3352 e.manipulate_lines(cx, |lines| {
3353 lines.pop();
3354 })
3355 });
3356 cx.assert_editor_state(indoc! {"
3357 «aaaˇ»
3358 "});
3359
3360 // Removing all lines
3361 cx.set_state(indoc! {"
3362 aa«a
3363 bbbˇ»
3364 "});
3365 cx.update_editor(|e, cx| {
3366 e.manipulate_lines(cx, |lines| {
3367 lines.drain(..);
3368 })
3369 });
3370 cx.assert_editor_state(indoc! {"
3371 ˇ
3372 "});
3373}
3374
3375#[gpui::test]
3376async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3377 init_test(cx, |_| {});
3378
3379 let mut cx = EditorTestContext::new(cx).await;
3380
3381 // Consider continuous selection as single selection
3382 cx.set_state(indoc! {"
3383 Aaa«aa
3384 cˇ»c«c
3385 bb
3386 aaaˇ»aa
3387 "});
3388 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3389 cx.assert_editor_state(indoc! {"
3390 «Aaaaa
3391 ccc
3392 bb
3393 aaaaaˇ»
3394 "});
3395
3396 cx.set_state(indoc! {"
3397 Aaa«aa
3398 cˇ»c«c
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «Aaaaa
3405 ccc
3406 bbˇ»
3407 "});
3408
3409 // Consider non continuous selection as distinct dedup operations
3410 cx.set_state(indoc! {"
3411 «aaaaa
3412 bb
3413 aaaaa
3414 aaaaaˇ»
3415
3416 aaa«aaˇ»
3417 "});
3418 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3419 cx.assert_editor_state(indoc! {"
3420 «aaaaa
3421 bbˇ»
3422
3423 «aaaaaˇ»
3424 "});
3425}
3426
3427#[gpui::test]
3428async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3429 init_test(cx, |_| {});
3430
3431 let mut cx = EditorTestContext::new(cx).await;
3432
3433 cx.set_state(indoc! {"
3434 «Aaa
3435 aAa
3436 Aaaˇ»
3437 "});
3438 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3439 cx.assert_editor_state(indoc! {"
3440 «Aaa
3441 aAaˇ»
3442 "});
3443
3444 cx.set_state(indoc! {"
3445 «Aaa
3446 aAa
3447 aaAˇ»
3448 "});
3449 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3450 cx.assert_editor_state(indoc! {"
3451 «Aaaˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Manipulate with multiple selections on a single line
3462 cx.set_state(indoc! {"
3463 dd«dd
3464 cˇ»c«c
3465 bb
3466 aaaˇ»aa
3467 "});
3468 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3469 cx.assert_editor_state(indoc! {"
3470 «aaaaa
3471 bb
3472 ccc
3473 ddddˇ»
3474 "});
3475
3476 // Manipulate with multiple disjoin selections
3477 cx.set_state(indoc! {"
3478 5«
3479 4
3480 3
3481 2
3482 1ˇ»
3483
3484 dd«dd
3485 ccc
3486 bb
3487 aaaˇ»aa
3488 "});
3489 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3490 cx.assert_editor_state(indoc! {"
3491 «1
3492 2
3493 3
3494 4
3495 5ˇ»
3496
3497 «aaaaa
3498 bb
3499 ccc
3500 ddddˇ»
3501 "});
3502
3503 // Adding lines on each selection
3504 cx.set_state(indoc! {"
3505 2«
3506 1ˇ»
3507
3508 bb«bb
3509 aaaˇ»aa
3510 "});
3511 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3512 cx.assert_editor_state(indoc! {"
3513 «2
3514 1
3515 added lineˇ»
3516
3517 «bbbb
3518 aaaaa
3519 added lineˇ»
3520 "});
3521
3522 // Removing lines on each selection
3523 cx.set_state(indoc! {"
3524 2«
3525 1ˇ»
3526
3527 bb«bb
3528 aaaˇ»aa
3529 "});
3530 cx.update_editor(|e, cx| {
3531 e.manipulate_lines(cx, |lines| {
3532 lines.pop();
3533 })
3534 });
3535 cx.assert_editor_state(indoc! {"
3536 «2ˇ»
3537
3538 «bbbbˇ»
3539 "});
3540}
3541
3542#[gpui::test]
3543async fn test_manipulate_text(cx: &mut TestAppContext) {
3544 init_test(cx, |_| {});
3545
3546 let mut cx = EditorTestContext::new(cx).await;
3547
3548 // Test convert_to_upper_case()
3549 cx.set_state(indoc! {"
3550 «hello worldˇ»
3551 "});
3552 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3553 cx.assert_editor_state(indoc! {"
3554 «HELLO WORLDˇ»
3555 "});
3556
3557 // Test convert_to_lower_case()
3558 cx.set_state(indoc! {"
3559 «HELLO WORLDˇ»
3560 "});
3561 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3562 cx.assert_editor_state(indoc! {"
3563 «hello worldˇ»
3564 "});
3565
3566 // Test multiple line, single selection case
3567 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3568 cx.set_state(indoc! {"
3569 «The quick brown
3570 fox jumps over
3571 the lazy dogˇ»
3572 "});
3573 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3574 cx.assert_editor_state(indoc! {"
3575 «The Quick Brown
3576 Fox Jumps Over
3577 The Lazy Dogˇ»
3578 "});
3579
3580 // Test multiple line, single selection case
3581 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3582 cx.set_state(indoc! {"
3583 «The quick brown
3584 fox jumps over
3585 the lazy dogˇ»
3586 "});
3587 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3588 cx.assert_editor_state(indoc! {"
3589 «TheQuickBrown
3590 FoxJumpsOver
3591 TheLazyDogˇ»
3592 "});
3593
3594 // From here on out, test more complex cases of manipulate_text()
3595
3596 // Test no selection case - should affect words cursors are in
3597 // Cursor at beginning, middle, and end of word
3598 cx.set_state(indoc! {"
3599 ˇhello big beauˇtiful worldˇ
3600 "});
3601 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3602 cx.assert_editor_state(indoc! {"
3603 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3604 "});
3605
3606 // Test multiple selections on a single line and across multiple lines
3607 cx.set_state(indoc! {"
3608 «Theˇ» quick «brown
3609 foxˇ» jumps «overˇ»
3610 the «lazyˇ» dog
3611 "});
3612 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3613 cx.assert_editor_state(indoc! {"
3614 «THEˇ» quick «BROWN
3615 FOXˇ» jumps «OVERˇ»
3616 the «LAZYˇ» dog
3617 "});
3618
3619 // Test case where text length grows
3620 cx.set_state(indoc! {"
3621 «tschüߡ»
3622 "});
3623 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3624 cx.assert_editor_state(indoc! {"
3625 «TSCHÜSSˇ»
3626 "});
3627
3628 // Test to make sure we don't crash when text shrinks
3629 cx.set_state(indoc! {"
3630 aaa_bbbˇ
3631 "});
3632 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3633 cx.assert_editor_state(indoc! {"
3634 «aaaBbbˇ»
3635 "});
3636
3637 // Test to make sure we all aware of the fact that each word can grow and shrink
3638 // Final selections should be aware of this fact
3639 cx.set_state(indoc! {"
3640 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3641 "});
3642 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3643 cx.assert_editor_state(indoc! {"
3644 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3645 "});
3646
3647 cx.set_state(indoc! {"
3648 «hElLo, WoRld!ˇ»
3649 "});
3650 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3651 cx.assert_editor_state(indoc! {"
3652 «HeLlO, wOrLD!ˇ»
3653 "});
3654}
3655
3656#[gpui::test]
3657fn test_duplicate_line(cx: &mut TestAppContext) {
3658 init_test(cx, |_| {});
3659
3660 let view = cx.add_window(|cx| {
3661 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3662 build_editor(buffer, cx)
3663 });
3664 _ = view.update(cx, |view, cx| {
3665 view.change_selections(None, cx, |s| {
3666 s.select_display_ranges([
3667 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3668 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3669 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3670 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3671 ])
3672 });
3673 view.duplicate_line_down(&DuplicateLineDown, cx);
3674 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3675 assert_eq!(
3676 view.selections.display_ranges(cx),
3677 vec![
3678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3679 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3680 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3681 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3682 ]
3683 );
3684 });
3685
3686 let view = cx.add_window(|cx| {
3687 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3688 build_editor(buffer, cx)
3689 });
3690 _ = view.update(cx, |view, cx| {
3691 view.change_selections(None, cx, |s| {
3692 s.select_display_ranges([
3693 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3694 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3695 ])
3696 });
3697 view.duplicate_line_down(&DuplicateLineDown, cx);
3698 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3699 assert_eq!(
3700 view.selections.display_ranges(cx),
3701 vec![
3702 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3703 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3704 ]
3705 );
3706 });
3707
3708 // With `move_upwards` the selections stay in place, except for
3709 // the lines inserted above them
3710 let view = cx.add_window(|cx| {
3711 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3712 build_editor(buffer, cx)
3713 });
3714 _ = view.update(cx, |view, cx| {
3715 view.change_selections(None, cx, |s| {
3716 s.select_display_ranges([
3717 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3718 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3719 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3720 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3721 ])
3722 });
3723 view.duplicate_line_up(&DuplicateLineUp, cx);
3724 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3729 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3730 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3731 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3732 ]
3733 );
3734 });
3735
3736 let view = cx.add_window(|cx| {
3737 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3738 build_editor(buffer, cx)
3739 });
3740 _ = view.update(cx, |view, cx| {
3741 view.change_selections(None, cx, |s| {
3742 s.select_display_ranges([
3743 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3744 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3745 ])
3746 });
3747 view.duplicate_line_up(&DuplicateLineUp, cx);
3748 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3749 assert_eq!(
3750 view.selections.display_ranges(cx),
3751 vec![
3752 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3753 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3754 ]
3755 );
3756 });
3757}
3758
3759#[gpui::test]
3760fn test_move_line_up_down(cx: &mut TestAppContext) {
3761 init_test(cx, |_| {});
3762
3763 let view = cx.add_window(|cx| {
3764 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3765 build_editor(buffer, cx)
3766 });
3767 _ = view.update(cx, |view, cx| {
3768 view.fold_ranges(
3769 vec![
3770 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3771 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3772 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3773 ],
3774 true,
3775 cx,
3776 );
3777 view.change_selections(None, cx, |s| {
3778 s.select_display_ranges([
3779 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3780 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3781 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3782 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3783 ])
3784 });
3785 assert_eq!(
3786 view.display_text(cx),
3787 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3788 );
3789
3790 view.move_line_up(&MoveLineUp, cx);
3791 assert_eq!(
3792 view.display_text(cx),
3793 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3794 );
3795 assert_eq!(
3796 view.selections.display_ranges(cx),
3797 vec![
3798 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3799 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3800 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3801 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3802 ]
3803 );
3804 });
3805
3806 _ = view.update(cx, |view, cx| {
3807 view.move_line_down(&MoveLineDown, cx);
3808 assert_eq!(
3809 view.display_text(cx),
3810 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3811 );
3812 assert_eq!(
3813 view.selections.display_ranges(cx),
3814 vec![
3815 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3816 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3817 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3818 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3819 ]
3820 );
3821 });
3822
3823 _ = view.update(cx, |view, cx| {
3824 view.move_line_down(&MoveLineDown, cx);
3825 assert_eq!(
3826 view.display_text(cx),
3827 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3828 );
3829 assert_eq!(
3830 view.selections.display_ranges(cx),
3831 vec![
3832 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3833 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3834 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3835 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3836 ]
3837 );
3838 });
3839
3840 _ = view.update(cx, |view, cx| {
3841 view.move_line_up(&MoveLineUp, cx);
3842 assert_eq!(
3843 view.display_text(cx),
3844 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3845 );
3846 assert_eq!(
3847 view.selections.display_ranges(cx),
3848 vec![
3849 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3850 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3851 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3852 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3853 ]
3854 );
3855 });
3856}
3857
3858#[gpui::test]
3859fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3860 init_test(cx, |_| {});
3861
3862 let editor = cx.add_window(|cx| {
3863 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3864 build_editor(buffer, cx)
3865 });
3866 _ = editor.update(cx, |editor, cx| {
3867 let snapshot = editor.buffer.read(cx).snapshot(cx);
3868 editor.insert_blocks(
3869 [BlockProperties {
3870 style: BlockStyle::Fixed,
3871 position: snapshot.anchor_after(Point::new(2, 0)),
3872 disposition: BlockDisposition::Below,
3873 height: 1,
3874 render: Box::new(|_| div().into_any()),
3875 priority: 0,
3876 }],
3877 Some(Autoscroll::fit()),
3878 cx,
3879 );
3880 editor.change_selections(None, cx, |s| {
3881 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3882 });
3883 editor.move_line_down(&MoveLineDown, cx);
3884 });
3885}
3886
3887#[gpui::test]
3888fn test_transpose(cx: &mut TestAppContext) {
3889 init_test(cx, |_| {});
3890
3891 _ = cx.add_window(|cx| {
3892 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3893 editor.set_style(EditorStyle::default(), cx);
3894 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3895 editor.transpose(&Default::default(), cx);
3896 assert_eq!(editor.text(cx), "bac");
3897 assert_eq!(editor.selections.ranges(cx), [2..2]);
3898
3899 editor.transpose(&Default::default(), cx);
3900 assert_eq!(editor.text(cx), "bca");
3901 assert_eq!(editor.selections.ranges(cx), [3..3]);
3902
3903 editor.transpose(&Default::default(), cx);
3904 assert_eq!(editor.text(cx), "bac");
3905 assert_eq!(editor.selections.ranges(cx), [3..3]);
3906
3907 editor
3908 });
3909
3910 _ = cx.add_window(|cx| {
3911 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3912 editor.set_style(EditorStyle::default(), cx);
3913 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3914 editor.transpose(&Default::default(), cx);
3915 assert_eq!(editor.text(cx), "acb\nde");
3916 assert_eq!(editor.selections.ranges(cx), [3..3]);
3917
3918 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3919 editor.transpose(&Default::default(), cx);
3920 assert_eq!(editor.text(cx), "acbd\ne");
3921 assert_eq!(editor.selections.ranges(cx), [5..5]);
3922
3923 editor.transpose(&Default::default(), cx);
3924 assert_eq!(editor.text(cx), "acbde\n");
3925 assert_eq!(editor.selections.ranges(cx), [6..6]);
3926
3927 editor.transpose(&Default::default(), cx);
3928 assert_eq!(editor.text(cx), "acbd\ne");
3929 assert_eq!(editor.selections.ranges(cx), [6..6]);
3930
3931 editor
3932 });
3933
3934 _ = cx.add_window(|cx| {
3935 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3936 editor.set_style(EditorStyle::default(), cx);
3937 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3938 editor.transpose(&Default::default(), cx);
3939 assert_eq!(editor.text(cx), "bacd\ne");
3940 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3941
3942 editor.transpose(&Default::default(), cx);
3943 assert_eq!(editor.text(cx), "bcade\n");
3944 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3945
3946 editor.transpose(&Default::default(), cx);
3947 assert_eq!(editor.text(cx), "bcda\ne");
3948 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3949
3950 editor.transpose(&Default::default(), cx);
3951 assert_eq!(editor.text(cx), "bcade\n");
3952 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3953
3954 editor.transpose(&Default::default(), cx);
3955 assert_eq!(editor.text(cx), "bcaed\n");
3956 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3957
3958 editor
3959 });
3960
3961 _ = cx.add_window(|cx| {
3962 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3963 editor.set_style(EditorStyle::default(), cx);
3964 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3965 editor.transpose(&Default::default(), cx);
3966 assert_eq!(editor.text(cx), "🏀🍐✋");
3967 assert_eq!(editor.selections.ranges(cx), [8..8]);
3968
3969 editor.transpose(&Default::default(), cx);
3970 assert_eq!(editor.text(cx), "🏀✋🍐");
3971 assert_eq!(editor.selections.ranges(cx), [11..11]);
3972
3973 editor.transpose(&Default::default(), cx);
3974 assert_eq!(editor.text(cx), "🏀🍐✋");
3975 assert_eq!(editor.selections.ranges(cx), [11..11]);
3976
3977 editor
3978 });
3979}
3980
3981#[gpui::test]
3982async fn test_rewrap(cx: &mut TestAppContext) {
3983 init_test(cx, |_| {});
3984
3985 let mut cx = EditorTestContext::new(cx).await;
3986
3987 {
3988 let language = Arc::new(Language::new(
3989 LanguageConfig {
3990 line_comments: vec!["// ".into()],
3991 ..LanguageConfig::default()
3992 },
3993 None,
3994 ));
3995 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3996
3997 let unwrapped_text = indoc! {"
3998 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
3999 "};
4000
4001 let wrapped_text = indoc! {"
4002 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4003 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4004 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4005 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4006 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4007 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4008 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4009 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4010 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4011 // porttitor id. Aliquam id accumsan eros.ˇ
4012 "};
4013
4014 cx.set_state(unwrapped_text);
4015 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4016 cx.assert_editor_state(wrapped_text);
4017 }
4018
4019 // Test that rewrapping works inside of a selection
4020 {
4021 let language = Arc::new(Language::new(
4022 LanguageConfig {
4023 line_comments: vec!["// ".into()],
4024 ..LanguageConfig::default()
4025 },
4026 None,
4027 ));
4028 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4029
4030 let unwrapped_text = indoc! {"
4031 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4032 "};
4033
4034 let wrapped_text = indoc! {"
4035 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4036 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4037 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4038 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4039 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4040 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4041 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4042 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4043 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4044 // porttitor id. Aliquam id accumsan eros.ˇ
4045 "};
4046
4047 cx.set_state(unwrapped_text);
4048 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4049 cx.assert_editor_state(wrapped_text);
4050 }
4051
4052 // Test that cursors that expand to the same region are collapsed.
4053 {
4054 let language = Arc::new(Language::new(
4055 LanguageConfig {
4056 line_comments: vec!["// ".into()],
4057 ..LanguageConfig::default()
4058 },
4059 None,
4060 ));
4061 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4062
4063 let unwrapped_text = indoc! {"
4064 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4065 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4066 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4067 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4068 "};
4069
4070 let wrapped_text = indoc! {"
4071 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4072 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4073 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4074 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4075 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4076 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4077 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4078 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4079 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4080 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4081 "};
4082
4083 cx.set_state(unwrapped_text);
4084 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4085 cx.assert_editor_state(wrapped_text);
4086 }
4087
4088 // Test that non-contiguous selections are treated separately.
4089 {
4090 let language = Arc::new(Language::new(
4091 LanguageConfig {
4092 line_comments: vec!["// ".into()],
4093 ..LanguageConfig::default()
4094 },
4095 None,
4096 ));
4097 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4098
4099 let unwrapped_text = indoc! {"
4100 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4101 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4102 //
4103 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4104 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4105 "};
4106
4107 let wrapped_text = indoc! {"
4108 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4109 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4110 // auctor, eu lacinia sapien scelerisque.ˇˇ
4111 //
4112 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4113 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4114 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4115 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4116 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4117 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4118 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4119 "};
4120
4121 cx.set_state(unwrapped_text);
4122 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4123 cx.assert_editor_state(wrapped_text);
4124 }
4125
4126 // Test that different comment prefixes are supported.
4127 {
4128 let language = Arc::new(Language::new(
4129 LanguageConfig {
4130 line_comments: vec!["# ".into()],
4131 ..LanguageConfig::default()
4132 },
4133 None,
4134 ));
4135 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4136
4137 let unwrapped_text = indoc! {"
4138 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4139 "};
4140
4141 let wrapped_text = indoc! {"
4142 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4143 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4144 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4145 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4146 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4147 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4148 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4149 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4150 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4151 # accumsan eros.ˇ
4152 "};
4153
4154 cx.set_state(unwrapped_text);
4155 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4156 cx.assert_editor_state(wrapped_text);
4157 }
4158
4159 // Test that rewrapping is ignored outside of comments in most languages.
4160 {
4161 let language = Arc::new(Language::new(
4162 LanguageConfig {
4163 line_comments: vec!["// ".into(), "/// ".into()],
4164 ..LanguageConfig::default()
4165 },
4166 Some(tree_sitter_rust::LANGUAGE.into()),
4167 ));
4168 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4169
4170 let unwrapped_text = indoc! {"
4171 /// Adds two numbers.
4172 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4173 fn add(a: u32, b: u32) -> u32 {
4174 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4175 }
4176 "};
4177
4178 let wrapped_text = indoc! {"
4179 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4180 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4181 fn add(a: u32, b: u32) -> u32 {
4182 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4183 }
4184 "};
4185
4186 cx.set_state(unwrapped_text);
4187 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4188 cx.assert_editor_state(wrapped_text);
4189 }
4190
4191 // Test that rewrapping works in Markdown and Plain Text languages.
4192 {
4193 let markdown_language = Arc::new(Language::new(
4194 LanguageConfig {
4195 name: "Markdown".into(),
4196 ..LanguageConfig::default()
4197 },
4198 None,
4199 ));
4200 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4201
4202 let unwrapped_text = indoc! {"
4203 # Hello
4204
4205 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4206 "};
4207
4208 let wrapped_text = indoc! {"
4209 # Hello
4210
4211 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4212 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4213 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4214 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4215 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4216 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4217 Integer sit amet scelerisque nisi.ˇ
4218 "};
4219
4220 cx.set_state(unwrapped_text);
4221 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4222 cx.assert_editor_state(wrapped_text);
4223
4224 let plaintext_language = Arc::new(Language::new(
4225 LanguageConfig {
4226 name: "Plain Text".into(),
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4232
4233 let unwrapped_text = indoc! {"
4234 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4235 "};
4236
4237 let wrapped_text = indoc! {"
4238 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4239 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4240 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4241 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4242 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4243 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4244 Integer sit amet scelerisque nisi.ˇ
4245 "};
4246
4247 cx.set_state(unwrapped_text);
4248 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4249 cx.assert_editor_state(wrapped_text);
4250 }
4251
4252 // Test rewrapping unaligned comments in a selection.
4253 {
4254 let language = Arc::new(Language::new(
4255 LanguageConfig {
4256 line_comments: vec!["// ".into(), "/// ".into()],
4257 ..LanguageConfig::default()
4258 },
4259 Some(tree_sitter_rust::LANGUAGE.into()),
4260 ));
4261 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4262
4263 let unwrapped_text = indoc! {"
4264 fn foo() {
4265 if true {
4266 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4267 // Praesent semper egestas tellus id dignissim.ˇ»
4268 do_something();
4269 } else {
4270 //
4271 }
4272 }
4273 "};
4274
4275 let wrapped_text = indoc! {"
4276 fn foo() {
4277 if true {
4278 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4279 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4280 // egestas tellus id dignissim.ˇ
4281 do_something();
4282 } else {
4283 //
4284 }
4285 }
4286 "};
4287
4288 cx.set_state(unwrapped_text);
4289 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4290 cx.assert_editor_state(wrapped_text);
4291
4292 let unwrapped_text = indoc! {"
4293 fn foo() {
4294 if true {
4295 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4296 // Praesent semper egestas tellus id dignissim.»
4297 do_something();
4298 } else {
4299 //
4300 }
4301
4302 }
4303 "};
4304
4305 let wrapped_text = indoc! {"
4306 fn foo() {
4307 if true {
4308 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4309 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4310 // egestas tellus id dignissim.ˇ
4311 do_something();
4312 } else {
4313 //
4314 }
4315
4316 }
4317 "};
4318
4319 cx.set_state(unwrapped_text);
4320 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4321 cx.assert_editor_state(wrapped_text);
4322 }
4323}
4324
4325#[gpui::test]
4326async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4327 init_test(cx, |_| {});
4328
4329 let mut cx = EditorTestContext::new(cx).await;
4330
4331 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4332 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4333 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4334
4335 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4336 cx.set_state("two ˇfour ˇsix ˇ");
4337 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4338 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4339
4340 // Paste again but with only two cursors. Since the number of cursors doesn't
4341 // match the number of slices in the clipboard, the entire clipboard text
4342 // is pasted at each cursor.
4343 cx.set_state("ˇtwo one✅ four three six five ˇ");
4344 cx.update_editor(|e, cx| {
4345 e.handle_input("( ", cx);
4346 e.paste(&Paste, cx);
4347 e.handle_input(") ", cx);
4348 });
4349 cx.assert_editor_state(
4350 &([
4351 "( one✅ ",
4352 "three ",
4353 "five ) ˇtwo one✅ four three six five ( one✅ ",
4354 "three ",
4355 "five ) ˇ",
4356 ]
4357 .join("\n")),
4358 );
4359
4360 // Cut with three selections, one of which is full-line.
4361 cx.set_state(indoc! {"
4362 1«2ˇ»3
4363 4ˇ567
4364 «8ˇ»9"});
4365 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4366 cx.assert_editor_state(indoc! {"
4367 1ˇ3
4368 ˇ9"});
4369
4370 // Paste with three selections, noticing how the copied selection that was full-line
4371 // gets inserted before the second cursor.
4372 cx.set_state(indoc! {"
4373 1ˇ3
4374 9ˇ
4375 «oˇ»ne"});
4376 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4377 cx.assert_editor_state(indoc! {"
4378 12ˇ3
4379 4567
4380 9ˇ
4381 8ˇne"});
4382
4383 // Copy with a single cursor only, which writes the whole line into the clipboard.
4384 cx.set_state(indoc! {"
4385 The quick brown
4386 fox juˇmps over
4387 the lazy dog"});
4388 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4389 assert_eq!(
4390 cx.read_from_clipboard()
4391 .and_then(|item| item.text().as_deref().map(str::to_string)),
4392 Some("fox jumps over\n".to_string())
4393 );
4394
4395 // Paste with three selections, noticing how the copied full-line selection is inserted
4396 // before the empty selections but replaces the selection that is non-empty.
4397 cx.set_state(indoc! {"
4398 Tˇhe quick brown
4399 «foˇ»x jumps over
4400 tˇhe lazy dog"});
4401 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4402 cx.assert_editor_state(indoc! {"
4403 fox jumps over
4404 Tˇhe quick brown
4405 fox jumps over
4406 ˇx jumps over
4407 fox jumps over
4408 tˇhe lazy dog"});
4409}
4410
4411#[gpui::test]
4412async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4413 init_test(cx, |_| {});
4414
4415 let mut cx = EditorTestContext::new(cx).await;
4416 let language = Arc::new(Language::new(
4417 LanguageConfig::default(),
4418 Some(tree_sitter_rust::LANGUAGE.into()),
4419 ));
4420 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4421
4422 // Cut an indented block, without the leading whitespace.
4423 cx.set_state(indoc! {"
4424 const a: B = (
4425 c(),
4426 «d(
4427 e,
4428 f
4429 )ˇ»
4430 );
4431 "});
4432 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4433 cx.assert_editor_state(indoc! {"
4434 const a: B = (
4435 c(),
4436 ˇ
4437 );
4438 "});
4439
4440 // Paste it at the same position.
4441 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4442 cx.assert_editor_state(indoc! {"
4443 const a: B = (
4444 c(),
4445 d(
4446 e,
4447 f
4448 )ˇ
4449 );
4450 "});
4451
4452 // Paste it at a line with a lower indent level.
4453 cx.set_state(indoc! {"
4454 ˇ
4455 const a: B = (
4456 c(),
4457 );
4458 "});
4459 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4460 cx.assert_editor_state(indoc! {"
4461 d(
4462 e,
4463 f
4464 )ˇ
4465 const a: B = (
4466 c(),
4467 );
4468 "});
4469
4470 // Cut an indented block, with the leading whitespace.
4471 cx.set_state(indoc! {"
4472 const a: B = (
4473 c(),
4474 « d(
4475 e,
4476 f
4477 )
4478 ˇ»);
4479 "});
4480 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4481 cx.assert_editor_state(indoc! {"
4482 const a: B = (
4483 c(),
4484 ˇ);
4485 "});
4486
4487 // Paste it at the same position.
4488 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4489 cx.assert_editor_state(indoc! {"
4490 const a: B = (
4491 c(),
4492 d(
4493 e,
4494 f
4495 )
4496 ˇ);
4497 "});
4498
4499 // Paste it at a line with a higher indent level.
4500 cx.set_state(indoc! {"
4501 const a: B = (
4502 c(),
4503 d(
4504 e,
4505 fˇ
4506 )
4507 );
4508 "});
4509 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4510 cx.assert_editor_state(indoc! {"
4511 const a: B = (
4512 c(),
4513 d(
4514 e,
4515 f d(
4516 e,
4517 f
4518 )
4519 ˇ
4520 )
4521 );
4522 "});
4523}
4524
4525#[gpui::test]
4526fn test_select_all(cx: &mut TestAppContext) {
4527 init_test(cx, |_| {});
4528
4529 let view = cx.add_window(|cx| {
4530 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4531 build_editor(buffer, cx)
4532 });
4533 _ = view.update(cx, |view, cx| {
4534 view.select_all(&SelectAll, cx);
4535 assert_eq!(
4536 view.selections.display_ranges(cx),
4537 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4538 );
4539 });
4540}
4541
4542#[gpui::test]
4543fn test_select_line(cx: &mut TestAppContext) {
4544 init_test(cx, |_| {});
4545
4546 let view = cx.add_window(|cx| {
4547 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4548 build_editor(buffer, cx)
4549 });
4550 _ = view.update(cx, |view, cx| {
4551 view.change_selections(None, cx, |s| {
4552 s.select_display_ranges([
4553 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4554 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4555 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4556 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4557 ])
4558 });
4559 view.select_line(&SelectLine, cx);
4560 assert_eq!(
4561 view.selections.display_ranges(cx),
4562 vec![
4563 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4564 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4565 ]
4566 );
4567 });
4568
4569 _ = view.update(cx, |view, cx| {
4570 view.select_line(&SelectLine, cx);
4571 assert_eq!(
4572 view.selections.display_ranges(cx),
4573 vec![
4574 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4575 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4576 ]
4577 );
4578 });
4579
4580 _ = view.update(cx, |view, cx| {
4581 view.select_line(&SelectLine, cx);
4582 assert_eq!(
4583 view.selections.display_ranges(cx),
4584 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4585 );
4586 });
4587}
4588
4589#[gpui::test]
4590fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4591 init_test(cx, |_| {});
4592
4593 let view = cx.add_window(|cx| {
4594 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4595 build_editor(buffer, cx)
4596 });
4597 _ = view.update(cx, |view, cx| {
4598 view.fold_ranges(
4599 vec![
4600 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4601 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4602 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4603 ],
4604 true,
4605 cx,
4606 );
4607 view.change_selections(None, cx, |s| {
4608 s.select_display_ranges([
4609 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4610 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4611 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4612 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4613 ])
4614 });
4615 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4616 });
4617
4618 _ = view.update(cx, |view, cx| {
4619 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4620 assert_eq!(
4621 view.display_text(cx),
4622 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4623 );
4624 assert_eq!(
4625 view.selections.display_ranges(cx),
4626 [
4627 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4628 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4629 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4630 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4631 ]
4632 );
4633 });
4634
4635 _ = view.update(cx, |view, cx| {
4636 view.change_selections(None, cx, |s| {
4637 s.select_display_ranges([
4638 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4639 ])
4640 });
4641 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4642 assert_eq!(
4643 view.display_text(cx),
4644 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4645 );
4646 assert_eq!(
4647 view.selections.display_ranges(cx),
4648 [
4649 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4650 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4651 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4652 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4653 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4654 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4655 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4656 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4657 ]
4658 );
4659 });
4660}
4661
4662#[gpui::test]
4663async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4664 init_test(cx, |_| {});
4665
4666 let mut cx = EditorTestContext::new(cx).await;
4667
4668 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4669 cx.set_state(indoc!(
4670 r#"abc
4671 defˇghi
4672
4673 jk
4674 nlmo
4675 "#
4676 ));
4677
4678 cx.update_editor(|editor, cx| {
4679 editor.add_selection_above(&Default::default(), cx);
4680 });
4681
4682 cx.assert_editor_state(indoc!(
4683 r#"abcˇ
4684 defˇghi
4685
4686 jk
4687 nlmo
4688 "#
4689 ));
4690
4691 cx.update_editor(|editor, cx| {
4692 editor.add_selection_above(&Default::default(), cx);
4693 });
4694
4695 cx.assert_editor_state(indoc!(
4696 r#"abcˇ
4697 defˇghi
4698
4699 jk
4700 nlmo
4701 "#
4702 ));
4703
4704 cx.update_editor(|view, cx| {
4705 view.add_selection_below(&Default::default(), cx);
4706 });
4707
4708 cx.assert_editor_state(indoc!(
4709 r#"abc
4710 defˇghi
4711
4712 jk
4713 nlmo
4714 "#
4715 ));
4716
4717 cx.update_editor(|view, cx| {
4718 view.undo_selection(&Default::default(), cx);
4719 });
4720
4721 cx.assert_editor_state(indoc!(
4722 r#"abcˇ
4723 defˇghi
4724
4725 jk
4726 nlmo
4727 "#
4728 ));
4729
4730 cx.update_editor(|view, cx| {
4731 view.redo_selection(&Default::default(), cx);
4732 });
4733
4734 cx.assert_editor_state(indoc!(
4735 r#"abc
4736 defˇghi
4737
4738 jk
4739 nlmo
4740 "#
4741 ));
4742
4743 cx.update_editor(|view, cx| {
4744 view.add_selection_below(&Default::default(), cx);
4745 });
4746
4747 cx.assert_editor_state(indoc!(
4748 r#"abc
4749 defˇghi
4750
4751 jk
4752 nlmˇo
4753 "#
4754 ));
4755
4756 cx.update_editor(|view, cx| {
4757 view.add_selection_below(&Default::default(), cx);
4758 });
4759
4760 cx.assert_editor_state(indoc!(
4761 r#"abc
4762 defˇghi
4763
4764 jk
4765 nlmˇo
4766 "#
4767 ));
4768
4769 // change selections
4770 cx.set_state(indoc!(
4771 r#"abc
4772 def«ˇg»hi
4773
4774 jk
4775 nlmo
4776 "#
4777 ));
4778
4779 cx.update_editor(|view, cx| {
4780 view.add_selection_below(&Default::default(), cx);
4781 });
4782
4783 cx.assert_editor_state(indoc!(
4784 r#"abc
4785 def«ˇg»hi
4786
4787 jk
4788 nlm«ˇo»
4789 "#
4790 ));
4791
4792 cx.update_editor(|view, cx| {
4793 view.add_selection_below(&Default::default(), cx);
4794 });
4795
4796 cx.assert_editor_state(indoc!(
4797 r#"abc
4798 def«ˇg»hi
4799
4800 jk
4801 nlm«ˇo»
4802 "#
4803 ));
4804
4805 cx.update_editor(|view, cx| {
4806 view.add_selection_above(&Default::default(), cx);
4807 });
4808
4809 cx.assert_editor_state(indoc!(
4810 r#"abc
4811 def«ˇg»hi
4812
4813 jk
4814 nlmo
4815 "#
4816 ));
4817
4818 cx.update_editor(|view, cx| {
4819 view.add_selection_above(&Default::default(), cx);
4820 });
4821
4822 cx.assert_editor_state(indoc!(
4823 r#"abc
4824 def«ˇg»hi
4825
4826 jk
4827 nlmo
4828 "#
4829 ));
4830
4831 // Change selections again
4832 cx.set_state(indoc!(
4833 r#"a«bc
4834 defgˇ»hi
4835
4836 jk
4837 nlmo
4838 "#
4839 ));
4840
4841 cx.update_editor(|view, cx| {
4842 view.add_selection_below(&Default::default(), cx);
4843 });
4844
4845 cx.assert_editor_state(indoc!(
4846 r#"a«bcˇ»
4847 d«efgˇ»hi
4848
4849 j«kˇ»
4850 nlmo
4851 "#
4852 ));
4853
4854 cx.update_editor(|view, cx| {
4855 view.add_selection_below(&Default::default(), cx);
4856 });
4857 cx.assert_editor_state(indoc!(
4858 r#"a«bcˇ»
4859 d«efgˇ»hi
4860
4861 j«kˇ»
4862 n«lmoˇ»
4863 "#
4864 ));
4865 cx.update_editor(|view, cx| {
4866 view.add_selection_above(&Default::default(), cx);
4867 });
4868
4869 cx.assert_editor_state(indoc!(
4870 r#"a«bcˇ»
4871 d«efgˇ»hi
4872
4873 j«kˇ»
4874 nlmo
4875 "#
4876 ));
4877
4878 // Change selections again
4879 cx.set_state(indoc!(
4880 r#"abc
4881 d«ˇefghi
4882
4883 jk
4884 nlm»o
4885 "#
4886 ));
4887
4888 cx.update_editor(|view, cx| {
4889 view.add_selection_above(&Default::default(), cx);
4890 });
4891
4892 cx.assert_editor_state(indoc!(
4893 r#"a«ˇbc»
4894 d«ˇef»ghi
4895
4896 j«ˇk»
4897 n«ˇlm»o
4898 "#
4899 ));
4900
4901 cx.update_editor(|view, cx| {
4902 view.add_selection_below(&Default::default(), cx);
4903 });
4904
4905 cx.assert_editor_state(indoc!(
4906 r#"abc
4907 d«ˇef»ghi
4908
4909 j«ˇk»
4910 n«ˇlm»o
4911 "#
4912 ));
4913}
4914
4915#[gpui::test]
4916async fn test_select_next(cx: &mut gpui::TestAppContext) {
4917 init_test(cx, |_| {});
4918
4919 let mut cx = EditorTestContext::new(cx).await;
4920 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4921
4922 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4923 .unwrap();
4924 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4925
4926 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4927 .unwrap();
4928 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4929
4930 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4931 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4932
4933 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4934 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4935
4936 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4937 .unwrap();
4938 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4939
4940 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4941 .unwrap();
4942 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4943}
4944
4945#[gpui::test]
4946async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4947 init_test(cx, |_| {});
4948
4949 let mut cx = EditorTestContext::new(cx).await;
4950 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4951
4952 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4953 .unwrap();
4954 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4955}
4956
4957#[gpui::test]
4958async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4959 init_test(cx, |_| {});
4960
4961 let mut cx = EditorTestContext::new(cx).await;
4962 cx.set_state(
4963 r#"let foo = 2;
4964lˇet foo = 2;
4965let fooˇ = 2;
4966let foo = 2;
4967let foo = ˇ2;"#,
4968 );
4969
4970 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4971 .unwrap();
4972 cx.assert_editor_state(
4973 r#"let foo = 2;
4974«letˇ» foo = 2;
4975let «fooˇ» = 2;
4976let foo = 2;
4977let foo = «2ˇ»;"#,
4978 );
4979
4980 // noop for multiple selections with different contents
4981 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4982 .unwrap();
4983 cx.assert_editor_state(
4984 r#"let foo = 2;
4985«letˇ» foo = 2;
4986let «fooˇ» = 2;
4987let foo = 2;
4988let foo = «2ˇ»;"#,
4989 );
4990}
4991
4992#[gpui::test]
4993async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4994 init_test(cx, |_| {});
4995
4996 let mut cx =
4997 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
4998
4999 cx.assert_editor_state(indoc! {"
5000 ˇbbb
5001 ccc
5002
5003 bbb
5004 ccc
5005 "});
5006 cx.dispatch_action(SelectPrevious::default());
5007 cx.assert_editor_state(indoc! {"
5008 «bbbˇ»
5009 ccc
5010
5011 bbb
5012 ccc
5013 "});
5014 cx.dispatch_action(SelectPrevious::default());
5015 cx.assert_editor_state(indoc! {"
5016 «bbbˇ»
5017 ccc
5018
5019 «bbbˇ»
5020 ccc
5021 "});
5022}
5023
5024#[gpui::test]
5025async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5026 init_test(cx, |_| {});
5027
5028 let mut cx = EditorTestContext::new(cx).await;
5029 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5030
5031 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5032 .unwrap();
5033 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5034
5035 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5036 .unwrap();
5037 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5038
5039 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5040 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5041
5042 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5043 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5044
5045 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5046 .unwrap();
5047 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5048
5049 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5050 .unwrap();
5051 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5052
5053 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5054 .unwrap();
5055 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5056}
5057
5058#[gpui::test]
5059async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5060 init_test(cx, |_| {});
5061
5062 let mut cx = EditorTestContext::new(cx).await;
5063 cx.set_state(
5064 r#"let foo = 2;
5065lˇet foo = 2;
5066let fooˇ = 2;
5067let foo = 2;
5068let foo = ˇ2;"#,
5069 );
5070
5071 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5072 .unwrap();
5073 cx.assert_editor_state(
5074 r#"let foo = 2;
5075«letˇ» foo = 2;
5076let «fooˇ» = 2;
5077let foo = 2;
5078let foo = «2ˇ»;"#,
5079 );
5080
5081 // noop for multiple selections with different contents
5082 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5083 .unwrap();
5084 cx.assert_editor_state(
5085 r#"let foo = 2;
5086«letˇ» foo = 2;
5087let «fooˇ» = 2;
5088let foo = 2;
5089let foo = «2ˇ»;"#,
5090 );
5091}
5092
5093#[gpui::test]
5094async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5095 init_test(cx, |_| {});
5096
5097 let mut cx = EditorTestContext::new(cx).await;
5098 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5099
5100 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5101 .unwrap();
5102 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5103
5104 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5105 .unwrap();
5106 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5107
5108 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5109 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5110
5111 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5112 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5113
5114 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5115 .unwrap();
5116 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5117
5118 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5119 .unwrap();
5120 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5121}
5122
5123#[gpui::test]
5124async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5125 init_test(cx, |_| {});
5126
5127 let language = Arc::new(Language::new(
5128 LanguageConfig::default(),
5129 Some(tree_sitter_rust::LANGUAGE.into()),
5130 ));
5131
5132 let text = r#"
5133 use mod1::mod2::{mod3, mod4};
5134
5135 fn fn_1(param1: bool, param2: &str) {
5136 let var1 = "text";
5137 }
5138 "#
5139 .unindent();
5140
5141 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5142 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5143 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5144
5145 editor
5146 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5147 .await;
5148
5149 editor.update(cx, |view, cx| {
5150 view.change_selections(None, cx, |s| {
5151 s.select_display_ranges([
5152 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5153 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5154 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5155 ]);
5156 });
5157 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5158 });
5159 editor.update(cx, |editor, cx| {
5160 assert_text_with_selections(
5161 editor,
5162 indoc! {r#"
5163 use mod1::mod2::{mod3, «mod4ˇ»};
5164
5165 fn fn_1«ˇ(param1: bool, param2: &str)» {
5166 let var1 = "«textˇ»";
5167 }
5168 "#},
5169 cx,
5170 );
5171 });
5172
5173 editor.update(cx, |view, cx| {
5174 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5175 });
5176 editor.update(cx, |editor, cx| {
5177 assert_text_with_selections(
5178 editor,
5179 indoc! {r#"
5180 use mod1::mod2::«{mod3, mod4}ˇ»;
5181
5182 «ˇfn fn_1(param1: bool, param2: &str) {
5183 let var1 = "text";
5184 }»
5185 "#},
5186 cx,
5187 );
5188 });
5189
5190 editor.update(cx, |view, cx| {
5191 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5192 });
5193 assert_eq!(
5194 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5195 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5196 );
5197
5198 // Trying to expand the selected syntax node one more time has no effect.
5199 editor.update(cx, |view, cx| {
5200 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5201 });
5202 assert_eq!(
5203 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5204 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5205 );
5206
5207 editor.update(cx, |view, cx| {
5208 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5209 });
5210 editor.update(cx, |editor, cx| {
5211 assert_text_with_selections(
5212 editor,
5213 indoc! {r#"
5214 use mod1::mod2::«{mod3, mod4}ˇ»;
5215
5216 «ˇfn fn_1(param1: bool, param2: &str) {
5217 let var1 = "text";
5218 }»
5219 "#},
5220 cx,
5221 );
5222 });
5223
5224 editor.update(cx, |view, cx| {
5225 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5226 });
5227 editor.update(cx, |editor, cx| {
5228 assert_text_with_selections(
5229 editor,
5230 indoc! {r#"
5231 use mod1::mod2::{mod3, «mod4ˇ»};
5232
5233 fn fn_1«ˇ(param1: bool, param2: &str)» {
5234 let var1 = "«textˇ»";
5235 }
5236 "#},
5237 cx,
5238 );
5239 });
5240
5241 editor.update(cx, |view, cx| {
5242 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5243 });
5244 editor.update(cx, |editor, cx| {
5245 assert_text_with_selections(
5246 editor,
5247 indoc! {r#"
5248 use mod1::mod2::{mod3, mo«ˇ»d4};
5249
5250 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5251 let var1 = "te«ˇ»xt";
5252 }
5253 "#},
5254 cx,
5255 );
5256 });
5257
5258 // Trying to shrink the selected syntax node one more time has no effect.
5259 editor.update(cx, |view, cx| {
5260 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5261 });
5262 editor.update(cx, |editor, cx| {
5263 assert_text_with_selections(
5264 editor,
5265 indoc! {r#"
5266 use mod1::mod2::{mod3, mo«ˇ»d4};
5267
5268 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5269 let var1 = "te«ˇ»xt";
5270 }
5271 "#},
5272 cx,
5273 );
5274 });
5275
5276 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5277 // a fold.
5278 editor.update(cx, |view, cx| {
5279 view.fold_ranges(
5280 vec![
5281 (
5282 Point::new(0, 21)..Point::new(0, 24),
5283 FoldPlaceholder::test(),
5284 ),
5285 (
5286 Point::new(3, 20)..Point::new(3, 22),
5287 FoldPlaceholder::test(),
5288 ),
5289 ],
5290 true,
5291 cx,
5292 );
5293 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5294 });
5295 editor.update(cx, |editor, cx| {
5296 assert_text_with_selections(
5297 editor,
5298 indoc! {r#"
5299 use mod1::mod2::«{mod3, mod4}ˇ»;
5300
5301 fn fn_1«ˇ(param1: bool, param2: &str)» {
5302 «let var1 = "text";ˇ»
5303 }
5304 "#},
5305 cx,
5306 );
5307 });
5308}
5309
5310#[gpui::test]
5311async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5312 init_test(cx, |_| {});
5313
5314 let language = Arc::new(
5315 Language::new(
5316 LanguageConfig {
5317 brackets: BracketPairConfig {
5318 pairs: vec![
5319 BracketPair {
5320 start: "{".to_string(),
5321 end: "}".to_string(),
5322 close: false,
5323 surround: false,
5324 newline: true,
5325 },
5326 BracketPair {
5327 start: "(".to_string(),
5328 end: ")".to_string(),
5329 close: false,
5330 surround: false,
5331 newline: true,
5332 },
5333 ],
5334 ..Default::default()
5335 },
5336 ..Default::default()
5337 },
5338 Some(tree_sitter_rust::LANGUAGE.into()),
5339 )
5340 .with_indents_query(
5341 r#"
5342 (_ "(" ")" @end) @indent
5343 (_ "{" "}" @end) @indent
5344 "#,
5345 )
5346 .unwrap(),
5347 );
5348
5349 let text = "fn a() {}";
5350
5351 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5352 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5353 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5354 editor
5355 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5356 .await;
5357
5358 editor.update(cx, |editor, cx| {
5359 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5360 editor.newline(&Newline, cx);
5361 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5362 assert_eq!(
5363 editor.selections.ranges(cx),
5364 &[
5365 Point::new(1, 4)..Point::new(1, 4),
5366 Point::new(3, 4)..Point::new(3, 4),
5367 Point::new(5, 0)..Point::new(5, 0)
5368 ]
5369 );
5370 });
5371}
5372
5373#[gpui::test]
5374async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5375 init_test(cx, |_| {});
5376
5377 let mut cx = EditorTestContext::new(cx).await;
5378
5379 let language = Arc::new(Language::new(
5380 LanguageConfig {
5381 brackets: BracketPairConfig {
5382 pairs: vec![
5383 BracketPair {
5384 start: "{".to_string(),
5385 end: "}".to_string(),
5386 close: true,
5387 surround: true,
5388 newline: true,
5389 },
5390 BracketPair {
5391 start: "(".to_string(),
5392 end: ")".to_string(),
5393 close: true,
5394 surround: true,
5395 newline: true,
5396 },
5397 BracketPair {
5398 start: "/*".to_string(),
5399 end: " */".to_string(),
5400 close: true,
5401 surround: true,
5402 newline: true,
5403 },
5404 BracketPair {
5405 start: "[".to_string(),
5406 end: "]".to_string(),
5407 close: false,
5408 surround: false,
5409 newline: true,
5410 },
5411 BracketPair {
5412 start: "\"".to_string(),
5413 end: "\"".to_string(),
5414 close: true,
5415 surround: true,
5416 newline: false,
5417 },
5418 BracketPair {
5419 start: "<".to_string(),
5420 end: ">".to_string(),
5421 close: false,
5422 surround: true,
5423 newline: true,
5424 },
5425 ],
5426 ..Default::default()
5427 },
5428 autoclose_before: "})]".to_string(),
5429 ..Default::default()
5430 },
5431 Some(tree_sitter_rust::LANGUAGE.into()),
5432 ));
5433
5434 cx.language_registry().add(language.clone());
5435 cx.update_buffer(|buffer, cx| {
5436 buffer.set_language(Some(language), cx);
5437 });
5438
5439 cx.set_state(
5440 &r#"
5441 🏀ˇ
5442 εˇ
5443 ❤️ˇ
5444 "#
5445 .unindent(),
5446 );
5447
5448 // autoclose multiple nested brackets at multiple cursors
5449 cx.update_editor(|view, cx| {
5450 view.handle_input("{", cx);
5451 view.handle_input("{", cx);
5452 view.handle_input("{", cx);
5453 });
5454 cx.assert_editor_state(
5455 &"
5456 🏀{{{ˇ}}}
5457 ε{{{ˇ}}}
5458 ❤️{{{ˇ}}}
5459 "
5460 .unindent(),
5461 );
5462
5463 // insert a different closing bracket
5464 cx.update_editor(|view, cx| {
5465 view.handle_input(")", cx);
5466 });
5467 cx.assert_editor_state(
5468 &"
5469 🏀{{{)ˇ}}}
5470 ε{{{)ˇ}}}
5471 ❤️{{{)ˇ}}}
5472 "
5473 .unindent(),
5474 );
5475
5476 // skip over the auto-closed brackets when typing a closing bracket
5477 cx.update_editor(|view, cx| {
5478 view.move_right(&MoveRight, cx);
5479 view.handle_input("}", cx);
5480 view.handle_input("}", cx);
5481 view.handle_input("}", cx);
5482 });
5483 cx.assert_editor_state(
5484 &"
5485 🏀{{{)}}}}ˇ
5486 ε{{{)}}}}ˇ
5487 ❤️{{{)}}}}ˇ
5488 "
5489 .unindent(),
5490 );
5491
5492 // autoclose multi-character pairs
5493 cx.set_state(
5494 &"
5495 ˇ
5496 ˇ
5497 "
5498 .unindent(),
5499 );
5500 cx.update_editor(|view, cx| {
5501 view.handle_input("/", cx);
5502 view.handle_input("*", cx);
5503 });
5504 cx.assert_editor_state(
5505 &"
5506 /*ˇ */
5507 /*ˇ */
5508 "
5509 .unindent(),
5510 );
5511
5512 // one cursor autocloses a multi-character pair, one cursor
5513 // does not autoclose.
5514 cx.set_state(
5515 &"
5516 /ˇ
5517 ˇ
5518 "
5519 .unindent(),
5520 );
5521 cx.update_editor(|view, cx| view.handle_input("*", cx));
5522 cx.assert_editor_state(
5523 &"
5524 /*ˇ */
5525 *ˇ
5526 "
5527 .unindent(),
5528 );
5529
5530 // Don't autoclose if the next character isn't whitespace and isn't
5531 // listed in the language's "autoclose_before" section.
5532 cx.set_state("ˇa b");
5533 cx.update_editor(|view, cx| view.handle_input("{", cx));
5534 cx.assert_editor_state("{ˇa b");
5535
5536 // Don't autoclose if `close` is false for the bracket pair
5537 cx.set_state("ˇ");
5538 cx.update_editor(|view, cx| view.handle_input("[", cx));
5539 cx.assert_editor_state("[ˇ");
5540
5541 // Surround with brackets if text is selected
5542 cx.set_state("«aˇ» b");
5543 cx.update_editor(|view, cx| view.handle_input("{", cx));
5544 cx.assert_editor_state("{«aˇ»} b");
5545
5546 // Autclose pair where the start and end characters are the same
5547 cx.set_state("aˇ");
5548 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5549 cx.assert_editor_state("a\"ˇ\"");
5550 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5551 cx.assert_editor_state("a\"\"ˇ");
5552
5553 // Don't autoclose pair if autoclose is disabled
5554 cx.set_state("ˇ");
5555 cx.update_editor(|view, cx| view.handle_input("<", cx));
5556 cx.assert_editor_state("<ˇ");
5557
5558 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5559 cx.set_state("«aˇ» b");
5560 cx.update_editor(|view, cx| view.handle_input("<", cx));
5561 cx.assert_editor_state("<«aˇ»> b");
5562}
5563
5564#[gpui::test]
5565async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5566 init_test(cx, |settings| {
5567 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5568 });
5569
5570 let mut cx = EditorTestContext::new(cx).await;
5571
5572 let language = Arc::new(Language::new(
5573 LanguageConfig {
5574 brackets: BracketPairConfig {
5575 pairs: vec![
5576 BracketPair {
5577 start: "{".to_string(),
5578 end: "}".to_string(),
5579 close: true,
5580 surround: true,
5581 newline: true,
5582 },
5583 BracketPair {
5584 start: "(".to_string(),
5585 end: ")".to_string(),
5586 close: true,
5587 surround: true,
5588 newline: true,
5589 },
5590 BracketPair {
5591 start: "[".to_string(),
5592 end: "]".to_string(),
5593 close: false,
5594 surround: false,
5595 newline: true,
5596 },
5597 ],
5598 ..Default::default()
5599 },
5600 autoclose_before: "})]".to_string(),
5601 ..Default::default()
5602 },
5603 Some(tree_sitter_rust::LANGUAGE.into()),
5604 ));
5605
5606 cx.language_registry().add(language.clone());
5607 cx.update_buffer(|buffer, cx| {
5608 buffer.set_language(Some(language), cx);
5609 });
5610
5611 cx.set_state(
5612 &"
5613 ˇ
5614 ˇ
5615 ˇ
5616 "
5617 .unindent(),
5618 );
5619
5620 // ensure only matching closing brackets are skipped over
5621 cx.update_editor(|view, cx| {
5622 view.handle_input("}", cx);
5623 view.move_left(&MoveLeft, cx);
5624 view.handle_input(")", cx);
5625 view.move_left(&MoveLeft, cx);
5626 });
5627 cx.assert_editor_state(
5628 &"
5629 ˇ)}
5630 ˇ)}
5631 ˇ)}
5632 "
5633 .unindent(),
5634 );
5635
5636 // skip-over closing brackets at multiple cursors
5637 cx.update_editor(|view, cx| {
5638 view.handle_input(")", cx);
5639 view.handle_input("}", cx);
5640 });
5641 cx.assert_editor_state(
5642 &"
5643 )}ˇ
5644 )}ˇ
5645 )}ˇ
5646 "
5647 .unindent(),
5648 );
5649
5650 // ignore non-close brackets
5651 cx.update_editor(|view, cx| {
5652 view.handle_input("]", cx);
5653 view.move_left(&MoveLeft, cx);
5654 view.handle_input("]", cx);
5655 });
5656 cx.assert_editor_state(
5657 &"
5658 )}]ˇ]
5659 )}]ˇ]
5660 )}]ˇ]
5661 "
5662 .unindent(),
5663 );
5664}
5665
5666#[gpui::test]
5667async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5668 init_test(cx, |_| {});
5669
5670 let mut cx = EditorTestContext::new(cx).await;
5671
5672 let html_language = Arc::new(
5673 Language::new(
5674 LanguageConfig {
5675 name: "HTML".into(),
5676 brackets: BracketPairConfig {
5677 pairs: vec![
5678 BracketPair {
5679 start: "<".into(),
5680 end: ">".into(),
5681 close: true,
5682 ..Default::default()
5683 },
5684 BracketPair {
5685 start: "{".into(),
5686 end: "}".into(),
5687 close: true,
5688 ..Default::default()
5689 },
5690 BracketPair {
5691 start: "(".into(),
5692 end: ")".into(),
5693 close: true,
5694 ..Default::default()
5695 },
5696 ],
5697 ..Default::default()
5698 },
5699 autoclose_before: "})]>".into(),
5700 ..Default::default()
5701 },
5702 Some(tree_sitter_html::language()),
5703 )
5704 .with_injection_query(
5705 r#"
5706 (script_element
5707 (raw_text) @content
5708 (#set! "language" "javascript"))
5709 "#,
5710 )
5711 .unwrap(),
5712 );
5713
5714 let javascript_language = Arc::new(Language::new(
5715 LanguageConfig {
5716 name: "JavaScript".into(),
5717 brackets: BracketPairConfig {
5718 pairs: vec![
5719 BracketPair {
5720 start: "/*".into(),
5721 end: " */".into(),
5722 close: true,
5723 ..Default::default()
5724 },
5725 BracketPair {
5726 start: "{".into(),
5727 end: "}".into(),
5728 close: true,
5729 ..Default::default()
5730 },
5731 BracketPair {
5732 start: "(".into(),
5733 end: ")".into(),
5734 close: true,
5735 ..Default::default()
5736 },
5737 ],
5738 ..Default::default()
5739 },
5740 autoclose_before: "})]>".into(),
5741 ..Default::default()
5742 },
5743 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5744 ));
5745
5746 cx.language_registry().add(html_language.clone());
5747 cx.language_registry().add(javascript_language.clone());
5748
5749 cx.update_buffer(|buffer, cx| {
5750 buffer.set_language(Some(html_language), cx);
5751 });
5752
5753 cx.set_state(
5754 &r#"
5755 <body>ˇ
5756 <script>
5757 var x = 1;ˇ
5758 </script>
5759 </body>ˇ
5760 "#
5761 .unindent(),
5762 );
5763
5764 // Precondition: different languages are active at different locations.
5765 cx.update_editor(|editor, cx| {
5766 let snapshot = editor.snapshot(cx);
5767 let cursors = editor.selections.ranges::<usize>(cx);
5768 let languages = cursors
5769 .iter()
5770 .map(|c| snapshot.language_at(c.start).unwrap().name())
5771 .collect::<Vec<_>>();
5772 assert_eq!(
5773 languages,
5774 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5775 );
5776 });
5777
5778 // Angle brackets autoclose in HTML, but not JavaScript.
5779 cx.update_editor(|editor, cx| {
5780 editor.handle_input("<", cx);
5781 editor.handle_input("a", cx);
5782 });
5783 cx.assert_editor_state(
5784 &r#"
5785 <body><aˇ>
5786 <script>
5787 var x = 1;<aˇ
5788 </script>
5789 </body><aˇ>
5790 "#
5791 .unindent(),
5792 );
5793
5794 // Curly braces and parens autoclose in both HTML and JavaScript.
5795 cx.update_editor(|editor, cx| {
5796 editor.handle_input(" b=", cx);
5797 editor.handle_input("{", cx);
5798 editor.handle_input("c", cx);
5799 editor.handle_input("(", cx);
5800 });
5801 cx.assert_editor_state(
5802 &r#"
5803 <body><a b={c(ˇ)}>
5804 <script>
5805 var x = 1;<a b={c(ˇ)}
5806 </script>
5807 </body><a b={c(ˇ)}>
5808 "#
5809 .unindent(),
5810 );
5811
5812 // Brackets that were already autoclosed are skipped.
5813 cx.update_editor(|editor, cx| {
5814 editor.handle_input(")", cx);
5815 editor.handle_input("d", cx);
5816 editor.handle_input("}", cx);
5817 });
5818 cx.assert_editor_state(
5819 &r#"
5820 <body><a b={c()d}ˇ>
5821 <script>
5822 var x = 1;<a b={c()d}ˇ
5823 </script>
5824 </body><a b={c()d}ˇ>
5825 "#
5826 .unindent(),
5827 );
5828 cx.update_editor(|editor, cx| {
5829 editor.handle_input(">", cx);
5830 });
5831 cx.assert_editor_state(
5832 &r#"
5833 <body><a b={c()d}>ˇ
5834 <script>
5835 var x = 1;<a b={c()d}>ˇ
5836 </script>
5837 </body><a b={c()d}>ˇ
5838 "#
5839 .unindent(),
5840 );
5841
5842 // Reset
5843 cx.set_state(
5844 &r#"
5845 <body>ˇ
5846 <script>
5847 var x = 1;ˇ
5848 </script>
5849 </body>ˇ
5850 "#
5851 .unindent(),
5852 );
5853
5854 cx.update_editor(|editor, cx| {
5855 editor.handle_input("<", cx);
5856 });
5857 cx.assert_editor_state(
5858 &r#"
5859 <body><ˇ>
5860 <script>
5861 var x = 1;<ˇ
5862 </script>
5863 </body><ˇ>
5864 "#
5865 .unindent(),
5866 );
5867
5868 // When backspacing, the closing angle brackets are removed.
5869 cx.update_editor(|editor, cx| {
5870 editor.backspace(&Backspace, cx);
5871 });
5872 cx.assert_editor_state(
5873 &r#"
5874 <body>ˇ
5875 <script>
5876 var x = 1;ˇ
5877 </script>
5878 </body>ˇ
5879 "#
5880 .unindent(),
5881 );
5882
5883 // Block comments autoclose in JavaScript, but not HTML.
5884 cx.update_editor(|editor, cx| {
5885 editor.handle_input("/", cx);
5886 editor.handle_input("*", cx);
5887 });
5888 cx.assert_editor_state(
5889 &r#"
5890 <body>/*ˇ
5891 <script>
5892 var x = 1;/*ˇ */
5893 </script>
5894 </body>/*ˇ
5895 "#
5896 .unindent(),
5897 );
5898}
5899
5900#[gpui::test]
5901async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5902 init_test(cx, |_| {});
5903
5904 let mut cx = EditorTestContext::new(cx).await;
5905
5906 let rust_language = Arc::new(
5907 Language::new(
5908 LanguageConfig {
5909 name: "Rust".into(),
5910 brackets: serde_json::from_value(json!([
5911 { "start": "{", "end": "}", "close": true, "newline": true },
5912 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5913 ]))
5914 .unwrap(),
5915 autoclose_before: "})]>".into(),
5916 ..Default::default()
5917 },
5918 Some(tree_sitter_rust::LANGUAGE.into()),
5919 )
5920 .with_override_query("(string_literal) @string")
5921 .unwrap(),
5922 );
5923
5924 cx.language_registry().add(rust_language.clone());
5925 cx.update_buffer(|buffer, cx| {
5926 buffer.set_language(Some(rust_language), cx);
5927 });
5928
5929 cx.set_state(
5930 &r#"
5931 let x = ˇ
5932 "#
5933 .unindent(),
5934 );
5935
5936 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5937 cx.update_editor(|editor, cx| {
5938 editor.handle_input("\"", cx);
5939 });
5940 cx.assert_editor_state(
5941 &r#"
5942 let x = "ˇ"
5943 "#
5944 .unindent(),
5945 );
5946
5947 // Inserting another quotation mark. The cursor moves across the existing
5948 // automatically-inserted quotation mark.
5949 cx.update_editor(|editor, cx| {
5950 editor.handle_input("\"", cx);
5951 });
5952 cx.assert_editor_state(
5953 &r#"
5954 let x = ""ˇ
5955 "#
5956 .unindent(),
5957 );
5958
5959 // Reset
5960 cx.set_state(
5961 &r#"
5962 let x = ˇ
5963 "#
5964 .unindent(),
5965 );
5966
5967 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5968 cx.update_editor(|editor, cx| {
5969 editor.handle_input("\"", cx);
5970 editor.handle_input(" ", cx);
5971 editor.move_left(&Default::default(), cx);
5972 editor.handle_input("\\", cx);
5973 editor.handle_input("\"", cx);
5974 });
5975 cx.assert_editor_state(
5976 &r#"
5977 let x = "\"ˇ "
5978 "#
5979 .unindent(),
5980 );
5981
5982 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5983 // mark. Nothing is inserted.
5984 cx.update_editor(|editor, cx| {
5985 editor.move_right(&Default::default(), cx);
5986 editor.handle_input("\"", cx);
5987 });
5988 cx.assert_editor_state(
5989 &r#"
5990 let x = "\" "ˇ
5991 "#
5992 .unindent(),
5993 );
5994}
5995
5996#[gpui::test]
5997async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5998 init_test(cx, |_| {});
5999
6000 let language = Arc::new(Language::new(
6001 LanguageConfig {
6002 brackets: BracketPairConfig {
6003 pairs: vec![
6004 BracketPair {
6005 start: "{".to_string(),
6006 end: "}".to_string(),
6007 close: true,
6008 surround: true,
6009 newline: true,
6010 },
6011 BracketPair {
6012 start: "/* ".to_string(),
6013 end: "*/".to_string(),
6014 close: true,
6015 surround: true,
6016 ..Default::default()
6017 },
6018 ],
6019 ..Default::default()
6020 },
6021 ..Default::default()
6022 },
6023 Some(tree_sitter_rust::LANGUAGE.into()),
6024 ));
6025
6026 let text = r#"
6027 a
6028 b
6029 c
6030 "#
6031 .unindent();
6032
6033 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6034 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6035 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6036 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6037 .await;
6038
6039 view.update(cx, |view, cx| {
6040 view.change_selections(None, cx, |s| {
6041 s.select_display_ranges([
6042 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6043 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6044 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6045 ])
6046 });
6047
6048 view.handle_input("{", cx);
6049 view.handle_input("{", cx);
6050 view.handle_input("{", cx);
6051 assert_eq!(
6052 view.text(cx),
6053 "
6054 {{{a}}}
6055 {{{b}}}
6056 {{{c}}}
6057 "
6058 .unindent()
6059 );
6060 assert_eq!(
6061 view.selections.display_ranges(cx),
6062 [
6063 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6064 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6065 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6066 ]
6067 );
6068
6069 view.undo(&Undo, cx);
6070 view.undo(&Undo, cx);
6071 view.undo(&Undo, cx);
6072 assert_eq!(
6073 view.text(cx),
6074 "
6075 a
6076 b
6077 c
6078 "
6079 .unindent()
6080 );
6081 assert_eq!(
6082 view.selections.display_ranges(cx),
6083 [
6084 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6085 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6086 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6087 ]
6088 );
6089
6090 // Ensure inserting the first character of a multi-byte bracket pair
6091 // doesn't surround the selections with the bracket.
6092 view.handle_input("/", cx);
6093 assert_eq!(
6094 view.text(cx),
6095 "
6096 /
6097 /
6098 /
6099 "
6100 .unindent()
6101 );
6102 assert_eq!(
6103 view.selections.display_ranges(cx),
6104 [
6105 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6106 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6107 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6108 ]
6109 );
6110
6111 view.undo(&Undo, cx);
6112 assert_eq!(
6113 view.text(cx),
6114 "
6115 a
6116 b
6117 c
6118 "
6119 .unindent()
6120 );
6121 assert_eq!(
6122 view.selections.display_ranges(cx),
6123 [
6124 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6125 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6126 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6127 ]
6128 );
6129
6130 // Ensure inserting the last character of a multi-byte bracket pair
6131 // doesn't surround the selections with the bracket.
6132 view.handle_input("*", cx);
6133 assert_eq!(
6134 view.text(cx),
6135 "
6136 *
6137 *
6138 *
6139 "
6140 .unindent()
6141 );
6142 assert_eq!(
6143 view.selections.display_ranges(cx),
6144 [
6145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6146 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6147 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6148 ]
6149 );
6150 });
6151}
6152
6153#[gpui::test]
6154async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6155 init_test(cx, |_| {});
6156
6157 let language = Arc::new(Language::new(
6158 LanguageConfig {
6159 brackets: BracketPairConfig {
6160 pairs: vec![BracketPair {
6161 start: "{".to_string(),
6162 end: "}".to_string(),
6163 close: true,
6164 surround: true,
6165 newline: true,
6166 }],
6167 ..Default::default()
6168 },
6169 autoclose_before: "}".to_string(),
6170 ..Default::default()
6171 },
6172 Some(tree_sitter_rust::LANGUAGE.into()),
6173 ));
6174
6175 let text = r#"
6176 a
6177 b
6178 c
6179 "#
6180 .unindent();
6181
6182 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6183 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6184 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6185 editor
6186 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6187 .await;
6188
6189 editor.update(cx, |editor, cx| {
6190 editor.change_selections(None, cx, |s| {
6191 s.select_ranges([
6192 Point::new(0, 1)..Point::new(0, 1),
6193 Point::new(1, 1)..Point::new(1, 1),
6194 Point::new(2, 1)..Point::new(2, 1),
6195 ])
6196 });
6197
6198 editor.handle_input("{", cx);
6199 editor.handle_input("{", cx);
6200 editor.handle_input("_", cx);
6201 assert_eq!(
6202 editor.text(cx),
6203 "
6204 a{{_}}
6205 b{{_}}
6206 c{{_}}
6207 "
6208 .unindent()
6209 );
6210 assert_eq!(
6211 editor.selections.ranges::<Point>(cx),
6212 [
6213 Point::new(0, 4)..Point::new(0, 4),
6214 Point::new(1, 4)..Point::new(1, 4),
6215 Point::new(2, 4)..Point::new(2, 4)
6216 ]
6217 );
6218
6219 editor.backspace(&Default::default(), cx);
6220 editor.backspace(&Default::default(), cx);
6221 assert_eq!(
6222 editor.text(cx),
6223 "
6224 a{}
6225 b{}
6226 c{}
6227 "
6228 .unindent()
6229 );
6230 assert_eq!(
6231 editor.selections.ranges::<Point>(cx),
6232 [
6233 Point::new(0, 2)..Point::new(0, 2),
6234 Point::new(1, 2)..Point::new(1, 2),
6235 Point::new(2, 2)..Point::new(2, 2)
6236 ]
6237 );
6238
6239 editor.delete_to_previous_word_start(&Default::default(), cx);
6240 assert_eq!(
6241 editor.text(cx),
6242 "
6243 a
6244 b
6245 c
6246 "
6247 .unindent()
6248 );
6249 assert_eq!(
6250 editor.selections.ranges::<Point>(cx),
6251 [
6252 Point::new(0, 1)..Point::new(0, 1),
6253 Point::new(1, 1)..Point::new(1, 1),
6254 Point::new(2, 1)..Point::new(2, 1)
6255 ]
6256 );
6257 });
6258}
6259
6260#[gpui::test]
6261async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6262 init_test(cx, |settings| {
6263 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6264 });
6265
6266 let mut cx = EditorTestContext::new(cx).await;
6267
6268 let language = Arc::new(Language::new(
6269 LanguageConfig {
6270 brackets: BracketPairConfig {
6271 pairs: vec![
6272 BracketPair {
6273 start: "{".to_string(),
6274 end: "}".to_string(),
6275 close: true,
6276 surround: true,
6277 newline: true,
6278 },
6279 BracketPair {
6280 start: "(".to_string(),
6281 end: ")".to_string(),
6282 close: true,
6283 surround: true,
6284 newline: true,
6285 },
6286 BracketPair {
6287 start: "[".to_string(),
6288 end: "]".to_string(),
6289 close: false,
6290 surround: true,
6291 newline: true,
6292 },
6293 ],
6294 ..Default::default()
6295 },
6296 autoclose_before: "})]".to_string(),
6297 ..Default::default()
6298 },
6299 Some(tree_sitter_rust::LANGUAGE.into()),
6300 ));
6301
6302 cx.language_registry().add(language.clone());
6303 cx.update_buffer(|buffer, cx| {
6304 buffer.set_language(Some(language), cx);
6305 });
6306
6307 cx.set_state(
6308 &"
6309 {(ˇ)}
6310 [[ˇ]]
6311 {(ˇ)}
6312 "
6313 .unindent(),
6314 );
6315
6316 cx.update_editor(|view, cx| {
6317 view.backspace(&Default::default(), cx);
6318 view.backspace(&Default::default(), cx);
6319 });
6320
6321 cx.assert_editor_state(
6322 &"
6323 ˇ
6324 ˇ]]
6325 ˇ
6326 "
6327 .unindent(),
6328 );
6329
6330 cx.update_editor(|view, cx| {
6331 view.handle_input("{", cx);
6332 view.handle_input("{", cx);
6333 view.move_right(&MoveRight, cx);
6334 view.move_right(&MoveRight, cx);
6335 view.move_left(&MoveLeft, cx);
6336 view.move_left(&MoveLeft, cx);
6337 view.backspace(&Default::default(), cx);
6338 });
6339
6340 cx.assert_editor_state(
6341 &"
6342 {ˇ}
6343 {ˇ}]]
6344 {ˇ}
6345 "
6346 .unindent(),
6347 );
6348
6349 cx.update_editor(|view, cx| {
6350 view.backspace(&Default::default(), cx);
6351 });
6352
6353 cx.assert_editor_state(
6354 &"
6355 ˇ
6356 ˇ]]
6357 ˇ
6358 "
6359 .unindent(),
6360 );
6361}
6362
6363#[gpui::test]
6364async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6365 init_test(cx, |_| {});
6366
6367 let language = Arc::new(Language::new(
6368 LanguageConfig::default(),
6369 Some(tree_sitter_rust::LANGUAGE.into()),
6370 ));
6371
6372 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6373 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6374 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6375 editor
6376 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6377 .await;
6378
6379 editor.update(cx, |editor, cx| {
6380 editor.set_auto_replace_emoji_shortcode(true);
6381
6382 editor.handle_input("Hello ", cx);
6383 editor.handle_input(":wave", cx);
6384 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6385
6386 editor.handle_input(":", cx);
6387 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6388
6389 editor.handle_input(" :smile", cx);
6390 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6391
6392 editor.handle_input(":", cx);
6393 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6394
6395 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6396 editor.handle_input(":wave", cx);
6397 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6398
6399 editor.handle_input(":", cx);
6400 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6401
6402 editor.handle_input(":1", cx);
6403 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6404
6405 editor.handle_input(":", cx);
6406 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6407
6408 // Ensure shortcode does not get replaced when it is part of a word
6409 editor.handle_input(" Test:wave", cx);
6410 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6411
6412 editor.handle_input(":", cx);
6413 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6414
6415 editor.set_auto_replace_emoji_shortcode(false);
6416
6417 // Ensure shortcode does not get replaced when auto replace is off
6418 editor.handle_input(" :wave", cx);
6419 assert_eq!(
6420 editor.text(cx),
6421 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6422 );
6423
6424 editor.handle_input(":", cx);
6425 assert_eq!(
6426 editor.text(cx),
6427 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6428 );
6429 });
6430}
6431
6432#[gpui::test]
6433async fn test_snippets(cx: &mut gpui::TestAppContext) {
6434 init_test(cx, |_| {});
6435
6436 let (text, insertion_ranges) = marked_text_ranges(
6437 indoc! {"
6438 a.ˇ b
6439 a.ˇ b
6440 a.ˇ b
6441 "},
6442 false,
6443 );
6444
6445 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6446 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6447
6448 editor.update(cx, |editor, cx| {
6449 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6450
6451 editor
6452 .insert_snippet(&insertion_ranges, snippet, cx)
6453 .unwrap();
6454
6455 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6456 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6457 assert_eq!(editor.text(cx), expected_text);
6458 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6459 }
6460
6461 assert(
6462 editor,
6463 cx,
6464 indoc! {"
6465 a.f(«one», two, «three») b
6466 a.f(«one», two, «three») b
6467 a.f(«one», two, «three») b
6468 "},
6469 );
6470
6471 // Can't move earlier than the first tab stop
6472 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6473 assert(
6474 editor,
6475 cx,
6476 indoc! {"
6477 a.f(«one», two, «three») b
6478 a.f(«one», two, «three») b
6479 a.f(«one», two, «three») b
6480 "},
6481 );
6482
6483 assert!(editor.move_to_next_snippet_tabstop(cx));
6484 assert(
6485 editor,
6486 cx,
6487 indoc! {"
6488 a.f(one, «two», three) b
6489 a.f(one, «two», three) b
6490 a.f(one, «two», three) b
6491 "},
6492 );
6493
6494 editor.move_to_prev_snippet_tabstop(cx);
6495 assert(
6496 editor,
6497 cx,
6498 indoc! {"
6499 a.f(«one», two, «three») b
6500 a.f(«one», two, «three») b
6501 a.f(«one», two, «three») b
6502 "},
6503 );
6504
6505 assert!(editor.move_to_next_snippet_tabstop(cx));
6506 assert(
6507 editor,
6508 cx,
6509 indoc! {"
6510 a.f(one, «two», three) b
6511 a.f(one, «two», three) b
6512 a.f(one, «two», three) b
6513 "},
6514 );
6515 assert!(editor.move_to_next_snippet_tabstop(cx));
6516 assert(
6517 editor,
6518 cx,
6519 indoc! {"
6520 a.f(one, two, three)ˇ b
6521 a.f(one, two, three)ˇ b
6522 a.f(one, two, three)ˇ b
6523 "},
6524 );
6525
6526 // As soon as the last tab stop is reached, snippet state is gone
6527 editor.move_to_prev_snippet_tabstop(cx);
6528 assert(
6529 editor,
6530 cx,
6531 indoc! {"
6532 a.f(one, two, three)ˇ b
6533 a.f(one, two, three)ˇ b
6534 a.f(one, two, three)ˇ b
6535 "},
6536 );
6537 });
6538}
6539
6540#[gpui::test]
6541async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6542 init_test(cx, |_| {});
6543
6544 let fs = FakeFs::new(cx.executor());
6545 fs.insert_file("/file.rs", Default::default()).await;
6546
6547 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6548
6549 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6550 language_registry.add(rust_lang());
6551 let mut fake_servers = language_registry.register_fake_lsp(
6552 "Rust",
6553 FakeLspAdapter {
6554 capabilities: lsp::ServerCapabilities {
6555 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6556 ..Default::default()
6557 },
6558 ..Default::default()
6559 },
6560 );
6561
6562 let buffer = project
6563 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6564 .await
6565 .unwrap();
6566
6567 cx.executor().start_waiting();
6568 let fake_server = fake_servers.next().await.unwrap();
6569
6570 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6571 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6572 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6573 assert!(cx.read(|cx| editor.is_dirty(cx)));
6574
6575 let save = editor
6576 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6577 .unwrap();
6578 fake_server
6579 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6580 assert_eq!(
6581 params.text_document.uri,
6582 lsp::Url::from_file_path("/file.rs").unwrap()
6583 );
6584 assert_eq!(params.options.tab_size, 4);
6585 Ok(Some(vec![lsp::TextEdit::new(
6586 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6587 ", ".to_string(),
6588 )]))
6589 })
6590 .next()
6591 .await;
6592 cx.executor().start_waiting();
6593 save.await;
6594
6595 assert_eq!(
6596 editor.update(cx, |editor, cx| editor.text(cx)),
6597 "one, two\nthree\n"
6598 );
6599 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6600
6601 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6602 assert!(cx.read(|cx| editor.is_dirty(cx)));
6603
6604 // Ensure we can still save even if formatting hangs.
6605 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6606 assert_eq!(
6607 params.text_document.uri,
6608 lsp::Url::from_file_path("/file.rs").unwrap()
6609 );
6610 futures::future::pending::<()>().await;
6611 unreachable!()
6612 });
6613 let save = editor
6614 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6615 .unwrap();
6616 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6617 cx.executor().start_waiting();
6618 save.await;
6619 assert_eq!(
6620 editor.update(cx, |editor, cx| editor.text(cx)),
6621 "one\ntwo\nthree\n"
6622 );
6623 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6624
6625 // For non-dirty buffer, no formatting request should be sent
6626 let save = editor
6627 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6628 .unwrap();
6629 let _pending_format_request = fake_server
6630 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6631 panic!("Should not be invoked on non-dirty buffer");
6632 })
6633 .next();
6634 cx.executor().start_waiting();
6635 save.await;
6636
6637 // Set rust language override and assert overridden tabsize is sent to language server
6638 update_test_language_settings(cx, |settings| {
6639 settings.languages.insert(
6640 "Rust".into(),
6641 LanguageSettingsContent {
6642 tab_size: NonZeroU32::new(8),
6643 ..Default::default()
6644 },
6645 );
6646 });
6647
6648 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6649 assert!(cx.read(|cx| editor.is_dirty(cx)));
6650 let save = editor
6651 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6652 .unwrap();
6653 fake_server
6654 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6655 assert_eq!(
6656 params.text_document.uri,
6657 lsp::Url::from_file_path("/file.rs").unwrap()
6658 );
6659 assert_eq!(params.options.tab_size, 8);
6660 Ok(Some(vec![]))
6661 })
6662 .next()
6663 .await;
6664 cx.executor().start_waiting();
6665 save.await;
6666}
6667
6668#[gpui::test]
6669async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6670 init_test(cx, |_| {});
6671
6672 let cols = 4;
6673 let rows = 10;
6674 let sample_text_1 = sample_text(rows, cols, 'a');
6675 assert_eq!(
6676 sample_text_1,
6677 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6678 );
6679 let sample_text_2 = sample_text(rows, cols, 'l');
6680 assert_eq!(
6681 sample_text_2,
6682 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6683 );
6684 let sample_text_3 = sample_text(rows, cols, 'v');
6685 assert_eq!(
6686 sample_text_3,
6687 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6688 );
6689
6690 let fs = FakeFs::new(cx.executor());
6691 fs.insert_tree(
6692 "/a",
6693 json!({
6694 "main.rs": sample_text_1,
6695 "other.rs": sample_text_2,
6696 "lib.rs": sample_text_3,
6697 }),
6698 )
6699 .await;
6700
6701 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6702 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6703 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6704
6705 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6706 language_registry.add(rust_lang());
6707 let mut fake_servers = language_registry.register_fake_lsp(
6708 "Rust",
6709 FakeLspAdapter {
6710 capabilities: lsp::ServerCapabilities {
6711 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6712 ..Default::default()
6713 },
6714 ..Default::default()
6715 },
6716 );
6717
6718 let worktree = project.update(cx, |project, cx| {
6719 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6720 assert_eq!(worktrees.len(), 1);
6721 worktrees.pop().unwrap()
6722 });
6723 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6724
6725 let buffer_1 = project
6726 .update(cx, |project, cx| {
6727 project.open_buffer((worktree_id, "main.rs"), cx)
6728 })
6729 .await
6730 .unwrap();
6731 let buffer_2 = project
6732 .update(cx, |project, cx| {
6733 project.open_buffer((worktree_id, "other.rs"), cx)
6734 })
6735 .await
6736 .unwrap();
6737 let buffer_3 = project
6738 .update(cx, |project, cx| {
6739 project.open_buffer((worktree_id, "lib.rs"), cx)
6740 })
6741 .await
6742 .unwrap();
6743
6744 let multi_buffer = cx.new_model(|cx| {
6745 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6746 multi_buffer.push_excerpts(
6747 buffer_1.clone(),
6748 [
6749 ExcerptRange {
6750 context: Point::new(0, 0)..Point::new(3, 0),
6751 primary: None,
6752 },
6753 ExcerptRange {
6754 context: Point::new(5, 0)..Point::new(7, 0),
6755 primary: None,
6756 },
6757 ExcerptRange {
6758 context: Point::new(9, 0)..Point::new(10, 4),
6759 primary: None,
6760 },
6761 ],
6762 cx,
6763 );
6764 multi_buffer.push_excerpts(
6765 buffer_2.clone(),
6766 [
6767 ExcerptRange {
6768 context: Point::new(0, 0)..Point::new(3, 0),
6769 primary: None,
6770 },
6771 ExcerptRange {
6772 context: Point::new(5, 0)..Point::new(7, 0),
6773 primary: None,
6774 },
6775 ExcerptRange {
6776 context: Point::new(9, 0)..Point::new(10, 4),
6777 primary: None,
6778 },
6779 ],
6780 cx,
6781 );
6782 multi_buffer.push_excerpts(
6783 buffer_3.clone(),
6784 [
6785 ExcerptRange {
6786 context: Point::new(0, 0)..Point::new(3, 0),
6787 primary: None,
6788 },
6789 ExcerptRange {
6790 context: Point::new(5, 0)..Point::new(7, 0),
6791 primary: None,
6792 },
6793 ExcerptRange {
6794 context: Point::new(9, 0)..Point::new(10, 4),
6795 primary: None,
6796 },
6797 ],
6798 cx,
6799 );
6800 multi_buffer
6801 });
6802 let multi_buffer_editor = cx.new_view(|cx| {
6803 Editor::new(
6804 EditorMode::Full,
6805 multi_buffer,
6806 Some(project.clone()),
6807 true,
6808 cx,
6809 )
6810 });
6811
6812 multi_buffer_editor.update(cx, |editor, cx| {
6813 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6814 editor.insert("|one|two|three|", cx);
6815 });
6816 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6817 multi_buffer_editor.update(cx, |editor, cx| {
6818 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6819 s.select_ranges(Some(60..70))
6820 });
6821 editor.insert("|four|five|six|", cx);
6822 });
6823 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6824
6825 // First two buffers should be edited, but not the third one.
6826 assert_eq!(
6827 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6828 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6829 );
6830 buffer_1.update(cx, |buffer, _| {
6831 assert!(buffer.is_dirty());
6832 assert_eq!(
6833 buffer.text(),
6834 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6835 )
6836 });
6837 buffer_2.update(cx, |buffer, _| {
6838 assert!(buffer.is_dirty());
6839 assert_eq!(
6840 buffer.text(),
6841 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6842 )
6843 });
6844 buffer_3.update(cx, |buffer, _| {
6845 assert!(!buffer.is_dirty());
6846 assert_eq!(buffer.text(), sample_text_3,)
6847 });
6848
6849 cx.executor().start_waiting();
6850 let save = multi_buffer_editor
6851 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6852 .unwrap();
6853
6854 let fake_server = fake_servers.next().await.unwrap();
6855 fake_server
6856 .server
6857 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6858 Ok(Some(vec![lsp::TextEdit::new(
6859 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6860 format!("[{} formatted]", params.text_document.uri),
6861 )]))
6862 })
6863 .detach();
6864 save.await;
6865
6866 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6867 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6868 assert_eq!(
6869 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6870 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6871 );
6872 buffer_1.update(cx, |buffer, _| {
6873 assert!(!buffer.is_dirty());
6874 assert_eq!(
6875 buffer.text(),
6876 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6877 )
6878 });
6879 buffer_2.update(cx, |buffer, _| {
6880 assert!(!buffer.is_dirty());
6881 assert_eq!(
6882 buffer.text(),
6883 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6884 )
6885 });
6886 buffer_3.update(cx, |buffer, _| {
6887 assert!(!buffer.is_dirty());
6888 assert_eq!(buffer.text(), sample_text_3,)
6889 });
6890}
6891
6892#[gpui::test]
6893async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6894 init_test(cx, |_| {});
6895
6896 let fs = FakeFs::new(cx.executor());
6897 fs.insert_file("/file.rs", Default::default()).await;
6898
6899 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6900
6901 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6902 language_registry.add(rust_lang());
6903 let mut fake_servers = language_registry.register_fake_lsp(
6904 "Rust",
6905 FakeLspAdapter {
6906 capabilities: lsp::ServerCapabilities {
6907 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6908 ..Default::default()
6909 },
6910 ..Default::default()
6911 },
6912 );
6913
6914 let buffer = project
6915 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6916 .await
6917 .unwrap();
6918
6919 cx.executor().start_waiting();
6920 let fake_server = fake_servers.next().await.unwrap();
6921
6922 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6923 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6924 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6925 assert!(cx.read(|cx| editor.is_dirty(cx)));
6926
6927 let save = editor
6928 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6929 .unwrap();
6930 fake_server
6931 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6932 assert_eq!(
6933 params.text_document.uri,
6934 lsp::Url::from_file_path("/file.rs").unwrap()
6935 );
6936 assert_eq!(params.options.tab_size, 4);
6937 Ok(Some(vec![lsp::TextEdit::new(
6938 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6939 ", ".to_string(),
6940 )]))
6941 })
6942 .next()
6943 .await;
6944 cx.executor().start_waiting();
6945 save.await;
6946 assert_eq!(
6947 editor.update(cx, |editor, cx| editor.text(cx)),
6948 "one, two\nthree\n"
6949 );
6950 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6951
6952 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6953 assert!(cx.read(|cx| editor.is_dirty(cx)));
6954
6955 // Ensure we can still save even if formatting hangs.
6956 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6957 move |params, _| async move {
6958 assert_eq!(
6959 params.text_document.uri,
6960 lsp::Url::from_file_path("/file.rs").unwrap()
6961 );
6962 futures::future::pending::<()>().await;
6963 unreachable!()
6964 },
6965 );
6966 let save = editor
6967 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6968 .unwrap();
6969 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6970 cx.executor().start_waiting();
6971 save.await;
6972 assert_eq!(
6973 editor.update(cx, |editor, cx| editor.text(cx)),
6974 "one\ntwo\nthree\n"
6975 );
6976 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6977
6978 // For non-dirty buffer, no formatting request should be sent
6979 let save = editor
6980 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6981 .unwrap();
6982 let _pending_format_request = fake_server
6983 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6984 panic!("Should not be invoked on non-dirty buffer");
6985 })
6986 .next();
6987 cx.executor().start_waiting();
6988 save.await;
6989
6990 // Set Rust language override and assert overridden tabsize is sent to language server
6991 update_test_language_settings(cx, |settings| {
6992 settings.languages.insert(
6993 "Rust".into(),
6994 LanguageSettingsContent {
6995 tab_size: NonZeroU32::new(8),
6996 ..Default::default()
6997 },
6998 );
6999 });
7000
7001 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7002 assert!(cx.read(|cx| editor.is_dirty(cx)));
7003 let save = editor
7004 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7005 .unwrap();
7006 fake_server
7007 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7008 assert_eq!(
7009 params.text_document.uri,
7010 lsp::Url::from_file_path("/file.rs").unwrap()
7011 );
7012 assert_eq!(params.options.tab_size, 8);
7013 Ok(Some(vec![]))
7014 })
7015 .next()
7016 .await;
7017 cx.executor().start_waiting();
7018 save.await;
7019}
7020
7021#[gpui::test]
7022async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7023 init_test(cx, |settings| {
7024 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7025 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7026 ))
7027 });
7028
7029 let fs = FakeFs::new(cx.executor());
7030 fs.insert_file("/file.rs", Default::default()).await;
7031
7032 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7033
7034 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7035 language_registry.add(Arc::new(Language::new(
7036 LanguageConfig {
7037 name: "Rust".into(),
7038 matcher: LanguageMatcher {
7039 path_suffixes: vec!["rs".to_string()],
7040 ..Default::default()
7041 },
7042 ..LanguageConfig::default()
7043 },
7044 Some(tree_sitter_rust::LANGUAGE.into()),
7045 )));
7046 update_test_language_settings(cx, |settings| {
7047 // Enable Prettier formatting for the same buffer, and ensure
7048 // LSP is called instead of Prettier.
7049 settings.defaults.prettier = Some(PrettierSettings {
7050 allowed: true,
7051 ..PrettierSettings::default()
7052 });
7053 });
7054 let mut fake_servers = language_registry.register_fake_lsp(
7055 "Rust",
7056 FakeLspAdapter {
7057 capabilities: lsp::ServerCapabilities {
7058 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7059 ..Default::default()
7060 },
7061 ..Default::default()
7062 },
7063 );
7064
7065 let buffer = project
7066 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7067 .await
7068 .unwrap();
7069
7070 cx.executor().start_waiting();
7071 let fake_server = fake_servers.next().await.unwrap();
7072
7073 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7074 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7075 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7076
7077 let format = editor
7078 .update(cx, |editor, cx| {
7079 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
7080 })
7081 .unwrap();
7082 fake_server
7083 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7084 assert_eq!(
7085 params.text_document.uri,
7086 lsp::Url::from_file_path("/file.rs").unwrap()
7087 );
7088 assert_eq!(params.options.tab_size, 4);
7089 Ok(Some(vec![lsp::TextEdit::new(
7090 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7091 ", ".to_string(),
7092 )]))
7093 })
7094 .next()
7095 .await;
7096 cx.executor().start_waiting();
7097 format.await;
7098 assert_eq!(
7099 editor.update(cx, |editor, cx| editor.text(cx)),
7100 "one, two\nthree\n"
7101 );
7102
7103 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7104 // Ensure we don't lock if formatting hangs.
7105 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7106 assert_eq!(
7107 params.text_document.uri,
7108 lsp::Url::from_file_path("/file.rs").unwrap()
7109 );
7110 futures::future::pending::<()>().await;
7111 unreachable!()
7112 });
7113 let format = editor
7114 .update(cx, |editor, cx| {
7115 editor.perform_format(project, FormatTrigger::Manual, cx)
7116 })
7117 .unwrap();
7118 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7119 cx.executor().start_waiting();
7120 format.await;
7121 assert_eq!(
7122 editor.update(cx, |editor, cx| editor.text(cx)),
7123 "one\ntwo\nthree\n"
7124 );
7125}
7126
7127#[gpui::test]
7128async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7129 init_test(cx, |_| {});
7130
7131 let mut cx = EditorLspTestContext::new_rust(
7132 lsp::ServerCapabilities {
7133 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7134 ..Default::default()
7135 },
7136 cx,
7137 )
7138 .await;
7139
7140 cx.set_state(indoc! {"
7141 one.twoˇ
7142 "});
7143
7144 // The format request takes a long time. When it completes, it inserts
7145 // a newline and an indent before the `.`
7146 cx.lsp
7147 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7148 let executor = cx.background_executor().clone();
7149 async move {
7150 executor.timer(Duration::from_millis(100)).await;
7151 Ok(Some(vec![lsp::TextEdit {
7152 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7153 new_text: "\n ".into(),
7154 }]))
7155 }
7156 });
7157
7158 // Submit a format request.
7159 let format_1 = cx
7160 .update_editor(|editor, cx| editor.format(&Format, cx))
7161 .unwrap();
7162 cx.executor().run_until_parked();
7163
7164 // Submit a second format request.
7165 let format_2 = cx
7166 .update_editor(|editor, cx| editor.format(&Format, cx))
7167 .unwrap();
7168 cx.executor().run_until_parked();
7169
7170 // Wait for both format requests to complete
7171 cx.executor().advance_clock(Duration::from_millis(200));
7172 cx.executor().start_waiting();
7173 format_1.await.unwrap();
7174 cx.executor().start_waiting();
7175 format_2.await.unwrap();
7176
7177 // The formatting edits only happens once.
7178 cx.assert_editor_state(indoc! {"
7179 one
7180 .twoˇ
7181 "});
7182}
7183
7184#[gpui::test]
7185async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7186 init_test(cx, |settings| {
7187 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7188 });
7189
7190 let mut cx = EditorLspTestContext::new_rust(
7191 lsp::ServerCapabilities {
7192 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7193 ..Default::default()
7194 },
7195 cx,
7196 )
7197 .await;
7198
7199 // Set up a buffer white some trailing whitespace and no trailing newline.
7200 cx.set_state(
7201 &[
7202 "one ", //
7203 "twoˇ", //
7204 "three ", //
7205 "four", //
7206 ]
7207 .join("\n"),
7208 );
7209
7210 // Submit a format request.
7211 let format = cx
7212 .update_editor(|editor, cx| editor.format(&Format, cx))
7213 .unwrap();
7214
7215 // Record which buffer changes have been sent to the language server
7216 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7217 cx.lsp
7218 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7219 let buffer_changes = buffer_changes.clone();
7220 move |params, _| {
7221 buffer_changes.lock().extend(
7222 params
7223 .content_changes
7224 .into_iter()
7225 .map(|e| (e.range.unwrap(), e.text)),
7226 );
7227 }
7228 });
7229
7230 // Handle formatting requests to the language server.
7231 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7232 let buffer_changes = buffer_changes.clone();
7233 move |_, _| {
7234 // When formatting is requested, trailing whitespace has already been stripped,
7235 // and the trailing newline has already been added.
7236 assert_eq!(
7237 &buffer_changes.lock()[1..],
7238 &[
7239 (
7240 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7241 "".into()
7242 ),
7243 (
7244 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7245 "".into()
7246 ),
7247 (
7248 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7249 "\n".into()
7250 ),
7251 ]
7252 );
7253
7254 // Insert blank lines between each line of the buffer.
7255 async move {
7256 Ok(Some(vec![
7257 lsp::TextEdit {
7258 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7259 new_text: "\n".into(),
7260 },
7261 lsp::TextEdit {
7262 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7263 new_text: "\n".into(),
7264 },
7265 ]))
7266 }
7267 }
7268 });
7269
7270 // After formatting the buffer, the trailing whitespace is stripped,
7271 // a newline is appended, and the edits provided by the language server
7272 // have been applied.
7273 format.await.unwrap();
7274 cx.assert_editor_state(
7275 &[
7276 "one", //
7277 "", //
7278 "twoˇ", //
7279 "", //
7280 "three", //
7281 "four", //
7282 "", //
7283 ]
7284 .join("\n"),
7285 );
7286
7287 // Undoing the formatting undoes the trailing whitespace removal, the
7288 // trailing newline, and the LSP edits.
7289 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7290 cx.assert_editor_state(
7291 &[
7292 "one ", //
7293 "twoˇ", //
7294 "three ", //
7295 "four", //
7296 ]
7297 .join("\n"),
7298 );
7299}
7300
7301#[gpui::test]
7302async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7303 cx: &mut gpui::TestAppContext,
7304) {
7305 init_test(cx, |_| {});
7306
7307 cx.update(|cx| {
7308 cx.update_global::<SettingsStore, _>(|settings, cx| {
7309 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7310 settings.auto_signature_help = Some(true);
7311 });
7312 });
7313 });
7314
7315 let mut cx = EditorLspTestContext::new_rust(
7316 lsp::ServerCapabilities {
7317 signature_help_provider: Some(lsp::SignatureHelpOptions {
7318 ..Default::default()
7319 }),
7320 ..Default::default()
7321 },
7322 cx,
7323 )
7324 .await;
7325
7326 let language = Language::new(
7327 LanguageConfig {
7328 name: "Rust".into(),
7329 brackets: BracketPairConfig {
7330 pairs: vec![
7331 BracketPair {
7332 start: "{".to_string(),
7333 end: "}".to_string(),
7334 close: true,
7335 surround: true,
7336 newline: true,
7337 },
7338 BracketPair {
7339 start: "(".to_string(),
7340 end: ")".to_string(),
7341 close: true,
7342 surround: true,
7343 newline: true,
7344 },
7345 BracketPair {
7346 start: "/*".to_string(),
7347 end: " */".to_string(),
7348 close: true,
7349 surround: true,
7350 newline: true,
7351 },
7352 BracketPair {
7353 start: "[".to_string(),
7354 end: "]".to_string(),
7355 close: false,
7356 surround: false,
7357 newline: true,
7358 },
7359 BracketPair {
7360 start: "\"".to_string(),
7361 end: "\"".to_string(),
7362 close: true,
7363 surround: true,
7364 newline: false,
7365 },
7366 BracketPair {
7367 start: "<".to_string(),
7368 end: ">".to_string(),
7369 close: false,
7370 surround: true,
7371 newline: true,
7372 },
7373 ],
7374 ..Default::default()
7375 },
7376 autoclose_before: "})]".to_string(),
7377 ..Default::default()
7378 },
7379 Some(tree_sitter_rust::LANGUAGE.into()),
7380 );
7381 let language = Arc::new(language);
7382
7383 cx.language_registry().add(language.clone());
7384 cx.update_buffer(|buffer, cx| {
7385 buffer.set_language(Some(language), cx);
7386 });
7387
7388 cx.set_state(
7389 &r#"
7390 fn main() {
7391 sampleˇ
7392 }
7393 "#
7394 .unindent(),
7395 );
7396
7397 cx.update_editor(|view, cx| {
7398 view.handle_input("(", cx);
7399 });
7400 cx.assert_editor_state(
7401 &"
7402 fn main() {
7403 sample(ˇ)
7404 }
7405 "
7406 .unindent(),
7407 );
7408
7409 let mocked_response = lsp::SignatureHelp {
7410 signatures: vec![lsp::SignatureInformation {
7411 label: "fn sample(param1: u8, param2: u8)".to_string(),
7412 documentation: None,
7413 parameters: Some(vec![
7414 lsp::ParameterInformation {
7415 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7416 documentation: None,
7417 },
7418 lsp::ParameterInformation {
7419 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7420 documentation: None,
7421 },
7422 ]),
7423 active_parameter: None,
7424 }],
7425 active_signature: Some(0),
7426 active_parameter: Some(0),
7427 };
7428 handle_signature_help_request(&mut cx, mocked_response).await;
7429
7430 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7431 .await;
7432
7433 cx.editor(|editor, _| {
7434 let signature_help_state = editor.signature_help_state.popover().cloned();
7435 assert!(signature_help_state.is_some());
7436 let ParsedMarkdown {
7437 text, highlights, ..
7438 } = signature_help_state.unwrap().parsed_content;
7439 assert_eq!(text, "param1: u8, param2: u8");
7440 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7441 });
7442}
7443
7444#[gpui::test]
7445async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7446 init_test(cx, |_| {});
7447
7448 cx.update(|cx| {
7449 cx.update_global::<SettingsStore, _>(|settings, cx| {
7450 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7451 settings.auto_signature_help = Some(false);
7452 settings.show_signature_help_after_edits = Some(false);
7453 });
7454 });
7455 });
7456
7457 let mut cx = EditorLspTestContext::new_rust(
7458 lsp::ServerCapabilities {
7459 signature_help_provider: Some(lsp::SignatureHelpOptions {
7460 ..Default::default()
7461 }),
7462 ..Default::default()
7463 },
7464 cx,
7465 )
7466 .await;
7467
7468 let language = Language::new(
7469 LanguageConfig {
7470 name: "Rust".into(),
7471 brackets: BracketPairConfig {
7472 pairs: vec![
7473 BracketPair {
7474 start: "{".to_string(),
7475 end: "}".to_string(),
7476 close: true,
7477 surround: true,
7478 newline: true,
7479 },
7480 BracketPair {
7481 start: "(".to_string(),
7482 end: ")".to_string(),
7483 close: true,
7484 surround: true,
7485 newline: true,
7486 },
7487 BracketPair {
7488 start: "/*".to_string(),
7489 end: " */".to_string(),
7490 close: true,
7491 surround: true,
7492 newline: true,
7493 },
7494 BracketPair {
7495 start: "[".to_string(),
7496 end: "]".to_string(),
7497 close: false,
7498 surround: false,
7499 newline: true,
7500 },
7501 BracketPair {
7502 start: "\"".to_string(),
7503 end: "\"".to_string(),
7504 close: true,
7505 surround: true,
7506 newline: false,
7507 },
7508 BracketPair {
7509 start: "<".to_string(),
7510 end: ">".to_string(),
7511 close: false,
7512 surround: true,
7513 newline: true,
7514 },
7515 ],
7516 ..Default::default()
7517 },
7518 autoclose_before: "})]".to_string(),
7519 ..Default::default()
7520 },
7521 Some(tree_sitter_rust::LANGUAGE.into()),
7522 );
7523 let language = Arc::new(language);
7524
7525 cx.language_registry().add(language.clone());
7526 cx.update_buffer(|buffer, cx| {
7527 buffer.set_language(Some(language), cx);
7528 });
7529
7530 // Ensure that signature_help is not called when no signature help is enabled.
7531 cx.set_state(
7532 &r#"
7533 fn main() {
7534 sampleˇ
7535 }
7536 "#
7537 .unindent(),
7538 );
7539 cx.update_editor(|view, cx| {
7540 view.handle_input("(", cx);
7541 });
7542 cx.assert_editor_state(
7543 &"
7544 fn main() {
7545 sample(ˇ)
7546 }
7547 "
7548 .unindent(),
7549 );
7550 cx.editor(|editor, _| {
7551 assert!(editor.signature_help_state.task().is_none());
7552 });
7553
7554 let mocked_response = lsp::SignatureHelp {
7555 signatures: vec![lsp::SignatureInformation {
7556 label: "fn sample(param1: u8, param2: u8)".to_string(),
7557 documentation: None,
7558 parameters: Some(vec![
7559 lsp::ParameterInformation {
7560 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7561 documentation: None,
7562 },
7563 lsp::ParameterInformation {
7564 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7565 documentation: None,
7566 },
7567 ]),
7568 active_parameter: None,
7569 }],
7570 active_signature: Some(0),
7571 active_parameter: Some(0),
7572 };
7573
7574 // Ensure that signature_help is called when enabled afte edits
7575 cx.update(|cx| {
7576 cx.update_global::<SettingsStore, _>(|settings, cx| {
7577 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7578 settings.auto_signature_help = Some(false);
7579 settings.show_signature_help_after_edits = Some(true);
7580 });
7581 });
7582 });
7583 cx.set_state(
7584 &r#"
7585 fn main() {
7586 sampleˇ
7587 }
7588 "#
7589 .unindent(),
7590 );
7591 cx.update_editor(|view, cx| {
7592 view.handle_input("(", cx);
7593 });
7594 cx.assert_editor_state(
7595 &"
7596 fn main() {
7597 sample(ˇ)
7598 }
7599 "
7600 .unindent(),
7601 );
7602 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7603 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7604 .await;
7605 cx.update_editor(|editor, _| {
7606 let signature_help_state = editor.signature_help_state.popover().cloned();
7607 assert!(signature_help_state.is_some());
7608 let ParsedMarkdown {
7609 text, highlights, ..
7610 } = signature_help_state.unwrap().parsed_content;
7611 assert_eq!(text, "param1: u8, param2: u8");
7612 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7613 editor.signature_help_state = SignatureHelpState::default();
7614 });
7615
7616 // Ensure that signature_help is called when auto signature help override is enabled
7617 cx.update(|cx| {
7618 cx.update_global::<SettingsStore, _>(|settings, cx| {
7619 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7620 settings.auto_signature_help = Some(true);
7621 settings.show_signature_help_after_edits = Some(false);
7622 });
7623 });
7624 });
7625 cx.set_state(
7626 &r#"
7627 fn main() {
7628 sampleˇ
7629 }
7630 "#
7631 .unindent(),
7632 );
7633 cx.update_editor(|view, cx| {
7634 view.handle_input("(", cx);
7635 });
7636 cx.assert_editor_state(
7637 &"
7638 fn main() {
7639 sample(ˇ)
7640 }
7641 "
7642 .unindent(),
7643 );
7644 handle_signature_help_request(&mut cx, mocked_response).await;
7645 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7646 .await;
7647 cx.editor(|editor, _| {
7648 let signature_help_state = editor.signature_help_state.popover().cloned();
7649 assert!(signature_help_state.is_some());
7650 let ParsedMarkdown {
7651 text, highlights, ..
7652 } = signature_help_state.unwrap().parsed_content;
7653 assert_eq!(text, "param1: u8, param2: u8");
7654 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7655 });
7656}
7657
7658#[gpui::test]
7659async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7660 init_test(cx, |_| {});
7661 cx.update(|cx| {
7662 cx.update_global::<SettingsStore, _>(|settings, cx| {
7663 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7664 settings.auto_signature_help = Some(true);
7665 });
7666 });
7667 });
7668
7669 let mut cx = EditorLspTestContext::new_rust(
7670 lsp::ServerCapabilities {
7671 signature_help_provider: Some(lsp::SignatureHelpOptions {
7672 ..Default::default()
7673 }),
7674 ..Default::default()
7675 },
7676 cx,
7677 )
7678 .await;
7679
7680 // A test that directly calls `show_signature_help`
7681 cx.update_editor(|editor, cx| {
7682 editor.show_signature_help(&ShowSignatureHelp, cx);
7683 });
7684
7685 let mocked_response = lsp::SignatureHelp {
7686 signatures: vec![lsp::SignatureInformation {
7687 label: "fn sample(param1: u8, param2: u8)".to_string(),
7688 documentation: None,
7689 parameters: Some(vec![
7690 lsp::ParameterInformation {
7691 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7692 documentation: None,
7693 },
7694 lsp::ParameterInformation {
7695 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7696 documentation: None,
7697 },
7698 ]),
7699 active_parameter: None,
7700 }],
7701 active_signature: Some(0),
7702 active_parameter: Some(0),
7703 };
7704 handle_signature_help_request(&mut cx, mocked_response).await;
7705
7706 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7707 .await;
7708
7709 cx.editor(|editor, _| {
7710 let signature_help_state = editor.signature_help_state.popover().cloned();
7711 assert!(signature_help_state.is_some());
7712 let ParsedMarkdown {
7713 text, highlights, ..
7714 } = signature_help_state.unwrap().parsed_content;
7715 assert_eq!(text, "param1: u8, param2: u8");
7716 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7717 });
7718
7719 // When exiting outside from inside the brackets, `signature_help` is closed.
7720 cx.set_state(indoc! {"
7721 fn main() {
7722 sample(ˇ);
7723 }
7724
7725 fn sample(param1: u8, param2: u8) {}
7726 "});
7727
7728 cx.update_editor(|editor, cx| {
7729 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7730 });
7731
7732 let mocked_response = lsp::SignatureHelp {
7733 signatures: Vec::new(),
7734 active_signature: None,
7735 active_parameter: None,
7736 };
7737 handle_signature_help_request(&mut cx, mocked_response).await;
7738
7739 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7740 .await;
7741
7742 cx.editor(|editor, _| {
7743 assert!(!editor.signature_help_state.is_shown());
7744 });
7745
7746 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7747 cx.set_state(indoc! {"
7748 fn main() {
7749 sample(ˇ);
7750 }
7751
7752 fn sample(param1: u8, param2: u8) {}
7753 "});
7754
7755 let mocked_response = lsp::SignatureHelp {
7756 signatures: vec![lsp::SignatureInformation {
7757 label: "fn sample(param1: u8, param2: u8)".to_string(),
7758 documentation: None,
7759 parameters: Some(vec![
7760 lsp::ParameterInformation {
7761 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7762 documentation: None,
7763 },
7764 lsp::ParameterInformation {
7765 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7766 documentation: None,
7767 },
7768 ]),
7769 active_parameter: None,
7770 }],
7771 active_signature: Some(0),
7772 active_parameter: Some(0),
7773 };
7774 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7775 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7776 .await;
7777 cx.editor(|editor, _| {
7778 assert!(editor.signature_help_state.is_shown());
7779 });
7780
7781 // Restore the popover with more parameter input
7782 cx.set_state(indoc! {"
7783 fn main() {
7784 sample(param1, param2ˇ);
7785 }
7786
7787 fn sample(param1: u8, param2: u8) {}
7788 "});
7789
7790 let mocked_response = lsp::SignatureHelp {
7791 signatures: vec![lsp::SignatureInformation {
7792 label: "fn sample(param1: u8, param2: u8)".to_string(),
7793 documentation: None,
7794 parameters: Some(vec![
7795 lsp::ParameterInformation {
7796 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7797 documentation: None,
7798 },
7799 lsp::ParameterInformation {
7800 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7801 documentation: None,
7802 },
7803 ]),
7804 active_parameter: None,
7805 }],
7806 active_signature: Some(0),
7807 active_parameter: Some(1),
7808 };
7809 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7810 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7811 .await;
7812
7813 // When selecting a range, the popover is gone.
7814 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7815 cx.update_editor(|editor, cx| {
7816 editor.change_selections(None, cx, |s| {
7817 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7818 })
7819 });
7820 cx.assert_editor_state(indoc! {"
7821 fn main() {
7822 sample(param1, «ˇparam2»);
7823 }
7824
7825 fn sample(param1: u8, param2: u8) {}
7826 "});
7827 cx.editor(|editor, _| {
7828 assert!(!editor.signature_help_state.is_shown());
7829 });
7830
7831 // When unselecting again, the popover is back if within the brackets.
7832 cx.update_editor(|editor, cx| {
7833 editor.change_selections(None, cx, |s| {
7834 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7835 })
7836 });
7837 cx.assert_editor_state(indoc! {"
7838 fn main() {
7839 sample(param1, ˇparam2);
7840 }
7841
7842 fn sample(param1: u8, param2: u8) {}
7843 "});
7844 handle_signature_help_request(&mut cx, mocked_response).await;
7845 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7846 .await;
7847 cx.editor(|editor, _| {
7848 assert!(editor.signature_help_state.is_shown());
7849 });
7850
7851 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7852 cx.update_editor(|editor, cx| {
7853 editor.change_selections(None, cx, |s| {
7854 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7855 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7856 })
7857 });
7858 cx.assert_editor_state(indoc! {"
7859 fn main() {
7860 sample(param1, ˇparam2);
7861 }
7862
7863 fn sample(param1: u8, param2: u8) {}
7864 "});
7865
7866 let mocked_response = lsp::SignatureHelp {
7867 signatures: vec![lsp::SignatureInformation {
7868 label: "fn sample(param1: u8, param2: u8)".to_string(),
7869 documentation: None,
7870 parameters: Some(vec![
7871 lsp::ParameterInformation {
7872 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7873 documentation: None,
7874 },
7875 lsp::ParameterInformation {
7876 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7877 documentation: None,
7878 },
7879 ]),
7880 active_parameter: None,
7881 }],
7882 active_signature: Some(0),
7883 active_parameter: Some(1),
7884 };
7885 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7886 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7887 .await;
7888 cx.update_editor(|editor, cx| {
7889 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7890 });
7891 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7892 .await;
7893 cx.update_editor(|editor, cx| {
7894 editor.change_selections(None, cx, |s| {
7895 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7896 })
7897 });
7898 cx.assert_editor_state(indoc! {"
7899 fn main() {
7900 sample(param1, «ˇparam2»);
7901 }
7902
7903 fn sample(param1: u8, param2: u8) {}
7904 "});
7905 cx.update_editor(|editor, cx| {
7906 editor.change_selections(None, cx, |s| {
7907 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7908 })
7909 });
7910 cx.assert_editor_state(indoc! {"
7911 fn main() {
7912 sample(param1, ˇparam2);
7913 }
7914
7915 fn sample(param1: u8, param2: u8) {}
7916 "});
7917 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7918 .await;
7919}
7920
7921#[gpui::test]
7922async fn test_completion(cx: &mut gpui::TestAppContext) {
7923 init_test(cx, |_| {});
7924
7925 let mut cx = EditorLspTestContext::new_rust(
7926 lsp::ServerCapabilities {
7927 completion_provider: Some(lsp::CompletionOptions {
7928 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7929 resolve_provider: Some(true),
7930 ..Default::default()
7931 }),
7932 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7933 ..Default::default()
7934 },
7935 cx,
7936 )
7937 .await;
7938 let counter = Arc::new(AtomicUsize::new(0));
7939
7940 cx.set_state(indoc! {"
7941 oneˇ
7942 two
7943 three
7944 "});
7945 cx.simulate_keystroke(".");
7946 handle_completion_request(
7947 &mut cx,
7948 indoc! {"
7949 one.|<>
7950 two
7951 three
7952 "},
7953 vec!["first_completion", "second_completion"],
7954 counter.clone(),
7955 )
7956 .await;
7957 cx.condition(|editor, _| editor.context_menu_visible())
7958 .await;
7959 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7960
7961 let _handler = handle_signature_help_request(
7962 &mut cx,
7963 lsp::SignatureHelp {
7964 signatures: vec![lsp::SignatureInformation {
7965 label: "test signature".to_string(),
7966 documentation: None,
7967 parameters: Some(vec![lsp::ParameterInformation {
7968 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7969 documentation: None,
7970 }]),
7971 active_parameter: None,
7972 }],
7973 active_signature: None,
7974 active_parameter: None,
7975 },
7976 );
7977 cx.update_editor(|editor, cx| {
7978 assert!(
7979 !editor.signature_help_state.is_shown(),
7980 "No signature help was called for"
7981 );
7982 editor.show_signature_help(&ShowSignatureHelp, cx);
7983 });
7984 cx.run_until_parked();
7985 cx.update_editor(|editor, _| {
7986 assert!(
7987 !editor.signature_help_state.is_shown(),
7988 "No signature help should be shown when completions menu is open"
7989 );
7990 });
7991
7992 let apply_additional_edits = cx.update_editor(|editor, cx| {
7993 editor.context_menu_next(&Default::default(), cx);
7994 editor
7995 .confirm_completion(&ConfirmCompletion::default(), cx)
7996 .unwrap()
7997 });
7998 cx.assert_editor_state(indoc! {"
7999 one.ˇ
8000 two
8001 three
8002 "});
8003
8004 handle_resolve_completion_request(
8005 &mut cx,
8006 Some(vec![
8007 (
8008 //This overlaps with the primary completion edit which is
8009 //misbehavior from the LSP spec, test that we filter it out
8010 indoc! {"
8011 one.second_ˇcompletion
8012 two
8013 threeˇ
8014 "},
8015 "overlapping additional edit",
8016 ),
8017 (
8018 indoc! {"
8019 one.second_completion
8020 two
8021 threeˇ
8022 "},
8023 "\nadditional edit",
8024 ),
8025 ]),
8026 )
8027 .await;
8028 apply_additional_edits.await.unwrap();
8029 cx.assert_editor_state(indoc! {"
8030 one.second_completionˇ
8031 two
8032 thoverlapping additional editree
8033
8034 additional edit"});
8035
8036 cx.set_state(indoc! {"
8037 one.second_completion
8038 twoˇ
8039 threeˇ
8040 additional edit
8041 "});
8042 cx.simulate_keystroke(" ");
8043 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8044 cx.simulate_keystroke("s");
8045 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8046
8047 cx.assert_editor_state(indoc! {"
8048 one.second_completion
8049 two sˇ
8050 three sˇ
8051 additional edit
8052 "});
8053 handle_completion_request(
8054 &mut cx,
8055 indoc! {"
8056 one.second_completion
8057 two s
8058 three <s|>
8059 additional edit
8060 "},
8061 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8062 counter.clone(),
8063 )
8064 .await;
8065 cx.condition(|editor, _| editor.context_menu_visible())
8066 .await;
8067 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8068
8069 cx.simulate_keystroke("i");
8070
8071 handle_completion_request(
8072 &mut cx,
8073 indoc! {"
8074 one.second_completion
8075 two si
8076 three <si|>
8077 additional edit
8078 "},
8079 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8080 counter.clone(),
8081 )
8082 .await;
8083 cx.condition(|editor, _| editor.context_menu_visible())
8084 .await;
8085 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8086
8087 let apply_additional_edits = cx.update_editor(|editor, cx| {
8088 editor
8089 .confirm_completion(&ConfirmCompletion::default(), cx)
8090 .unwrap()
8091 });
8092 cx.assert_editor_state(indoc! {"
8093 one.second_completion
8094 two siˇ
8095 three siˇ
8096 additional edit
8097 "});
8098
8099 handle_resolve_completion_request(&mut cx, None).await;
8100 apply_additional_edits.await.unwrap();
8101
8102 cx.update(|cx| {
8103 cx.update_global::<SettingsStore, _>(|settings, cx| {
8104 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8105 settings.show_completions_on_input = Some(false);
8106 });
8107 })
8108 });
8109 cx.set_state("editorˇ");
8110 cx.simulate_keystroke(".");
8111 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8112 cx.simulate_keystroke("c");
8113 cx.simulate_keystroke("l");
8114 cx.simulate_keystroke("o");
8115 cx.assert_editor_state("editor.cloˇ");
8116 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8117 cx.update_editor(|editor, cx| {
8118 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8119 });
8120 handle_completion_request(
8121 &mut cx,
8122 "editor.<clo|>",
8123 vec!["close", "clobber"],
8124 counter.clone(),
8125 )
8126 .await;
8127 cx.condition(|editor, _| editor.context_menu_visible())
8128 .await;
8129 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8130
8131 let apply_additional_edits = cx.update_editor(|editor, cx| {
8132 editor
8133 .confirm_completion(&ConfirmCompletion::default(), cx)
8134 .unwrap()
8135 });
8136 cx.assert_editor_state("editor.cloˇ");
8137 handle_resolve_completion_request(&mut cx, None).await;
8138 apply_additional_edits.await.unwrap();
8139 cx.assert_editor_state(indoc! {"
8140 editor.closeˇ"});
8141}
8142
8143#[gpui::test]
8144async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8145 init_test(cx, |_| {});
8146 let mut cx = EditorLspTestContext::new_rust(
8147 lsp::ServerCapabilities {
8148 completion_provider: Some(lsp::CompletionOptions {
8149 trigger_characters: Some(vec![".".to_string()]),
8150 ..Default::default()
8151 }),
8152 ..Default::default()
8153 },
8154 cx,
8155 )
8156 .await;
8157 cx.lsp
8158 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8159 Ok(Some(lsp::CompletionResponse::Array(vec![
8160 lsp::CompletionItem {
8161 label: "first".into(),
8162 ..Default::default()
8163 },
8164 lsp::CompletionItem {
8165 label: "last".into(),
8166 ..Default::default()
8167 },
8168 ])))
8169 });
8170 cx.set_state("variableˇ");
8171 cx.simulate_keystroke(".");
8172 cx.executor().run_until_parked();
8173
8174 cx.update_editor(|editor, _| {
8175 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8176 assert_eq!(
8177 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8178 &["first", "last"]
8179 );
8180 } else {
8181 panic!("expected completion menu to be open");
8182 }
8183 });
8184
8185 cx.update_editor(|editor, cx| {
8186 editor.move_page_down(&MovePageDown::default(), cx);
8187 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8188 assert!(
8189 menu.selected_item == 1,
8190 "expected PageDown to select the last item from the context menu"
8191 );
8192 } else {
8193 panic!("expected completion menu to stay open after PageDown");
8194 }
8195 });
8196
8197 cx.update_editor(|editor, cx| {
8198 editor.move_page_up(&MovePageUp::default(), cx);
8199 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8200 assert!(
8201 menu.selected_item == 0,
8202 "expected PageUp to select the first item from the context menu"
8203 );
8204 } else {
8205 panic!("expected completion menu to stay open after PageUp");
8206 }
8207 });
8208}
8209
8210#[gpui::test]
8211async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8212 init_test(cx, |_| {});
8213
8214 let mut cx = EditorLspTestContext::new_rust(
8215 lsp::ServerCapabilities {
8216 completion_provider: Some(lsp::CompletionOptions {
8217 trigger_characters: Some(vec![".".to_string()]),
8218 resolve_provider: Some(true),
8219 ..Default::default()
8220 }),
8221 ..Default::default()
8222 },
8223 cx,
8224 )
8225 .await;
8226
8227 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8228 cx.simulate_keystroke(".");
8229 let completion_item = lsp::CompletionItem {
8230 label: "Some".into(),
8231 kind: Some(lsp::CompletionItemKind::SNIPPET),
8232 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8233 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8234 kind: lsp::MarkupKind::Markdown,
8235 value: "```rust\nSome(2)\n```".to_string(),
8236 })),
8237 deprecated: Some(false),
8238 sort_text: Some("Some".to_string()),
8239 filter_text: Some("Some".to_string()),
8240 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8241 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8242 range: lsp::Range {
8243 start: lsp::Position {
8244 line: 0,
8245 character: 22,
8246 },
8247 end: lsp::Position {
8248 line: 0,
8249 character: 22,
8250 },
8251 },
8252 new_text: "Some(2)".to_string(),
8253 })),
8254 additional_text_edits: Some(vec![lsp::TextEdit {
8255 range: lsp::Range {
8256 start: lsp::Position {
8257 line: 0,
8258 character: 20,
8259 },
8260 end: lsp::Position {
8261 line: 0,
8262 character: 22,
8263 },
8264 },
8265 new_text: "".to_string(),
8266 }]),
8267 ..Default::default()
8268 };
8269
8270 let closure_completion_item = completion_item.clone();
8271 let counter = Arc::new(AtomicUsize::new(0));
8272 let counter_clone = counter.clone();
8273 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8274 let task_completion_item = closure_completion_item.clone();
8275 counter_clone.fetch_add(1, atomic::Ordering::Release);
8276 async move {
8277 Ok(Some(lsp::CompletionResponse::Array(vec![
8278 task_completion_item,
8279 ])))
8280 }
8281 });
8282
8283 cx.condition(|editor, _| editor.context_menu_visible())
8284 .await;
8285 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8286 assert!(request.next().await.is_some());
8287 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8288
8289 cx.simulate_keystroke("S");
8290 cx.simulate_keystroke("o");
8291 cx.simulate_keystroke("m");
8292 cx.condition(|editor, _| editor.context_menu_visible())
8293 .await;
8294 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8295 assert!(request.next().await.is_some());
8296 assert!(request.next().await.is_some());
8297 assert!(request.next().await.is_some());
8298 request.close();
8299 assert!(request.next().await.is_none());
8300 assert_eq!(
8301 counter.load(atomic::Ordering::Acquire),
8302 4,
8303 "With the completions menu open, only one LSP request should happen per input"
8304 );
8305}
8306
8307#[gpui::test]
8308async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8309 init_test(cx, |_| {});
8310 let mut cx = EditorTestContext::new(cx).await;
8311 let language = Arc::new(Language::new(
8312 LanguageConfig {
8313 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8314 ..Default::default()
8315 },
8316 Some(tree_sitter_rust::LANGUAGE.into()),
8317 ));
8318 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8319
8320 // If multiple selections intersect a line, the line is only toggled once.
8321 cx.set_state(indoc! {"
8322 fn a() {
8323 «//b();
8324 ˇ»// «c();
8325 //ˇ» d();
8326 }
8327 "});
8328
8329 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8330
8331 cx.assert_editor_state(indoc! {"
8332 fn a() {
8333 «b();
8334 c();
8335 ˇ» d();
8336 }
8337 "});
8338
8339 // The comment prefix is inserted at the same column for every line in a
8340 // selection.
8341 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8342
8343 cx.assert_editor_state(indoc! {"
8344 fn a() {
8345 // «b();
8346 // c();
8347 ˇ»// d();
8348 }
8349 "});
8350
8351 // If a selection ends at the beginning of a line, that line is not toggled.
8352 cx.set_selections_state(indoc! {"
8353 fn a() {
8354 // b();
8355 «// c();
8356 ˇ» // d();
8357 }
8358 "});
8359
8360 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8361
8362 cx.assert_editor_state(indoc! {"
8363 fn a() {
8364 // b();
8365 «c();
8366 ˇ» // d();
8367 }
8368 "});
8369
8370 // If a selection span a single line and is empty, the line is toggled.
8371 cx.set_state(indoc! {"
8372 fn a() {
8373 a();
8374 b();
8375 ˇ
8376 }
8377 "});
8378
8379 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8380
8381 cx.assert_editor_state(indoc! {"
8382 fn a() {
8383 a();
8384 b();
8385 //•ˇ
8386 }
8387 "});
8388
8389 // If a selection span multiple lines, empty lines are not toggled.
8390 cx.set_state(indoc! {"
8391 fn a() {
8392 «a();
8393
8394 c();ˇ»
8395 }
8396 "});
8397
8398 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8399
8400 cx.assert_editor_state(indoc! {"
8401 fn a() {
8402 // «a();
8403
8404 // c();ˇ»
8405 }
8406 "});
8407
8408 // If a selection includes multiple comment prefixes, all lines are uncommented.
8409 cx.set_state(indoc! {"
8410 fn a() {
8411 «// a();
8412 /// b();
8413 //! c();ˇ»
8414 }
8415 "});
8416
8417 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8418
8419 cx.assert_editor_state(indoc! {"
8420 fn a() {
8421 «a();
8422 b();
8423 c();ˇ»
8424 }
8425 "});
8426}
8427
8428#[gpui::test]
8429async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8430 init_test(cx, |_| {});
8431
8432 let language = Arc::new(Language::new(
8433 LanguageConfig {
8434 line_comments: vec!["// ".into()],
8435 ..Default::default()
8436 },
8437 Some(tree_sitter_rust::LANGUAGE.into()),
8438 ));
8439
8440 let mut cx = EditorTestContext::new(cx).await;
8441
8442 cx.language_registry().add(language.clone());
8443 cx.update_buffer(|buffer, cx| {
8444 buffer.set_language(Some(language), cx);
8445 });
8446
8447 let toggle_comments = &ToggleComments {
8448 advance_downwards: true,
8449 };
8450
8451 // Single cursor on one line -> advance
8452 // Cursor moves horizontally 3 characters as well on non-blank line
8453 cx.set_state(indoc!(
8454 "fn a() {
8455 ˇdog();
8456 cat();
8457 }"
8458 ));
8459 cx.update_editor(|editor, cx| {
8460 editor.toggle_comments(toggle_comments, cx);
8461 });
8462 cx.assert_editor_state(indoc!(
8463 "fn a() {
8464 // dog();
8465 catˇ();
8466 }"
8467 ));
8468
8469 // Single selection on one line -> don't advance
8470 cx.set_state(indoc!(
8471 "fn a() {
8472 «dog()ˇ»;
8473 cat();
8474 }"
8475 ));
8476 cx.update_editor(|editor, cx| {
8477 editor.toggle_comments(toggle_comments, cx);
8478 });
8479 cx.assert_editor_state(indoc!(
8480 "fn a() {
8481 // «dog()ˇ»;
8482 cat();
8483 }"
8484 ));
8485
8486 // Multiple cursors on one line -> advance
8487 cx.set_state(indoc!(
8488 "fn a() {
8489 ˇdˇog();
8490 cat();
8491 }"
8492 ));
8493 cx.update_editor(|editor, cx| {
8494 editor.toggle_comments(toggle_comments, cx);
8495 });
8496 cx.assert_editor_state(indoc!(
8497 "fn a() {
8498 // dog();
8499 catˇ(ˇ);
8500 }"
8501 ));
8502
8503 // Multiple cursors on one line, with selection -> don't advance
8504 cx.set_state(indoc!(
8505 "fn a() {
8506 ˇdˇog«()ˇ»;
8507 cat();
8508 }"
8509 ));
8510 cx.update_editor(|editor, cx| {
8511 editor.toggle_comments(toggle_comments, cx);
8512 });
8513 cx.assert_editor_state(indoc!(
8514 "fn a() {
8515 // ˇdˇog«()ˇ»;
8516 cat();
8517 }"
8518 ));
8519
8520 // Single cursor on one line -> advance
8521 // Cursor moves to column 0 on blank line
8522 cx.set_state(indoc!(
8523 "fn a() {
8524 ˇdog();
8525
8526 cat();
8527 }"
8528 ));
8529 cx.update_editor(|editor, cx| {
8530 editor.toggle_comments(toggle_comments, cx);
8531 });
8532 cx.assert_editor_state(indoc!(
8533 "fn a() {
8534 // dog();
8535 ˇ
8536 cat();
8537 }"
8538 ));
8539
8540 // Single cursor on one line -> advance
8541 // Cursor starts and ends at column 0
8542 cx.set_state(indoc!(
8543 "fn a() {
8544 ˇ dog();
8545 cat();
8546 }"
8547 ));
8548 cx.update_editor(|editor, cx| {
8549 editor.toggle_comments(toggle_comments, cx);
8550 });
8551 cx.assert_editor_state(indoc!(
8552 "fn a() {
8553 // dog();
8554 ˇ cat();
8555 }"
8556 ));
8557}
8558
8559#[gpui::test]
8560async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8561 init_test(cx, |_| {});
8562
8563 let mut cx = EditorTestContext::new(cx).await;
8564
8565 let html_language = Arc::new(
8566 Language::new(
8567 LanguageConfig {
8568 name: "HTML".into(),
8569 block_comment: Some(("<!-- ".into(), " -->".into())),
8570 ..Default::default()
8571 },
8572 Some(tree_sitter_html::language()),
8573 )
8574 .with_injection_query(
8575 r#"
8576 (script_element
8577 (raw_text) @content
8578 (#set! "language" "javascript"))
8579 "#,
8580 )
8581 .unwrap(),
8582 );
8583
8584 let javascript_language = Arc::new(Language::new(
8585 LanguageConfig {
8586 name: "JavaScript".into(),
8587 line_comments: vec!["// ".into()],
8588 ..Default::default()
8589 },
8590 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8591 ));
8592
8593 cx.language_registry().add(html_language.clone());
8594 cx.language_registry().add(javascript_language.clone());
8595 cx.update_buffer(|buffer, cx| {
8596 buffer.set_language(Some(html_language), cx);
8597 });
8598
8599 // Toggle comments for empty selections
8600 cx.set_state(
8601 &r#"
8602 <p>A</p>ˇ
8603 <p>B</p>ˇ
8604 <p>C</p>ˇ
8605 "#
8606 .unindent(),
8607 );
8608 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8609 cx.assert_editor_state(
8610 &r#"
8611 <!-- <p>A</p>ˇ -->
8612 <!-- <p>B</p>ˇ -->
8613 <!-- <p>C</p>ˇ -->
8614 "#
8615 .unindent(),
8616 );
8617 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8618 cx.assert_editor_state(
8619 &r#"
8620 <p>A</p>ˇ
8621 <p>B</p>ˇ
8622 <p>C</p>ˇ
8623 "#
8624 .unindent(),
8625 );
8626
8627 // Toggle comments for mixture of empty and non-empty selections, where
8628 // multiple selections occupy a given line.
8629 cx.set_state(
8630 &r#"
8631 <p>A«</p>
8632 <p>ˇ»B</p>ˇ
8633 <p>C«</p>
8634 <p>ˇ»D</p>ˇ
8635 "#
8636 .unindent(),
8637 );
8638
8639 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8640 cx.assert_editor_state(
8641 &r#"
8642 <!-- <p>A«</p>
8643 <p>ˇ»B</p>ˇ -->
8644 <!-- <p>C«</p>
8645 <p>ˇ»D</p>ˇ -->
8646 "#
8647 .unindent(),
8648 );
8649 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8650 cx.assert_editor_state(
8651 &r#"
8652 <p>A«</p>
8653 <p>ˇ»B</p>ˇ
8654 <p>C«</p>
8655 <p>ˇ»D</p>ˇ
8656 "#
8657 .unindent(),
8658 );
8659
8660 // Toggle comments when different languages are active for different
8661 // selections.
8662 cx.set_state(
8663 &r#"
8664 ˇ<script>
8665 ˇvar x = new Y();
8666 ˇ</script>
8667 "#
8668 .unindent(),
8669 );
8670 cx.executor().run_until_parked();
8671 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8672 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8673 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8674 cx.assert_editor_state(
8675 &r#"
8676 <!-- ˇ<script> -->
8677 // ˇvar x = new Y();
8678 // ˇ</script>
8679 "#
8680 .unindent(),
8681 );
8682}
8683
8684#[gpui::test]
8685fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8686 init_test(cx, |_| {});
8687
8688 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8689 let multibuffer = cx.new_model(|cx| {
8690 let mut multibuffer = MultiBuffer::new(ReadWrite);
8691 multibuffer.push_excerpts(
8692 buffer.clone(),
8693 [
8694 ExcerptRange {
8695 context: Point::new(0, 0)..Point::new(0, 4),
8696 primary: None,
8697 },
8698 ExcerptRange {
8699 context: Point::new(1, 0)..Point::new(1, 4),
8700 primary: None,
8701 },
8702 ],
8703 cx,
8704 );
8705 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8706 multibuffer
8707 });
8708
8709 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8710 view.update(cx, |view, cx| {
8711 assert_eq!(view.text(cx), "aaaa\nbbbb");
8712 view.change_selections(None, cx, |s| {
8713 s.select_ranges([
8714 Point::new(0, 0)..Point::new(0, 0),
8715 Point::new(1, 0)..Point::new(1, 0),
8716 ])
8717 });
8718
8719 view.handle_input("X", cx);
8720 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8721 assert_eq!(
8722 view.selections.ranges(cx),
8723 [
8724 Point::new(0, 1)..Point::new(0, 1),
8725 Point::new(1, 1)..Point::new(1, 1),
8726 ]
8727 );
8728
8729 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8730 view.change_selections(None, cx, |s| {
8731 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8732 });
8733 view.backspace(&Default::default(), cx);
8734 assert_eq!(view.text(cx), "Xa\nbbb");
8735 assert_eq!(
8736 view.selections.ranges(cx),
8737 [Point::new(1, 0)..Point::new(1, 0)]
8738 );
8739
8740 view.change_selections(None, cx, |s| {
8741 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8742 });
8743 view.backspace(&Default::default(), cx);
8744 assert_eq!(view.text(cx), "X\nbb");
8745 assert_eq!(
8746 view.selections.ranges(cx),
8747 [Point::new(0, 1)..Point::new(0, 1)]
8748 );
8749 });
8750}
8751
8752#[gpui::test]
8753fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8754 init_test(cx, |_| {});
8755
8756 let markers = vec![('[', ']').into(), ('(', ')').into()];
8757 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8758 indoc! {"
8759 [aaaa
8760 (bbbb]
8761 cccc)",
8762 },
8763 markers.clone(),
8764 );
8765 let excerpt_ranges = markers.into_iter().map(|marker| {
8766 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8767 ExcerptRange {
8768 context,
8769 primary: None,
8770 }
8771 });
8772 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8773 let multibuffer = cx.new_model(|cx| {
8774 let mut multibuffer = MultiBuffer::new(ReadWrite);
8775 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8776 multibuffer
8777 });
8778
8779 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8780 view.update(cx, |view, cx| {
8781 let (expected_text, selection_ranges) = marked_text_ranges(
8782 indoc! {"
8783 aaaa
8784 bˇbbb
8785 bˇbbˇb
8786 cccc"
8787 },
8788 true,
8789 );
8790 assert_eq!(view.text(cx), expected_text);
8791 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8792
8793 view.handle_input("X", cx);
8794
8795 let (expected_text, expected_selections) = marked_text_ranges(
8796 indoc! {"
8797 aaaa
8798 bXˇbbXb
8799 bXˇbbXˇb
8800 cccc"
8801 },
8802 false,
8803 );
8804 assert_eq!(view.text(cx), expected_text);
8805 assert_eq!(view.selections.ranges(cx), expected_selections);
8806
8807 view.newline(&Newline, cx);
8808 let (expected_text, expected_selections) = marked_text_ranges(
8809 indoc! {"
8810 aaaa
8811 bX
8812 ˇbbX
8813 b
8814 bX
8815 ˇbbX
8816 ˇb
8817 cccc"
8818 },
8819 false,
8820 );
8821 assert_eq!(view.text(cx), expected_text);
8822 assert_eq!(view.selections.ranges(cx), expected_selections);
8823 });
8824}
8825
8826#[gpui::test]
8827fn test_refresh_selections(cx: &mut TestAppContext) {
8828 init_test(cx, |_| {});
8829
8830 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8831 let mut excerpt1_id = None;
8832 let multibuffer = cx.new_model(|cx| {
8833 let mut multibuffer = MultiBuffer::new(ReadWrite);
8834 excerpt1_id = multibuffer
8835 .push_excerpts(
8836 buffer.clone(),
8837 [
8838 ExcerptRange {
8839 context: Point::new(0, 0)..Point::new(1, 4),
8840 primary: None,
8841 },
8842 ExcerptRange {
8843 context: Point::new(1, 0)..Point::new(2, 4),
8844 primary: None,
8845 },
8846 ],
8847 cx,
8848 )
8849 .into_iter()
8850 .next();
8851 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8852 multibuffer
8853 });
8854
8855 let editor = cx.add_window(|cx| {
8856 let mut editor = build_editor(multibuffer.clone(), cx);
8857 let snapshot = editor.snapshot(cx);
8858 editor.change_selections(None, cx, |s| {
8859 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8860 });
8861 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8862 assert_eq!(
8863 editor.selections.ranges(cx),
8864 [
8865 Point::new(1, 3)..Point::new(1, 3),
8866 Point::new(2, 1)..Point::new(2, 1),
8867 ]
8868 );
8869 editor
8870 });
8871
8872 // Refreshing selections is a no-op when excerpts haven't changed.
8873 _ = editor.update(cx, |editor, cx| {
8874 editor.change_selections(None, cx, |s| s.refresh());
8875 assert_eq!(
8876 editor.selections.ranges(cx),
8877 [
8878 Point::new(1, 3)..Point::new(1, 3),
8879 Point::new(2, 1)..Point::new(2, 1),
8880 ]
8881 );
8882 });
8883
8884 multibuffer.update(cx, |multibuffer, cx| {
8885 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8886 });
8887 _ = editor.update(cx, |editor, cx| {
8888 // Removing an excerpt causes the first selection to become degenerate.
8889 assert_eq!(
8890 editor.selections.ranges(cx),
8891 [
8892 Point::new(0, 0)..Point::new(0, 0),
8893 Point::new(0, 1)..Point::new(0, 1)
8894 ]
8895 );
8896
8897 // Refreshing selections will relocate the first selection to the original buffer
8898 // location.
8899 editor.change_selections(None, cx, |s| s.refresh());
8900 assert_eq!(
8901 editor.selections.ranges(cx),
8902 [
8903 Point::new(0, 1)..Point::new(0, 1),
8904 Point::new(0, 3)..Point::new(0, 3)
8905 ]
8906 );
8907 assert!(editor.selections.pending_anchor().is_some());
8908 });
8909}
8910
8911#[gpui::test]
8912fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8913 init_test(cx, |_| {});
8914
8915 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8916 let mut excerpt1_id = None;
8917 let multibuffer = cx.new_model(|cx| {
8918 let mut multibuffer = MultiBuffer::new(ReadWrite);
8919 excerpt1_id = multibuffer
8920 .push_excerpts(
8921 buffer.clone(),
8922 [
8923 ExcerptRange {
8924 context: Point::new(0, 0)..Point::new(1, 4),
8925 primary: None,
8926 },
8927 ExcerptRange {
8928 context: Point::new(1, 0)..Point::new(2, 4),
8929 primary: None,
8930 },
8931 ],
8932 cx,
8933 )
8934 .into_iter()
8935 .next();
8936 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8937 multibuffer
8938 });
8939
8940 let editor = cx.add_window(|cx| {
8941 let mut editor = build_editor(multibuffer.clone(), cx);
8942 let snapshot = editor.snapshot(cx);
8943 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8944 assert_eq!(
8945 editor.selections.ranges(cx),
8946 [Point::new(1, 3)..Point::new(1, 3)]
8947 );
8948 editor
8949 });
8950
8951 multibuffer.update(cx, |multibuffer, cx| {
8952 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8953 });
8954 _ = editor.update(cx, |editor, cx| {
8955 assert_eq!(
8956 editor.selections.ranges(cx),
8957 [Point::new(0, 0)..Point::new(0, 0)]
8958 );
8959
8960 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8961 editor.change_selections(None, cx, |s| s.refresh());
8962 assert_eq!(
8963 editor.selections.ranges(cx),
8964 [Point::new(0, 3)..Point::new(0, 3)]
8965 );
8966 assert!(editor.selections.pending_anchor().is_some());
8967 });
8968}
8969
8970#[gpui::test]
8971async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8972 init_test(cx, |_| {});
8973
8974 let language = Arc::new(
8975 Language::new(
8976 LanguageConfig {
8977 brackets: BracketPairConfig {
8978 pairs: vec![
8979 BracketPair {
8980 start: "{".to_string(),
8981 end: "}".to_string(),
8982 close: true,
8983 surround: true,
8984 newline: true,
8985 },
8986 BracketPair {
8987 start: "/* ".to_string(),
8988 end: " */".to_string(),
8989 close: true,
8990 surround: true,
8991 newline: true,
8992 },
8993 ],
8994 ..Default::default()
8995 },
8996 ..Default::default()
8997 },
8998 Some(tree_sitter_rust::LANGUAGE.into()),
8999 )
9000 .with_indents_query("")
9001 .unwrap(),
9002 );
9003
9004 let text = concat!(
9005 "{ }\n", //
9006 " x\n", //
9007 " /* */\n", //
9008 "x\n", //
9009 "{{} }\n", //
9010 );
9011
9012 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9013 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9014 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9015 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9016 .await;
9017
9018 view.update(cx, |view, cx| {
9019 view.change_selections(None, cx, |s| {
9020 s.select_display_ranges([
9021 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9022 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9023 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9024 ])
9025 });
9026 view.newline(&Newline, cx);
9027
9028 assert_eq!(
9029 view.buffer().read(cx).read(cx).text(),
9030 concat!(
9031 "{ \n", // Suppress rustfmt
9032 "\n", //
9033 "}\n", //
9034 " x\n", //
9035 " /* \n", //
9036 " \n", //
9037 " */\n", //
9038 "x\n", //
9039 "{{} \n", //
9040 "}\n", //
9041 )
9042 );
9043 });
9044}
9045
9046#[gpui::test]
9047fn test_highlighted_ranges(cx: &mut TestAppContext) {
9048 init_test(cx, |_| {});
9049
9050 let editor = cx.add_window(|cx| {
9051 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9052 build_editor(buffer.clone(), cx)
9053 });
9054
9055 _ = editor.update(cx, |editor, cx| {
9056 struct Type1;
9057 struct Type2;
9058
9059 let buffer = editor.buffer.read(cx).snapshot(cx);
9060
9061 let anchor_range =
9062 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9063
9064 editor.highlight_background::<Type1>(
9065 &[
9066 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9067 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9068 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9069 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9070 ],
9071 |_| Hsla::red(),
9072 cx,
9073 );
9074 editor.highlight_background::<Type2>(
9075 &[
9076 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9077 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9078 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9079 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9080 ],
9081 |_| Hsla::green(),
9082 cx,
9083 );
9084
9085 let snapshot = editor.snapshot(cx);
9086 let mut highlighted_ranges = editor.background_highlights_in_range(
9087 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9088 &snapshot,
9089 cx.theme().colors(),
9090 );
9091 // Enforce a consistent ordering based on color without relying on the ordering of the
9092 // highlight's `TypeId` which is non-executor.
9093 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9094 assert_eq!(
9095 highlighted_ranges,
9096 &[
9097 (
9098 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9099 Hsla::red(),
9100 ),
9101 (
9102 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9103 Hsla::red(),
9104 ),
9105 (
9106 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9107 Hsla::green(),
9108 ),
9109 (
9110 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9111 Hsla::green(),
9112 ),
9113 ]
9114 );
9115 assert_eq!(
9116 editor.background_highlights_in_range(
9117 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9118 &snapshot,
9119 cx.theme().colors(),
9120 ),
9121 &[(
9122 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9123 Hsla::red(),
9124 )]
9125 );
9126 });
9127}
9128
9129#[gpui::test]
9130async fn test_following(cx: &mut gpui::TestAppContext) {
9131 init_test(cx, |_| {});
9132
9133 let fs = FakeFs::new(cx.executor());
9134 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9135
9136 let buffer = project.update(cx, |project, cx| {
9137 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9138 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9139 });
9140 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9141 let follower = cx.update(|cx| {
9142 cx.open_window(
9143 WindowOptions {
9144 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9145 gpui::Point::new(px(0.), px(0.)),
9146 gpui::Point::new(px(10.), px(80.)),
9147 ))),
9148 ..Default::default()
9149 },
9150 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9151 )
9152 .unwrap()
9153 });
9154
9155 let is_still_following = Rc::new(RefCell::new(true));
9156 let follower_edit_event_count = Rc::new(RefCell::new(0));
9157 let pending_update = Rc::new(RefCell::new(None));
9158 _ = follower.update(cx, {
9159 let update = pending_update.clone();
9160 let is_still_following = is_still_following.clone();
9161 let follower_edit_event_count = follower_edit_event_count.clone();
9162 |_, cx| {
9163 cx.subscribe(
9164 &leader.root_view(cx).unwrap(),
9165 move |_, leader, event, cx| {
9166 leader
9167 .read(cx)
9168 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9169 },
9170 )
9171 .detach();
9172
9173 cx.subscribe(
9174 &follower.root_view(cx).unwrap(),
9175 move |_, _, event: &EditorEvent, _cx| {
9176 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9177 *is_still_following.borrow_mut() = false;
9178 }
9179
9180 if let EditorEvent::BufferEdited = event {
9181 *follower_edit_event_count.borrow_mut() += 1;
9182 }
9183 },
9184 )
9185 .detach();
9186 }
9187 });
9188
9189 // Update the selections only
9190 _ = leader.update(cx, |leader, cx| {
9191 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9192 });
9193 follower
9194 .update(cx, |follower, cx| {
9195 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9196 })
9197 .unwrap()
9198 .await
9199 .unwrap();
9200 _ = follower.update(cx, |follower, cx| {
9201 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9202 });
9203 assert!(*is_still_following.borrow());
9204 assert_eq!(*follower_edit_event_count.borrow(), 0);
9205
9206 // Update the scroll position only
9207 _ = leader.update(cx, |leader, cx| {
9208 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9209 });
9210 follower
9211 .update(cx, |follower, cx| {
9212 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9213 })
9214 .unwrap()
9215 .await
9216 .unwrap();
9217 assert_eq!(
9218 follower
9219 .update(cx, |follower, cx| follower.scroll_position(cx))
9220 .unwrap(),
9221 gpui::Point::new(1.5, 3.5)
9222 );
9223 assert!(*is_still_following.borrow());
9224 assert_eq!(*follower_edit_event_count.borrow(), 0);
9225
9226 // Update the selections and scroll position. The follower's scroll position is updated
9227 // via autoscroll, not via the leader's exact scroll position.
9228 _ = leader.update(cx, |leader, cx| {
9229 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9230 leader.request_autoscroll(Autoscroll::newest(), cx);
9231 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9232 });
9233 follower
9234 .update(cx, |follower, cx| {
9235 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9236 })
9237 .unwrap()
9238 .await
9239 .unwrap();
9240 _ = follower.update(cx, |follower, cx| {
9241 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9242 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9243 });
9244 assert!(*is_still_following.borrow());
9245
9246 // Creating a pending selection that precedes another selection
9247 _ = leader.update(cx, |leader, cx| {
9248 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9249 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9250 });
9251 follower
9252 .update(cx, |follower, cx| {
9253 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9254 })
9255 .unwrap()
9256 .await
9257 .unwrap();
9258 _ = follower.update(cx, |follower, cx| {
9259 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9260 });
9261 assert!(*is_still_following.borrow());
9262
9263 // Extend the pending selection so that it surrounds another selection
9264 _ = leader.update(cx, |leader, cx| {
9265 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9266 });
9267 follower
9268 .update(cx, |follower, cx| {
9269 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9270 })
9271 .unwrap()
9272 .await
9273 .unwrap();
9274 _ = follower.update(cx, |follower, cx| {
9275 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9276 });
9277
9278 // Scrolling locally breaks the follow
9279 _ = follower.update(cx, |follower, cx| {
9280 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9281 follower.set_scroll_anchor(
9282 ScrollAnchor {
9283 anchor: top_anchor,
9284 offset: gpui::Point::new(0.0, 0.5),
9285 },
9286 cx,
9287 );
9288 });
9289 assert!(!(*is_still_following.borrow()));
9290}
9291
9292#[gpui::test]
9293async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9294 init_test(cx, |_| {});
9295
9296 let fs = FakeFs::new(cx.executor());
9297 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9298 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9299 let pane = workspace
9300 .update(cx, |workspace, _| workspace.active_pane().clone())
9301 .unwrap();
9302
9303 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9304
9305 let leader = pane.update(cx, |_, cx| {
9306 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9307 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9308 });
9309
9310 // Start following the editor when it has no excerpts.
9311 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9312 let follower_1 = cx
9313 .update_window(*workspace.deref(), |_, cx| {
9314 Editor::from_state_proto(
9315 workspace.root_view(cx).unwrap(),
9316 ViewId {
9317 creator: Default::default(),
9318 id: 0,
9319 },
9320 &mut state_message,
9321 cx,
9322 )
9323 })
9324 .unwrap()
9325 .unwrap()
9326 .await
9327 .unwrap();
9328
9329 let update_message = Rc::new(RefCell::new(None));
9330 follower_1.update(cx, {
9331 let update = update_message.clone();
9332 |_, cx| {
9333 cx.subscribe(&leader, move |_, leader, event, cx| {
9334 leader
9335 .read(cx)
9336 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9337 })
9338 .detach();
9339 }
9340 });
9341
9342 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9343 (
9344 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9345 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9346 )
9347 });
9348
9349 // Insert some excerpts.
9350 leader.update(cx, |leader, cx| {
9351 leader.buffer.update(cx, |multibuffer, cx| {
9352 let excerpt_ids = multibuffer.push_excerpts(
9353 buffer_1.clone(),
9354 [
9355 ExcerptRange {
9356 context: 1..6,
9357 primary: None,
9358 },
9359 ExcerptRange {
9360 context: 12..15,
9361 primary: None,
9362 },
9363 ExcerptRange {
9364 context: 0..3,
9365 primary: None,
9366 },
9367 ],
9368 cx,
9369 );
9370 multibuffer.insert_excerpts_after(
9371 excerpt_ids[0],
9372 buffer_2.clone(),
9373 [
9374 ExcerptRange {
9375 context: 8..12,
9376 primary: None,
9377 },
9378 ExcerptRange {
9379 context: 0..6,
9380 primary: None,
9381 },
9382 ],
9383 cx,
9384 );
9385 });
9386 });
9387
9388 // Apply the update of adding the excerpts.
9389 follower_1
9390 .update(cx, |follower, cx| {
9391 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9392 })
9393 .await
9394 .unwrap();
9395 assert_eq!(
9396 follower_1.update(cx, |editor, cx| editor.text(cx)),
9397 leader.update(cx, |editor, cx| editor.text(cx))
9398 );
9399 update_message.borrow_mut().take();
9400
9401 // Start following separately after it already has excerpts.
9402 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9403 let follower_2 = cx
9404 .update_window(*workspace.deref(), |_, cx| {
9405 Editor::from_state_proto(
9406 workspace.root_view(cx).unwrap().clone(),
9407 ViewId {
9408 creator: Default::default(),
9409 id: 0,
9410 },
9411 &mut state_message,
9412 cx,
9413 )
9414 })
9415 .unwrap()
9416 .unwrap()
9417 .await
9418 .unwrap();
9419 assert_eq!(
9420 follower_2.update(cx, |editor, cx| editor.text(cx)),
9421 leader.update(cx, |editor, cx| editor.text(cx))
9422 );
9423
9424 // Remove some excerpts.
9425 leader.update(cx, |leader, cx| {
9426 leader.buffer.update(cx, |multibuffer, cx| {
9427 let excerpt_ids = multibuffer.excerpt_ids();
9428 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9429 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9430 });
9431 });
9432
9433 // Apply the update of removing the excerpts.
9434 follower_1
9435 .update(cx, |follower, cx| {
9436 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9437 })
9438 .await
9439 .unwrap();
9440 follower_2
9441 .update(cx, |follower, cx| {
9442 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9443 })
9444 .await
9445 .unwrap();
9446 update_message.borrow_mut().take();
9447 assert_eq!(
9448 follower_1.update(cx, |editor, cx| editor.text(cx)),
9449 leader.update(cx, |editor, cx| editor.text(cx))
9450 );
9451}
9452
9453#[gpui::test]
9454async fn go_to_prev_overlapping_diagnostic(
9455 executor: BackgroundExecutor,
9456 cx: &mut gpui::TestAppContext,
9457) {
9458 init_test(cx, |_| {});
9459
9460 let mut cx = EditorTestContext::new(cx).await;
9461 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9462
9463 cx.set_state(indoc! {"
9464 ˇfn func(abc def: i32) -> u32 {
9465 }
9466 "});
9467
9468 cx.update(|cx| {
9469 project.update(cx, |project, cx| {
9470 project
9471 .update_diagnostics(
9472 LanguageServerId(0),
9473 lsp::PublishDiagnosticsParams {
9474 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9475 version: None,
9476 diagnostics: vec![
9477 lsp::Diagnostic {
9478 range: lsp::Range::new(
9479 lsp::Position::new(0, 11),
9480 lsp::Position::new(0, 12),
9481 ),
9482 severity: Some(lsp::DiagnosticSeverity::ERROR),
9483 ..Default::default()
9484 },
9485 lsp::Diagnostic {
9486 range: lsp::Range::new(
9487 lsp::Position::new(0, 12),
9488 lsp::Position::new(0, 15),
9489 ),
9490 severity: Some(lsp::DiagnosticSeverity::ERROR),
9491 ..Default::default()
9492 },
9493 lsp::Diagnostic {
9494 range: lsp::Range::new(
9495 lsp::Position::new(0, 25),
9496 lsp::Position::new(0, 28),
9497 ),
9498 severity: Some(lsp::DiagnosticSeverity::ERROR),
9499 ..Default::default()
9500 },
9501 ],
9502 },
9503 &[],
9504 cx,
9505 )
9506 .unwrap()
9507 });
9508 });
9509
9510 executor.run_until_parked();
9511
9512 cx.update_editor(|editor, cx| {
9513 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9514 });
9515
9516 cx.assert_editor_state(indoc! {"
9517 fn func(abc def: i32) -> ˇu32 {
9518 }
9519 "});
9520
9521 cx.update_editor(|editor, cx| {
9522 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9523 });
9524
9525 cx.assert_editor_state(indoc! {"
9526 fn func(abc ˇdef: i32) -> u32 {
9527 }
9528 "});
9529
9530 cx.update_editor(|editor, cx| {
9531 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9532 });
9533
9534 cx.assert_editor_state(indoc! {"
9535 fn func(abcˇ def: i32) -> u32 {
9536 }
9537 "});
9538
9539 cx.update_editor(|editor, cx| {
9540 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9541 });
9542
9543 cx.assert_editor_state(indoc! {"
9544 fn func(abc def: i32) -> ˇu32 {
9545 }
9546 "});
9547}
9548
9549#[gpui::test]
9550async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9551 init_test(cx, |_| {});
9552
9553 let mut cx = EditorTestContext::new(cx).await;
9554
9555 cx.set_state(indoc! {"
9556 fn func(abˇc def: i32) -> u32 {
9557 }
9558 "});
9559 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9560
9561 cx.update(|cx| {
9562 project.update(cx, |project, cx| {
9563 project.update_diagnostics(
9564 LanguageServerId(0),
9565 lsp::PublishDiagnosticsParams {
9566 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9567 version: None,
9568 diagnostics: vec![lsp::Diagnostic {
9569 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9570 severity: Some(lsp::DiagnosticSeverity::ERROR),
9571 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9572 ..Default::default()
9573 }],
9574 },
9575 &[],
9576 cx,
9577 )
9578 })
9579 }).unwrap();
9580 cx.run_until_parked();
9581 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9582 cx.run_until_parked();
9583 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9584}
9585
9586#[gpui::test]
9587async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9588 init_test(cx, |_| {});
9589
9590 let mut cx = EditorTestContext::new(cx).await;
9591
9592 let diff_base = r#"
9593 use some::mod;
9594
9595 const A: u32 = 42;
9596
9597 fn main() {
9598 println!("hello");
9599
9600 println!("world");
9601 }
9602 "#
9603 .unindent();
9604
9605 // Edits are modified, removed, modified, added
9606 cx.set_state(
9607 &r#"
9608 use some::modified;
9609
9610 ˇ
9611 fn main() {
9612 println!("hello there");
9613
9614 println!("around the");
9615 println!("world");
9616 }
9617 "#
9618 .unindent(),
9619 );
9620
9621 cx.set_diff_base(Some(&diff_base));
9622 executor.run_until_parked();
9623
9624 cx.update_editor(|editor, cx| {
9625 //Wrap around the bottom of the buffer
9626 for _ in 0..3 {
9627 editor.go_to_next_hunk(&GoToHunk, cx);
9628 }
9629 });
9630
9631 cx.assert_editor_state(
9632 &r#"
9633 ˇuse some::modified;
9634
9635
9636 fn main() {
9637 println!("hello there");
9638
9639 println!("around the");
9640 println!("world");
9641 }
9642 "#
9643 .unindent(),
9644 );
9645
9646 cx.update_editor(|editor, cx| {
9647 //Wrap around the top of the buffer
9648 for _ in 0..2 {
9649 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9650 }
9651 });
9652
9653 cx.assert_editor_state(
9654 &r#"
9655 use some::modified;
9656
9657
9658 fn main() {
9659 ˇ println!("hello there");
9660
9661 println!("around the");
9662 println!("world");
9663 }
9664 "#
9665 .unindent(),
9666 );
9667
9668 cx.update_editor(|editor, cx| {
9669 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9670 });
9671
9672 cx.assert_editor_state(
9673 &r#"
9674 use some::modified;
9675
9676 ˇ
9677 fn main() {
9678 println!("hello there");
9679
9680 println!("around the");
9681 println!("world");
9682 }
9683 "#
9684 .unindent(),
9685 );
9686
9687 cx.update_editor(|editor, cx| {
9688 for _ in 0..3 {
9689 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9690 }
9691 });
9692
9693 cx.assert_editor_state(
9694 &r#"
9695 use some::modified;
9696
9697
9698 fn main() {
9699 ˇ println!("hello there");
9700
9701 println!("around the");
9702 println!("world");
9703 }
9704 "#
9705 .unindent(),
9706 );
9707
9708 cx.update_editor(|editor, cx| {
9709 editor.fold(&Fold, cx);
9710
9711 //Make sure that the fold only gets one hunk
9712 for _ in 0..4 {
9713 editor.go_to_next_hunk(&GoToHunk, cx);
9714 }
9715 });
9716
9717 cx.assert_editor_state(
9718 &r#"
9719 ˇuse some::modified;
9720
9721
9722 fn main() {
9723 println!("hello there");
9724
9725 println!("around the");
9726 println!("world");
9727 }
9728 "#
9729 .unindent(),
9730 );
9731}
9732
9733#[test]
9734fn test_split_words() {
9735 fn split(text: &str) -> Vec<&str> {
9736 split_words(text).collect()
9737 }
9738
9739 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9740 assert_eq!(split("hello_world"), &["hello_", "world"]);
9741 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9742 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9743 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9744 assert_eq!(split("helloworld"), &["helloworld"]);
9745
9746 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9747}
9748
9749#[gpui::test]
9750async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9751 init_test(cx, |_| {});
9752
9753 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9754 let mut assert = |before, after| {
9755 let _state_context = cx.set_state(before);
9756 cx.update_editor(|editor, cx| {
9757 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9758 });
9759 cx.assert_editor_state(after);
9760 };
9761
9762 // Outside bracket jumps to outside of matching bracket
9763 assert("console.logˇ(var);", "console.log(var)ˇ;");
9764 assert("console.log(var)ˇ;", "console.logˇ(var);");
9765
9766 // Inside bracket jumps to inside of matching bracket
9767 assert("console.log(ˇvar);", "console.log(varˇ);");
9768 assert("console.log(varˇ);", "console.log(ˇvar);");
9769
9770 // When outside a bracket and inside, favor jumping to the inside bracket
9771 assert(
9772 "console.log('foo', [1, 2, 3]ˇ);",
9773 "console.log(ˇ'foo', [1, 2, 3]);",
9774 );
9775 assert(
9776 "console.log(ˇ'foo', [1, 2, 3]);",
9777 "console.log('foo', [1, 2, 3]ˇ);",
9778 );
9779
9780 // Bias forward if two options are equally likely
9781 assert(
9782 "let result = curried_fun()ˇ();",
9783 "let result = curried_fun()()ˇ;",
9784 );
9785
9786 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9787 assert(
9788 indoc! {"
9789 function test() {
9790 console.log('test')ˇ
9791 }"},
9792 indoc! {"
9793 function test() {
9794 console.logˇ('test')
9795 }"},
9796 );
9797}
9798
9799#[gpui::test]
9800async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9801 init_test(cx, |_| {});
9802
9803 let fs = FakeFs::new(cx.executor());
9804 fs.insert_tree(
9805 "/a",
9806 json!({
9807 "main.rs": "fn main() { let a = 5; }",
9808 "other.rs": "// Test file",
9809 }),
9810 )
9811 .await;
9812 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9813
9814 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9815 language_registry.add(Arc::new(Language::new(
9816 LanguageConfig {
9817 name: "Rust".into(),
9818 matcher: LanguageMatcher {
9819 path_suffixes: vec!["rs".to_string()],
9820 ..Default::default()
9821 },
9822 brackets: BracketPairConfig {
9823 pairs: vec![BracketPair {
9824 start: "{".to_string(),
9825 end: "}".to_string(),
9826 close: true,
9827 surround: true,
9828 newline: true,
9829 }],
9830 disabled_scopes_by_bracket_ix: Vec::new(),
9831 },
9832 ..Default::default()
9833 },
9834 Some(tree_sitter_rust::LANGUAGE.into()),
9835 )));
9836 let mut fake_servers = language_registry.register_fake_lsp(
9837 "Rust",
9838 FakeLspAdapter {
9839 capabilities: lsp::ServerCapabilities {
9840 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9841 first_trigger_character: "{".to_string(),
9842 more_trigger_character: None,
9843 }),
9844 ..Default::default()
9845 },
9846 ..Default::default()
9847 },
9848 );
9849
9850 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9851
9852 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9853
9854 let worktree_id = workspace
9855 .update(cx, |workspace, cx| {
9856 workspace.project().update(cx, |project, cx| {
9857 project.worktrees(cx).next().unwrap().read(cx).id()
9858 })
9859 })
9860 .unwrap();
9861
9862 let buffer = project
9863 .update(cx, |project, cx| {
9864 project.open_local_buffer("/a/main.rs", cx)
9865 })
9866 .await
9867 .unwrap();
9868 cx.executor().run_until_parked();
9869 cx.executor().start_waiting();
9870 let fake_server = fake_servers.next().await.unwrap();
9871 let editor_handle = workspace
9872 .update(cx, |workspace, cx| {
9873 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9874 })
9875 .unwrap()
9876 .await
9877 .unwrap()
9878 .downcast::<Editor>()
9879 .unwrap();
9880
9881 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9882 assert_eq!(
9883 params.text_document_position.text_document.uri,
9884 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9885 );
9886 assert_eq!(
9887 params.text_document_position.position,
9888 lsp::Position::new(0, 21),
9889 );
9890
9891 Ok(Some(vec![lsp::TextEdit {
9892 new_text: "]".to_string(),
9893 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9894 }]))
9895 });
9896
9897 editor_handle.update(cx, |editor, cx| {
9898 editor.focus(cx);
9899 editor.change_selections(None, cx, |s| {
9900 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9901 });
9902 editor.handle_input("{", cx);
9903 });
9904
9905 cx.executor().run_until_parked();
9906
9907 buffer.update(cx, |buffer, _| {
9908 assert_eq!(
9909 buffer.text(),
9910 "fn main() { let a = {5}; }",
9911 "No extra braces from on type formatting should appear in the buffer"
9912 )
9913 });
9914}
9915
9916#[gpui::test]
9917async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9918 init_test(cx, |_| {});
9919
9920 let fs = FakeFs::new(cx.executor());
9921 fs.insert_tree(
9922 "/a",
9923 json!({
9924 "main.rs": "fn main() { let a = 5; }",
9925 "other.rs": "// Test file",
9926 }),
9927 )
9928 .await;
9929
9930 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9931
9932 let server_restarts = Arc::new(AtomicUsize::new(0));
9933 let closure_restarts = Arc::clone(&server_restarts);
9934 let language_server_name = "test language server";
9935 let language_name: LanguageName = "Rust".into();
9936
9937 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9938 language_registry.add(Arc::new(Language::new(
9939 LanguageConfig {
9940 name: language_name.clone(),
9941 matcher: LanguageMatcher {
9942 path_suffixes: vec!["rs".to_string()],
9943 ..Default::default()
9944 },
9945 ..Default::default()
9946 },
9947 Some(tree_sitter_rust::LANGUAGE.into()),
9948 )));
9949 let mut fake_servers = language_registry.register_fake_lsp(
9950 "Rust",
9951 FakeLspAdapter {
9952 name: language_server_name,
9953 initialization_options: Some(json!({
9954 "testOptionValue": true
9955 })),
9956 initializer: Some(Box::new(move |fake_server| {
9957 let task_restarts = Arc::clone(&closure_restarts);
9958 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9959 task_restarts.fetch_add(1, atomic::Ordering::Release);
9960 futures::future::ready(Ok(()))
9961 });
9962 })),
9963 ..Default::default()
9964 },
9965 );
9966
9967 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9968 let _buffer = project
9969 .update(cx, |project, cx| {
9970 project.open_local_buffer("/a/main.rs", cx)
9971 })
9972 .await
9973 .unwrap();
9974 let _fake_server = fake_servers.next().await.unwrap();
9975 update_test_language_settings(cx, |language_settings| {
9976 language_settings.languages.insert(
9977 language_name.clone(),
9978 LanguageSettingsContent {
9979 tab_size: NonZeroU32::new(8),
9980 ..Default::default()
9981 },
9982 );
9983 });
9984 cx.executor().run_until_parked();
9985 assert_eq!(
9986 server_restarts.load(atomic::Ordering::Acquire),
9987 0,
9988 "Should not restart LSP server on an unrelated change"
9989 );
9990
9991 update_test_project_settings(cx, |project_settings| {
9992 project_settings.lsp.insert(
9993 "Some other server name".into(),
9994 LspSettings {
9995 binary: None,
9996 settings: None,
9997 initialization_options: Some(json!({
9998 "some other init value": false
9999 })),
10000 },
10001 );
10002 });
10003 cx.executor().run_until_parked();
10004 assert_eq!(
10005 server_restarts.load(atomic::Ordering::Acquire),
10006 0,
10007 "Should not restart LSP server on an unrelated LSP settings change"
10008 );
10009
10010 update_test_project_settings(cx, |project_settings| {
10011 project_settings.lsp.insert(
10012 language_server_name.into(),
10013 LspSettings {
10014 binary: None,
10015 settings: None,
10016 initialization_options: Some(json!({
10017 "anotherInitValue": false
10018 })),
10019 },
10020 );
10021 });
10022 cx.executor().run_until_parked();
10023 assert_eq!(
10024 server_restarts.load(atomic::Ordering::Acquire),
10025 1,
10026 "Should restart LSP server on a related LSP settings change"
10027 );
10028
10029 update_test_project_settings(cx, |project_settings| {
10030 project_settings.lsp.insert(
10031 language_server_name.into(),
10032 LspSettings {
10033 binary: None,
10034 settings: None,
10035 initialization_options: Some(json!({
10036 "anotherInitValue": false
10037 })),
10038 },
10039 );
10040 });
10041 cx.executor().run_until_parked();
10042 assert_eq!(
10043 server_restarts.load(atomic::Ordering::Acquire),
10044 1,
10045 "Should not restart LSP server on a related LSP settings change that is the same"
10046 );
10047
10048 update_test_project_settings(cx, |project_settings| {
10049 project_settings.lsp.insert(
10050 language_server_name.into(),
10051 LspSettings {
10052 binary: None,
10053 settings: None,
10054 initialization_options: None,
10055 },
10056 );
10057 });
10058 cx.executor().run_until_parked();
10059 assert_eq!(
10060 server_restarts.load(atomic::Ordering::Acquire),
10061 2,
10062 "Should restart LSP server on another related LSP settings change"
10063 );
10064}
10065
10066#[gpui::test]
10067async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10068 init_test(cx, |_| {});
10069
10070 let mut cx = EditorLspTestContext::new_rust(
10071 lsp::ServerCapabilities {
10072 completion_provider: Some(lsp::CompletionOptions {
10073 trigger_characters: Some(vec![".".to_string()]),
10074 resolve_provider: Some(true),
10075 ..Default::default()
10076 }),
10077 ..Default::default()
10078 },
10079 cx,
10080 )
10081 .await;
10082
10083 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10084 cx.simulate_keystroke(".");
10085 let completion_item = lsp::CompletionItem {
10086 label: "some".into(),
10087 kind: Some(lsp::CompletionItemKind::SNIPPET),
10088 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10089 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10090 kind: lsp::MarkupKind::Markdown,
10091 value: "```rust\nSome(2)\n```".to_string(),
10092 })),
10093 deprecated: Some(false),
10094 sort_text: Some("fffffff2".to_string()),
10095 filter_text: Some("some".to_string()),
10096 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10097 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10098 range: lsp::Range {
10099 start: lsp::Position {
10100 line: 0,
10101 character: 22,
10102 },
10103 end: lsp::Position {
10104 line: 0,
10105 character: 22,
10106 },
10107 },
10108 new_text: "Some(2)".to_string(),
10109 })),
10110 additional_text_edits: Some(vec![lsp::TextEdit {
10111 range: lsp::Range {
10112 start: lsp::Position {
10113 line: 0,
10114 character: 20,
10115 },
10116 end: lsp::Position {
10117 line: 0,
10118 character: 22,
10119 },
10120 },
10121 new_text: "".to_string(),
10122 }]),
10123 ..Default::default()
10124 };
10125
10126 let closure_completion_item = completion_item.clone();
10127 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10128 let task_completion_item = closure_completion_item.clone();
10129 async move {
10130 Ok(Some(lsp::CompletionResponse::Array(vec![
10131 task_completion_item,
10132 ])))
10133 }
10134 });
10135
10136 request.next().await;
10137
10138 cx.condition(|editor, _| editor.context_menu_visible())
10139 .await;
10140 let apply_additional_edits = cx.update_editor(|editor, cx| {
10141 editor
10142 .confirm_completion(&ConfirmCompletion::default(), cx)
10143 .unwrap()
10144 });
10145 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
10146
10147 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10148 let task_completion_item = completion_item.clone();
10149 async move { Ok(task_completion_item) }
10150 })
10151 .next()
10152 .await
10153 .unwrap();
10154 apply_additional_edits.await.unwrap();
10155 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10156}
10157
10158#[gpui::test]
10159async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10160 init_test(cx, |_| {});
10161
10162 let mut cx = EditorLspTestContext::new(
10163 Language::new(
10164 LanguageConfig {
10165 matcher: LanguageMatcher {
10166 path_suffixes: vec!["jsx".into()],
10167 ..Default::default()
10168 },
10169 overrides: [(
10170 "element".into(),
10171 LanguageConfigOverride {
10172 word_characters: Override::Set(['-'].into_iter().collect()),
10173 ..Default::default()
10174 },
10175 )]
10176 .into_iter()
10177 .collect(),
10178 ..Default::default()
10179 },
10180 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10181 )
10182 .with_override_query("(jsx_self_closing_element) @element")
10183 .unwrap(),
10184 lsp::ServerCapabilities {
10185 completion_provider: Some(lsp::CompletionOptions {
10186 trigger_characters: Some(vec![":".to_string()]),
10187 ..Default::default()
10188 }),
10189 ..Default::default()
10190 },
10191 cx,
10192 )
10193 .await;
10194
10195 cx.lsp
10196 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10197 Ok(Some(lsp::CompletionResponse::Array(vec![
10198 lsp::CompletionItem {
10199 label: "bg-blue".into(),
10200 ..Default::default()
10201 },
10202 lsp::CompletionItem {
10203 label: "bg-red".into(),
10204 ..Default::default()
10205 },
10206 lsp::CompletionItem {
10207 label: "bg-yellow".into(),
10208 ..Default::default()
10209 },
10210 ])))
10211 });
10212
10213 cx.set_state(r#"<p class="bgˇ" />"#);
10214
10215 // Trigger completion when typing a dash, because the dash is an extra
10216 // word character in the 'element' scope, which contains the cursor.
10217 cx.simulate_keystroke("-");
10218 cx.executor().run_until_parked();
10219 cx.update_editor(|editor, _| {
10220 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10221 assert_eq!(
10222 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10223 &["bg-red", "bg-blue", "bg-yellow"]
10224 );
10225 } else {
10226 panic!("expected completion menu to be open");
10227 }
10228 });
10229
10230 cx.simulate_keystroke("l");
10231 cx.executor().run_until_parked();
10232 cx.update_editor(|editor, _| {
10233 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10234 assert_eq!(
10235 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10236 &["bg-blue", "bg-yellow"]
10237 );
10238 } else {
10239 panic!("expected completion menu to be open");
10240 }
10241 });
10242
10243 // When filtering completions, consider the character after the '-' to
10244 // be the start of a subword.
10245 cx.set_state(r#"<p class="yelˇ" />"#);
10246 cx.simulate_keystroke("l");
10247 cx.executor().run_until_parked();
10248 cx.update_editor(|editor, _| {
10249 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10250 assert_eq!(
10251 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10252 &["bg-yellow"]
10253 );
10254 } else {
10255 panic!("expected completion menu to be open");
10256 }
10257 });
10258}
10259
10260#[gpui::test]
10261async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10262 init_test(cx, |settings| {
10263 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10264 FormatterList(vec![Formatter::Prettier].into()),
10265 ))
10266 });
10267
10268 let fs = FakeFs::new(cx.executor());
10269 fs.insert_file("/file.ts", Default::default()).await;
10270
10271 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10272 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10273
10274 language_registry.add(Arc::new(Language::new(
10275 LanguageConfig {
10276 name: "TypeScript".into(),
10277 matcher: LanguageMatcher {
10278 path_suffixes: vec!["ts".to_string()],
10279 ..Default::default()
10280 },
10281 ..Default::default()
10282 },
10283 Some(tree_sitter_rust::LANGUAGE.into()),
10284 )));
10285 update_test_language_settings(cx, |settings| {
10286 settings.defaults.prettier = Some(PrettierSettings {
10287 allowed: true,
10288 ..PrettierSettings::default()
10289 });
10290 });
10291
10292 let test_plugin = "test_plugin";
10293 let _ = language_registry.register_fake_lsp(
10294 "TypeScript",
10295 FakeLspAdapter {
10296 prettier_plugins: vec![test_plugin],
10297 ..Default::default()
10298 },
10299 );
10300
10301 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10302 let buffer = project
10303 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10304 .await
10305 .unwrap();
10306
10307 let buffer_text = "one\ntwo\nthree\n";
10308 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10309 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10310 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10311
10312 editor
10313 .update(cx, |editor, cx| {
10314 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10315 })
10316 .unwrap()
10317 .await;
10318 assert_eq!(
10319 editor.update(cx, |editor, cx| editor.text(cx)),
10320 buffer_text.to_string() + prettier_format_suffix,
10321 "Test prettier formatting was not applied to the original buffer text",
10322 );
10323
10324 update_test_language_settings(cx, |settings| {
10325 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10326 });
10327 let format = editor.update(cx, |editor, cx| {
10328 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10329 });
10330 format.await.unwrap();
10331 assert_eq!(
10332 editor.update(cx, |editor, cx| editor.text(cx)),
10333 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10334 "Autoformatting (via test prettier) was not applied to the original buffer text",
10335 );
10336}
10337
10338#[gpui::test]
10339async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10340 init_test(cx, |_| {});
10341 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10342 let base_text = indoc! {r#"struct Row;
10343struct Row1;
10344struct Row2;
10345
10346struct Row4;
10347struct Row5;
10348struct Row6;
10349
10350struct Row8;
10351struct Row9;
10352struct Row10;"#};
10353
10354 // When addition hunks are not adjacent to carets, no hunk revert is performed
10355 assert_hunk_revert(
10356 indoc! {r#"struct Row;
10357 struct Row1;
10358 struct Row1.1;
10359 struct Row1.2;
10360 struct Row2;ˇ
10361
10362 struct Row4;
10363 struct Row5;
10364 struct Row6;
10365
10366 struct Row8;
10367 ˇstruct Row9;
10368 struct Row9.1;
10369 struct Row9.2;
10370 struct Row9.3;
10371 struct Row10;"#},
10372 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10373 indoc! {r#"struct Row;
10374 struct Row1;
10375 struct Row1.1;
10376 struct Row1.2;
10377 struct Row2;ˇ
10378
10379 struct Row4;
10380 struct Row5;
10381 struct Row6;
10382
10383 struct Row8;
10384 ˇstruct Row9;
10385 struct Row9.1;
10386 struct Row9.2;
10387 struct Row9.3;
10388 struct Row10;"#},
10389 base_text,
10390 &mut cx,
10391 );
10392 // Same for selections
10393 assert_hunk_revert(
10394 indoc! {r#"struct Row;
10395 struct Row1;
10396 struct Row2;
10397 struct Row2.1;
10398 struct Row2.2;
10399 «ˇ
10400 struct Row4;
10401 struct» Row5;
10402 «struct Row6;
10403 ˇ»
10404 struct Row9.1;
10405 struct Row9.2;
10406 struct Row9.3;
10407 struct Row8;
10408 struct Row9;
10409 struct Row10;"#},
10410 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10411 indoc! {r#"struct Row;
10412 struct Row1;
10413 struct Row2;
10414 struct Row2.1;
10415 struct Row2.2;
10416 «ˇ
10417 struct Row4;
10418 struct» Row5;
10419 «struct Row6;
10420 ˇ»
10421 struct Row9.1;
10422 struct Row9.2;
10423 struct Row9.3;
10424 struct Row8;
10425 struct Row9;
10426 struct Row10;"#},
10427 base_text,
10428 &mut cx,
10429 );
10430
10431 // When carets and selections intersect the addition hunks, those are reverted.
10432 // Adjacent carets got merged.
10433 assert_hunk_revert(
10434 indoc! {r#"struct Row;
10435 ˇ// something on the top
10436 struct Row1;
10437 struct Row2;
10438 struct Roˇw3.1;
10439 struct Row2.2;
10440 struct Row2.3;ˇ
10441
10442 struct Row4;
10443 struct ˇRow5.1;
10444 struct Row5.2;
10445 struct «Rowˇ»5.3;
10446 struct Row5;
10447 struct Row6;
10448 ˇ
10449 struct Row9.1;
10450 struct «Rowˇ»9.2;
10451 struct «ˇRow»9.3;
10452 struct Row8;
10453 struct Row9;
10454 «ˇ// something on bottom»
10455 struct Row10;"#},
10456 vec![
10457 DiffHunkStatus::Added,
10458 DiffHunkStatus::Added,
10459 DiffHunkStatus::Added,
10460 DiffHunkStatus::Added,
10461 DiffHunkStatus::Added,
10462 ],
10463 indoc! {r#"struct Row;
10464 ˇstruct Row1;
10465 struct Row2;
10466 ˇ
10467 struct Row4;
10468 ˇstruct Row5;
10469 struct Row6;
10470 ˇ
10471 ˇstruct Row8;
10472 struct Row9;
10473 ˇstruct Row10;"#},
10474 base_text,
10475 &mut cx,
10476 );
10477}
10478
10479#[gpui::test]
10480async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10481 init_test(cx, |_| {});
10482 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10483 let base_text = indoc! {r#"struct Row;
10484struct Row1;
10485struct Row2;
10486
10487struct Row4;
10488struct Row5;
10489struct Row6;
10490
10491struct Row8;
10492struct Row9;
10493struct Row10;"#};
10494
10495 // Modification hunks behave the same as the addition ones.
10496 assert_hunk_revert(
10497 indoc! {r#"struct Row;
10498 struct Row1;
10499 struct Row33;
10500 ˇ
10501 struct Row4;
10502 struct Row5;
10503 struct Row6;
10504 ˇ
10505 struct Row99;
10506 struct Row9;
10507 struct Row10;"#},
10508 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10509 indoc! {r#"struct Row;
10510 struct Row1;
10511 struct Row33;
10512 ˇ
10513 struct Row4;
10514 struct Row5;
10515 struct Row6;
10516 ˇ
10517 struct Row99;
10518 struct Row9;
10519 struct Row10;"#},
10520 base_text,
10521 &mut cx,
10522 );
10523 assert_hunk_revert(
10524 indoc! {r#"struct Row;
10525 struct Row1;
10526 struct Row33;
10527 «ˇ
10528 struct Row4;
10529 struct» Row5;
10530 «struct Row6;
10531 ˇ»
10532 struct Row99;
10533 struct Row9;
10534 struct Row10;"#},
10535 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10536 indoc! {r#"struct Row;
10537 struct Row1;
10538 struct Row33;
10539 «ˇ
10540 struct Row4;
10541 struct» Row5;
10542 «struct Row6;
10543 ˇ»
10544 struct Row99;
10545 struct Row9;
10546 struct Row10;"#},
10547 base_text,
10548 &mut cx,
10549 );
10550
10551 assert_hunk_revert(
10552 indoc! {r#"ˇstruct Row1.1;
10553 struct Row1;
10554 «ˇstr»uct Row22;
10555
10556 struct ˇRow44;
10557 struct Row5;
10558 struct «Rˇ»ow66;ˇ
10559
10560 «struˇ»ct Row88;
10561 struct Row9;
10562 struct Row1011;ˇ"#},
10563 vec![
10564 DiffHunkStatus::Modified,
10565 DiffHunkStatus::Modified,
10566 DiffHunkStatus::Modified,
10567 DiffHunkStatus::Modified,
10568 DiffHunkStatus::Modified,
10569 DiffHunkStatus::Modified,
10570 ],
10571 indoc! {r#"struct Row;
10572 ˇstruct Row1;
10573 struct Row2;
10574 ˇ
10575 struct Row4;
10576 ˇstruct Row5;
10577 struct Row6;
10578 ˇ
10579 struct Row8;
10580 ˇstruct Row9;
10581 struct Row10;ˇ"#},
10582 base_text,
10583 &mut cx,
10584 );
10585}
10586
10587#[gpui::test]
10588async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10589 init_test(cx, |_| {});
10590 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10591 let base_text = indoc! {r#"struct Row;
10592struct Row1;
10593struct Row2;
10594
10595struct Row4;
10596struct Row5;
10597struct Row6;
10598
10599struct Row8;
10600struct Row9;
10601struct Row10;"#};
10602
10603 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10604 assert_hunk_revert(
10605 indoc! {r#"struct Row;
10606 struct Row2;
10607
10608 ˇstruct Row4;
10609 struct Row5;
10610 struct Row6;
10611 ˇ
10612 struct Row8;
10613 struct Row10;"#},
10614 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10615 indoc! {r#"struct Row;
10616 struct Row2;
10617
10618 ˇstruct Row4;
10619 struct Row5;
10620 struct Row6;
10621 ˇ
10622 struct Row8;
10623 struct Row10;"#},
10624 base_text,
10625 &mut cx,
10626 );
10627 assert_hunk_revert(
10628 indoc! {r#"struct Row;
10629 struct Row2;
10630
10631 «ˇstruct Row4;
10632 struct» Row5;
10633 «struct Row6;
10634 ˇ»
10635 struct Row8;
10636 struct Row10;"#},
10637 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10638 indoc! {r#"struct Row;
10639 struct Row2;
10640
10641 «ˇstruct Row4;
10642 struct» Row5;
10643 «struct Row6;
10644 ˇ»
10645 struct Row8;
10646 struct Row10;"#},
10647 base_text,
10648 &mut cx,
10649 );
10650
10651 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10652 assert_hunk_revert(
10653 indoc! {r#"struct Row;
10654 ˇstruct Row2;
10655
10656 struct Row4;
10657 struct Row5;
10658 struct Row6;
10659
10660 struct Row8;ˇ
10661 struct Row10;"#},
10662 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10663 indoc! {r#"struct Row;
10664 struct Row1;
10665 ˇstruct Row2;
10666
10667 struct Row4;
10668 struct Row5;
10669 struct Row6;
10670
10671 struct Row8;ˇ
10672 struct Row9;
10673 struct Row10;"#},
10674 base_text,
10675 &mut cx,
10676 );
10677 assert_hunk_revert(
10678 indoc! {r#"struct Row;
10679 struct Row2«ˇ;
10680 struct Row4;
10681 struct» Row5;
10682 «struct Row6;
10683
10684 struct Row8;ˇ»
10685 struct Row10;"#},
10686 vec![
10687 DiffHunkStatus::Removed,
10688 DiffHunkStatus::Removed,
10689 DiffHunkStatus::Removed,
10690 ],
10691 indoc! {r#"struct Row;
10692 struct Row1;
10693 struct Row2«ˇ;
10694
10695 struct Row4;
10696 struct» Row5;
10697 «struct Row6;
10698
10699 struct Row8;ˇ»
10700 struct Row9;
10701 struct Row10;"#},
10702 base_text,
10703 &mut cx,
10704 );
10705}
10706
10707#[gpui::test]
10708async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10709 init_test(cx, |_| {});
10710
10711 let cols = 4;
10712 let rows = 10;
10713 let sample_text_1 = sample_text(rows, cols, 'a');
10714 assert_eq!(
10715 sample_text_1,
10716 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10717 );
10718 let sample_text_2 = sample_text(rows, cols, 'l');
10719 assert_eq!(
10720 sample_text_2,
10721 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10722 );
10723 let sample_text_3 = sample_text(rows, cols, 'v');
10724 assert_eq!(
10725 sample_text_3,
10726 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10727 );
10728
10729 fn diff_every_buffer_row(
10730 buffer: &Model<Buffer>,
10731 sample_text: String,
10732 cols: usize,
10733 cx: &mut gpui::TestAppContext,
10734 ) {
10735 // revert first character in each row, creating one large diff hunk per buffer
10736 let is_first_char = |offset: usize| offset % cols == 0;
10737 buffer.update(cx, |buffer, cx| {
10738 buffer.set_text(
10739 sample_text
10740 .chars()
10741 .enumerate()
10742 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10743 .collect::<String>(),
10744 cx,
10745 );
10746 buffer.set_diff_base(Some(sample_text), cx);
10747 });
10748 cx.executor().run_until_parked();
10749 }
10750
10751 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10752 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10753
10754 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10755 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10756
10757 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10758 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10759
10760 let multibuffer = cx.new_model(|cx| {
10761 let mut multibuffer = MultiBuffer::new(ReadWrite);
10762 multibuffer.push_excerpts(
10763 buffer_1.clone(),
10764 [
10765 ExcerptRange {
10766 context: Point::new(0, 0)..Point::new(3, 0),
10767 primary: None,
10768 },
10769 ExcerptRange {
10770 context: Point::new(5, 0)..Point::new(7, 0),
10771 primary: None,
10772 },
10773 ExcerptRange {
10774 context: Point::new(9, 0)..Point::new(10, 4),
10775 primary: None,
10776 },
10777 ],
10778 cx,
10779 );
10780 multibuffer.push_excerpts(
10781 buffer_2.clone(),
10782 [
10783 ExcerptRange {
10784 context: Point::new(0, 0)..Point::new(3, 0),
10785 primary: None,
10786 },
10787 ExcerptRange {
10788 context: Point::new(5, 0)..Point::new(7, 0),
10789 primary: None,
10790 },
10791 ExcerptRange {
10792 context: Point::new(9, 0)..Point::new(10, 4),
10793 primary: None,
10794 },
10795 ],
10796 cx,
10797 );
10798 multibuffer.push_excerpts(
10799 buffer_3.clone(),
10800 [
10801 ExcerptRange {
10802 context: Point::new(0, 0)..Point::new(3, 0),
10803 primary: None,
10804 },
10805 ExcerptRange {
10806 context: Point::new(5, 0)..Point::new(7, 0),
10807 primary: None,
10808 },
10809 ExcerptRange {
10810 context: Point::new(9, 0)..Point::new(10, 4),
10811 primary: None,
10812 },
10813 ],
10814 cx,
10815 );
10816 multibuffer
10817 });
10818
10819 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10820 editor.update(cx, |editor, cx| {
10821 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");
10822 editor.select_all(&SelectAll, cx);
10823 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10824 });
10825 cx.executor().run_until_parked();
10826 // When all ranges are selected, all buffer hunks are reverted.
10827 editor.update(cx, |editor, cx| {
10828 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");
10829 });
10830 buffer_1.update(cx, |buffer, _| {
10831 assert_eq!(buffer.text(), sample_text_1);
10832 });
10833 buffer_2.update(cx, |buffer, _| {
10834 assert_eq!(buffer.text(), sample_text_2);
10835 });
10836 buffer_3.update(cx, |buffer, _| {
10837 assert_eq!(buffer.text(), sample_text_3);
10838 });
10839
10840 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10841 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10842 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10843 editor.update(cx, |editor, cx| {
10844 editor.change_selections(None, cx, |s| {
10845 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10846 });
10847 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10848 });
10849 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10850 // but not affect buffer_2 and its related excerpts.
10851 editor.update(cx, |editor, cx| {
10852 assert_eq!(
10853 editor.text(cx),
10854 "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"
10855 );
10856 });
10857 buffer_1.update(cx, |buffer, _| {
10858 assert_eq!(buffer.text(), sample_text_1);
10859 });
10860 buffer_2.update(cx, |buffer, _| {
10861 assert_eq!(
10862 buffer.text(),
10863 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10864 );
10865 });
10866 buffer_3.update(cx, |buffer, _| {
10867 assert_eq!(
10868 buffer.text(),
10869 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10870 );
10871 });
10872}
10873
10874#[gpui::test]
10875async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10876 init_test(cx, |_| {});
10877
10878 let cols = 4;
10879 let rows = 10;
10880 let sample_text_1 = sample_text(rows, cols, 'a');
10881 assert_eq!(
10882 sample_text_1,
10883 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10884 );
10885 let sample_text_2 = sample_text(rows, cols, 'l');
10886 assert_eq!(
10887 sample_text_2,
10888 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10889 );
10890 let sample_text_3 = sample_text(rows, cols, 'v');
10891 assert_eq!(
10892 sample_text_3,
10893 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10894 );
10895
10896 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10897 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10898 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10899
10900 let multi_buffer = cx.new_model(|cx| {
10901 let mut multibuffer = MultiBuffer::new(ReadWrite);
10902 multibuffer.push_excerpts(
10903 buffer_1.clone(),
10904 [
10905 ExcerptRange {
10906 context: Point::new(0, 0)..Point::new(3, 0),
10907 primary: None,
10908 },
10909 ExcerptRange {
10910 context: Point::new(5, 0)..Point::new(7, 0),
10911 primary: None,
10912 },
10913 ExcerptRange {
10914 context: Point::new(9, 0)..Point::new(10, 4),
10915 primary: None,
10916 },
10917 ],
10918 cx,
10919 );
10920 multibuffer.push_excerpts(
10921 buffer_2.clone(),
10922 [
10923 ExcerptRange {
10924 context: Point::new(0, 0)..Point::new(3, 0),
10925 primary: None,
10926 },
10927 ExcerptRange {
10928 context: Point::new(5, 0)..Point::new(7, 0),
10929 primary: None,
10930 },
10931 ExcerptRange {
10932 context: Point::new(9, 0)..Point::new(10, 4),
10933 primary: None,
10934 },
10935 ],
10936 cx,
10937 );
10938 multibuffer.push_excerpts(
10939 buffer_3.clone(),
10940 [
10941 ExcerptRange {
10942 context: Point::new(0, 0)..Point::new(3, 0),
10943 primary: None,
10944 },
10945 ExcerptRange {
10946 context: Point::new(5, 0)..Point::new(7, 0),
10947 primary: None,
10948 },
10949 ExcerptRange {
10950 context: Point::new(9, 0)..Point::new(10, 4),
10951 primary: None,
10952 },
10953 ],
10954 cx,
10955 );
10956 multibuffer
10957 });
10958
10959 let fs = FakeFs::new(cx.executor());
10960 fs.insert_tree(
10961 "/a",
10962 json!({
10963 "main.rs": sample_text_1,
10964 "other.rs": sample_text_2,
10965 "lib.rs": sample_text_3,
10966 }),
10967 )
10968 .await;
10969 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10970 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10971 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10972 let multi_buffer_editor = cx.new_view(|cx| {
10973 Editor::new(
10974 EditorMode::Full,
10975 multi_buffer,
10976 Some(project.clone()),
10977 true,
10978 cx,
10979 )
10980 });
10981 let multibuffer_item_id = workspace
10982 .update(cx, |workspace, cx| {
10983 assert!(
10984 workspace.active_item(cx).is_none(),
10985 "active item should be None before the first item is added"
10986 );
10987 workspace.add_item_to_active_pane(
10988 Box::new(multi_buffer_editor.clone()),
10989 None,
10990 true,
10991 cx,
10992 );
10993 let active_item = workspace
10994 .active_item(cx)
10995 .expect("should have an active item after adding the multi buffer");
10996 assert!(
10997 !active_item.is_singleton(cx),
10998 "A multi buffer was expected to active after adding"
10999 );
11000 active_item.item_id()
11001 })
11002 .unwrap();
11003 cx.executor().run_until_parked();
11004
11005 multi_buffer_editor.update(cx, |editor, cx| {
11006 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11007 editor.open_excerpts(&OpenExcerpts, cx);
11008 });
11009 cx.executor().run_until_parked();
11010 let first_item_id = workspace
11011 .update(cx, |workspace, cx| {
11012 let active_item = workspace
11013 .active_item(cx)
11014 .expect("should have an active item after navigating into the 1st buffer");
11015 let first_item_id = active_item.item_id();
11016 assert_ne!(
11017 first_item_id, multibuffer_item_id,
11018 "Should navigate into the 1st buffer and activate it"
11019 );
11020 assert!(
11021 active_item.is_singleton(cx),
11022 "New active item should be a singleton buffer"
11023 );
11024 assert_eq!(
11025 active_item
11026 .act_as::<Editor>(cx)
11027 .expect("should have navigated into an editor for the 1st buffer")
11028 .read(cx)
11029 .text(cx),
11030 sample_text_1
11031 );
11032
11033 workspace
11034 .go_back(workspace.active_pane().downgrade(), cx)
11035 .detach_and_log_err(cx);
11036
11037 first_item_id
11038 })
11039 .unwrap();
11040 cx.executor().run_until_parked();
11041 workspace
11042 .update(cx, |workspace, cx| {
11043 let active_item = workspace
11044 .active_item(cx)
11045 .expect("should have an active item after navigating back");
11046 assert_eq!(
11047 active_item.item_id(),
11048 multibuffer_item_id,
11049 "Should navigate back to the multi buffer"
11050 );
11051 assert!(!active_item.is_singleton(cx));
11052 })
11053 .unwrap();
11054
11055 multi_buffer_editor.update(cx, |editor, cx| {
11056 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11057 s.select_ranges(Some(39..40))
11058 });
11059 editor.open_excerpts(&OpenExcerpts, cx);
11060 });
11061 cx.executor().run_until_parked();
11062 let second_item_id = workspace
11063 .update(cx, |workspace, cx| {
11064 let active_item = workspace
11065 .active_item(cx)
11066 .expect("should have an active item after navigating into the 2nd buffer");
11067 let second_item_id = active_item.item_id();
11068 assert_ne!(
11069 second_item_id, multibuffer_item_id,
11070 "Should navigate away from the multibuffer"
11071 );
11072 assert_ne!(
11073 second_item_id, first_item_id,
11074 "Should navigate into the 2nd buffer and activate it"
11075 );
11076 assert!(
11077 active_item.is_singleton(cx),
11078 "New active item should be a singleton buffer"
11079 );
11080 assert_eq!(
11081 active_item
11082 .act_as::<Editor>(cx)
11083 .expect("should have navigated into an editor")
11084 .read(cx)
11085 .text(cx),
11086 sample_text_2
11087 );
11088
11089 workspace
11090 .go_back(workspace.active_pane().downgrade(), cx)
11091 .detach_and_log_err(cx);
11092
11093 second_item_id
11094 })
11095 .unwrap();
11096 cx.executor().run_until_parked();
11097 workspace
11098 .update(cx, |workspace, cx| {
11099 let active_item = workspace
11100 .active_item(cx)
11101 .expect("should have an active item after navigating back from the 2nd buffer");
11102 assert_eq!(
11103 active_item.item_id(),
11104 multibuffer_item_id,
11105 "Should navigate back from the 2nd buffer to the multi buffer"
11106 );
11107 assert!(!active_item.is_singleton(cx));
11108 })
11109 .unwrap();
11110
11111 multi_buffer_editor.update(cx, |editor, cx| {
11112 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11113 s.select_ranges(Some(60..70))
11114 });
11115 editor.open_excerpts(&OpenExcerpts, cx);
11116 });
11117 cx.executor().run_until_parked();
11118 workspace
11119 .update(cx, |workspace, cx| {
11120 let active_item = workspace
11121 .active_item(cx)
11122 .expect("should have an active item after navigating into the 3rd buffer");
11123 let third_item_id = active_item.item_id();
11124 assert_ne!(
11125 third_item_id, multibuffer_item_id,
11126 "Should navigate into the 3rd buffer and activate it"
11127 );
11128 assert_ne!(third_item_id, first_item_id);
11129 assert_ne!(third_item_id, second_item_id);
11130 assert!(
11131 active_item.is_singleton(cx),
11132 "New active item should be a singleton buffer"
11133 );
11134 assert_eq!(
11135 active_item
11136 .act_as::<Editor>(cx)
11137 .expect("should have navigated into an editor")
11138 .read(cx)
11139 .text(cx),
11140 sample_text_3
11141 );
11142
11143 workspace
11144 .go_back(workspace.active_pane().downgrade(), cx)
11145 .detach_and_log_err(cx);
11146 })
11147 .unwrap();
11148 cx.executor().run_until_parked();
11149 workspace
11150 .update(cx, |workspace, cx| {
11151 let active_item = workspace
11152 .active_item(cx)
11153 .expect("should have an active item after navigating back from the 3rd buffer");
11154 assert_eq!(
11155 active_item.item_id(),
11156 multibuffer_item_id,
11157 "Should navigate back from the 3rd buffer to the multi buffer"
11158 );
11159 assert!(!active_item.is_singleton(cx));
11160 })
11161 .unwrap();
11162}
11163
11164#[gpui::test]
11165async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11166 init_test(cx, |_| {});
11167
11168 let mut cx = EditorTestContext::new(cx).await;
11169
11170 let diff_base = r#"
11171 use some::mod;
11172
11173 const A: u32 = 42;
11174
11175 fn main() {
11176 println!("hello");
11177
11178 println!("world");
11179 }
11180 "#
11181 .unindent();
11182
11183 cx.set_state(
11184 &r#"
11185 use some::modified;
11186
11187 ˇ
11188 fn main() {
11189 println!("hello there");
11190
11191 println!("around the");
11192 println!("world");
11193 }
11194 "#
11195 .unindent(),
11196 );
11197
11198 cx.set_diff_base(Some(&diff_base));
11199 executor.run_until_parked();
11200
11201 cx.update_editor(|editor, cx| {
11202 editor.go_to_next_hunk(&GoToHunk, cx);
11203 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11204 });
11205 executor.run_until_parked();
11206 cx.assert_diff_hunks(
11207 r#"
11208 use some::modified;
11209
11210
11211 fn main() {
11212 - println!("hello");
11213 + println!("hello there");
11214
11215 println!("around the");
11216 println!("world");
11217 }
11218 "#
11219 .unindent(),
11220 );
11221
11222 cx.update_editor(|editor, cx| {
11223 for _ in 0..3 {
11224 editor.go_to_next_hunk(&GoToHunk, cx);
11225 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11226 }
11227 });
11228 executor.run_until_parked();
11229 cx.assert_editor_state(
11230 &r#"
11231 use some::modified;
11232
11233 ˇ
11234 fn main() {
11235 println!("hello there");
11236
11237 println!("around the");
11238 println!("world");
11239 }
11240 "#
11241 .unindent(),
11242 );
11243
11244 cx.assert_diff_hunks(
11245 r#"
11246 - use some::mod;
11247 + use some::modified;
11248
11249 - const A: u32 = 42;
11250
11251 fn main() {
11252 - println!("hello");
11253 + println!("hello there");
11254
11255 + println!("around the");
11256 println!("world");
11257 }
11258 "#
11259 .unindent(),
11260 );
11261
11262 cx.update_editor(|editor, cx| {
11263 editor.cancel(&Cancel, cx);
11264 });
11265
11266 cx.assert_diff_hunks(
11267 r#"
11268 use some::modified;
11269
11270
11271 fn main() {
11272 println!("hello there");
11273
11274 println!("around the");
11275 println!("world");
11276 }
11277 "#
11278 .unindent(),
11279 );
11280}
11281
11282#[gpui::test]
11283async fn test_diff_base_change_with_expanded_diff_hunks(
11284 executor: BackgroundExecutor,
11285 cx: &mut gpui::TestAppContext,
11286) {
11287 init_test(cx, |_| {});
11288
11289 let mut cx = EditorTestContext::new(cx).await;
11290
11291 let diff_base = r#"
11292 use some::mod1;
11293 use some::mod2;
11294
11295 const A: u32 = 42;
11296 const B: u32 = 42;
11297 const C: u32 = 42;
11298
11299 fn main() {
11300 println!("hello");
11301
11302 println!("world");
11303 }
11304 "#
11305 .unindent();
11306
11307 cx.set_state(
11308 &r#"
11309 use some::mod2;
11310
11311 const A: u32 = 42;
11312 const C: u32 = 42;
11313
11314 fn main(ˇ) {
11315 //println!("hello");
11316
11317 println!("world");
11318 //
11319 //
11320 }
11321 "#
11322 .unindent(),
11323 );
11324
11325 cx.set_diff_base(Some(&diff_base));
11326 executor.run_until_parked();
11327
11328 cx.update_editor(|editor, cx| {
11329 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11330 });
11331 executor.run_until_parked();
11332 cx.assert_diff_hunks(
11333 r#"
11334 - use some::mod1;
11335 use some::mod2;
11336
11337 const A: u32 = 42;
11338 - const B: u32 = 42;
11339 const C: u32 = 42;
11340
11341 fn main() {
11342 - println!("hello");
11343 + //println!("hello");
11344
11345 println!("world");
11346 + //
11347 + //
11348 }
11349 "#
11350 .unindent(),
11351 );
11352
11353 cx.set_diff_base(Some("new diff base!"));
11354 executor.run_until_parked();
11355 cx.assert_diff_hunks(
11356 r#"
11357 use some::mod2;
11358
11359 const A: u32 = 42;
11360 const C: u32 = 42;
11361
11362 fn main() {
11363 //println!("hello");
11364
11365 println!("world");
11366 //
11367 //
11368 }
11369 "#
11370 .unindent(),
11371 );
11372
11373 cx.update_editor(|editor, cx| {
11374 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11375 });
11376 executor.run_until_parked();
11377 cx.assert_diff_hunks(
11378 r#"
11379 - new diff base!
11380 + use some::mod2;
11381 +
11382 + const A: u32 = 42;
11383 + const C: u32 = 42;
11384 +
11385 + fn main() {
11386 + //println!("hello");
11387 +
11388 + println!("world");
11389 + //
11390 + //
11391 + }
11392 "#
11393 .unindent(),
11394 );
11395}
11396
11397#[gpui::test]
11398async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11399 init_test(cx, |_| {});
11400
11401 let mut cx = EditorTestContext::new(cx).await;
11402
11403 let diff_base = r#"
11404 use some::mod1;
11405 use some::mod2;
11406
11407 const A: u32 = 42;
11408 const B: u32 = 42;
11409 const C: u32 = 42;
11410
11411 fn main() {
11412 println!("hello");
11413
11414 println!("world");
11415 }
11416
11417 fn another() {
11418 println!("another");
11419 }
11420
11421 fn another2() {
11422 println!("another2");
11423 }
11424 "#
11425 .unindent();
11426
11427 cx.set_state(
11428 &r#"
11429 «use some::mod2;
11430
11431 const A: u32 = 42;
11432 const C: u32 = 42;
11433
11434 fn main() {
11435 //println!("hello");
11436
11437 println!("world");
11438 //
11439 //ˇ»
11440 }
11441
11442 fn another() {
11443 println!("another");
11444 println!("another");
11445 }
11446
11447 println!("another2");
11448 }
11449 "#
11450 .unindent(),
11451 );
11452
11453 cx.set_diff_base(Some(&diff_base));
11454 executor.run_until_parked();
11455
11456 cx.update_editor(|editor, cx| {
11457 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11458 });
11459 executor.run_until_parked();
11460
11461 cx.assert_diff_hunks(
11462 r#"
11463 - use some::mod1;
11464 use some::mod2;
11465
11466 const A: u32 = 42;
11467 - const B: u32 = 42;
11468 const C: u32 = 42;
11469
11470 fn main() {
11471 - println!("hello");
11472 + //println!("hello");
11473
11474 println!("world");
11475 + //
11476 + //
11477 }
11478
11479 fn another() {
11480 println!("another");
11481 + println!("another");
11482 }
11483
11484 - fn another2() {
11485 println!("another2");
11486 }
11487 "#
11488 .unindent(),
11489 );
11490
11491 // Fold across some of the diff hunks. They should no longer appear expanded.
11492 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11493 cx.executor().run_until_parked();
11494
11495 // Hunks are not shown if their position is within a fold
11496 cx.assert_diff_hunks(
11497 r#"
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 fn another() {
11512 println!("another");
11513 + println!("another");
11514 }
11515
11516 - fn another2() {
11517 println!("another2");
11518 }
11519 "#
11520 .unindent(),
11521 );
11522
11523 cx.update_editor(|editor, cx| {
11524 editor.select_all(&SelectAll, cx);
11525 editor.unfold_lines(&UnfoldLines, cx);
11526 });
11527 cx.executor().run_until_parked();
11528
11529 // The deletions reappear when unfolding.
11530 cx.assert_diff_hunks(
11531 r#"
11532 - use some::mod1;
11533 use some::mod2;
11534
11535 const A: u32 = 42;
11536 - const B: u32 = 42;
11537 const C: u32 = 42;
11538
11539 fn main() {
11540 - println!("hello");
11541 + //println!("hello");
11542
11543 println!("world");
11544 + //
11545 + //
11546 }
11547
11548 fn another() {
11549 println!("another");
11550 + println!("another");
11551 }
11552
11553 - fn another2() {
11554 println!("another2");
11555 }
11556 "#
11557 .unindent(),
11558 );
11559}
11560
11561#[gpui::test]
11562async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11563 init_test(cx, |_| {});
11564
11565 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11566 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11567 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11568 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11569 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11570 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11571
11572 let buffer_1 = cx.new_model(|cx| {
11573 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11574 buffer.set_diff_base(Some(file_1_old.into()), cx);
11575 buffer
11576 });
11577 let buffer_2 = cx.new_model(|cx| {
11578 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11579 buffer.set_diff_base(Some(file_2_old.into()), cx);
11580 buffer
11581 });
11582 let buffer_3 = cx.new_model(|cx| {
11583 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11584 buffer.set_diff_base(Some(file_3_old.into()), cx);
11585 buffer
11586 });
11587
11588 let multi_buffer = cx.new_model(|cx| {
11589 let mut multibuffer = MultiBuffer::new(ReadWrite);
11590 multibuffer.push_excerpts(
11591 buffer_1.clone(),
11592 [
11593 ExcerptRange {
11594 context: Point::new(0, 0)..Point::new(3, 0),
11595 primary: None,
11596 },
11597 ExcerptRange {
11598 context: Point::new(5, 0)..Point::new(7, 0),
11599 primary: None,
11600 },
11601 ExcerptRange {
11602 context: Point::new(9, 0)..Point::new(10, 3),
11603 primary: None,
11604 },
11605 ],
11606 cx,
11607 );
11608 multibuffer.push_excerpts(
11609 buffer_2.clone(),
11610 [
11611 ExcerptRange {
11612 context: Point::new(0, 0)..Point::new(3, 0),
11613 primary: None,
11614 },
11615 ExcerptRange {
11616 context: Point::new(5, 0)..Point::new(7, 0),
11617 primary: None,
11618 },
11619 ExcerptRange {
11620 context: Point::new(9, 0)..Point::new(10, 3),
11621 primary: None,
11622 },
11623 ],
11624 cx,
11625 );
11626 multibuffer.push_excerpts(
11627 buffer_3.clone(),
11628 [
11629 ExcerptRange {
11630 context: Point::new(0, 0)..Point::new(3, 0),
11631 primary: None,
11632 },
11633 ExcerptRange {
11634 context: Point::new(5, 0)..Point::new(7, 0),
11635 primary: None,
11636 },
11637 ExcerptRange {
11638 context: Point::new(9, 0)..Point::new(10, 3),
11639 primary: None,
11640 },
11641 ],
11642 cx,
11643 );
11644 multibuffer
11645 });
11646
11647 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11648 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11649 cx.run_until_parked();
11650
11651 cx.assert_editor_state(
11652 &"
11653 ˇaaa
11654 ccc
11655 ddd
11656
11657 ggg
11658 hhh
11659
11660
11661 lll
11662 mmm
11663 NNN
11664
11665 qqq
11666 rrr
11667
11668 uuu
11669 111
11670 222
11671 333
11672
11673 666
11674 777
11675
11676 000
11677 !!!"
11678 .unindent(),
11679 );
11680
11681 cx.update_editor(|editor, cx| {
11682 editor.select_all(&SelectAll, cx);
11683 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11684 });
11685 cx.executor().run_until_parked();
11686
11687 cx.assert_diff_hunks(
11688 "
11689 aaa
11690 - bbb
11691 ccc
11692 ddd
11693
11694 ggg
11695 hhh
11696
11697
11698 lll
11699 mmm
11700 - nnn
11701 + NNN
11702
11703 qqq
11704 rrr
11705
11706 uuu
11707 111
11708 222
11709 333
11710
11711 + 666
11712 777
11713
11714 000
11715 !!!"
11716 .unindent(),
11717 );
11718}
11719
11720#[gpui::test]
11721async fn test_edits_around_expanded_insertion_hunks(
11722 executor: BackgroundExecutor,
11723 cx: &mut gpui::TestAppContext,
11724) {
11725 init_test(cx, |_| {});
11726
11727 let mut cx = EditorTestContext::new(cx).await;
11728
11729 let diff_base = r#"
11730 use some::mod1;
11731 use some::mod2;
11732
11733 const A: u32 = 42;
11734
11735 fn main() {
11736 println!("hello");
11737
11738 println!("world");
11739 }
11740 "#
11741 .unindent();
11742 executor.run_until_parked();
11743 cx.set_state(
11744 &r#"
11745 use some::mod1;
11746 use some::mod2;
11747
11748 const A: u32 = 42;
11749 const B: u32 = 42;
11750 const C: u32 = 42;
11751 ˇ
11752
11753 fn main() {
11754 println!("hello");
11755
11756 println!("world");
11757 }
11758 "#
11759 .unindent(),
11760 );
11761
11762 cx.set_diff_base(Some(&diff_base));
11763 executor.run_until_parked();
11764
11765 cx.update_editor(|editor, cx| {
11766 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11767 });
11768 executor.run_until_parked();
11769
11770 cx.assert_diff_hunks(
11771 r#"
11772 use some::mod1;
11773 use some::mod2;
11774
11775 const A: u32 = 42;
11776 + const B: u32 = 42;
11777 + const C: u32 = 42;
11778 +
11779
11780 fn main() {
11781 println!("hello");
11782
11783 println!("world");
11784 }
11785 "#
11786 .unindent(),
11787 );
11788
11789 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11790 executor.run_until_parked();
11791
11792 cx.assert_diff_hunks(
11793 r#"
11794 use some::mod1;
11795 use some::mod2;
11796
11797 const A: u32 = 42;
11798 + const B: u32 = 42;
11799 + const C: u32 = 42;
11800 + const D: u32 = 42;
11801 +
11802
11803 fn main() {
11804 println!("hello");
11805
11806 println!("world");
11807 }
11808 "#
11809 .unindent(),
11810 );
11811
11812 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11813 executor.run_until_parked();
11814
11815 cx.assert_diff_hunks(
11816 r#"
11817 use some::mod1;
11818 use some::mod2;
11819
11820 const A: u32 = 42;
11821 + const B: u32 = 42;
11822 + const C: u32 = 42;
11823 + const D: u32 = 42;
11824 + const E: u32 = 42;
11825 +
11826
11827 fn main() {
11828 println!("hello");
11829
11830 println!("world");
11831 }
11832 "#
11833 .unindent(),
11834 );
11835
11836 cx.update_editor(|editor, cx| {
11837 editor.delete_line(&DeleteLine, cx);
11838 });
11839 executor.run_until_parked();
11840
11841 cx.assert_diff_hunks(
11842 r#"
11843 use some::mod1;
11844 use some::mod2;
11845
11846 const A: u32 = 42;
11847 + const B: u32 = 42;
11848 + const C: u32 = 42;
11849 + const D: u32 = 42;
11850 + const E: u32 = 42;
11851
11852 fn main() {
11853 println!("hello");
11854
11855 println!("world");
11856 }
11857 "#
11858 .unindent(),
11859 );
11860
11861 cx.update_editor(|editor, cx| {
11862 editor.move_up(&MoveUp, cx);
11863 editor.delete_line(&DeleteLine, cx);
11864 editor.move_up(&MoveUp, cx);
11865 editor.delete_line(&DeleteLine, cx);
11866 editor.move_up(&MoveUp, cx);
11867 editor.delete_line(&DeleteLine, cx);
11868 });
11869 executor.run_until_parked();
11870 cx.assert_editor_state(
11871 &r#"
11872 use some::mod1;
11873 use some::mod2;
11874
11875 const A: u32 = 42;
11876 const B: u32 = 42;
11877 ˇ
11878 fn main() {
11879 println!("hello");
11880
11881 println!("world");
11882 }
11883 "#
11884 .unindent(),
11885 );
11886
11887 cx.assert_diff_hunks(
11888 r#"
11889 use some::mod1;
11890 use some::mod2;
11891
11892 const A: u32 = 42;
11893 + const B: u32 = 42;
11894
11895 fn main() {
11896 println!("hello");
11897
11898 println!("world");
11899 }
11900 "#
11901 .unindent(),
11902 );
11903
11904 cx.update_editor(|editor, cx| {
11905 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11906 editor.delete_line(&DeleteLine, cx);
11907 });
11908 executor.run_until_parked();
11909 cx.assert_diff_hunks(
11910 r#"
11911 use some::mod1;
11912 - use some::mod2;
11913 -
11914 - const A: u32 = 42;
11915
11916 fn main() {
11917 println!("hello");
11918
11919 println!("world");
11920 }
11921 "#
11922 .unindent(),
11923 );
11924}
11925
11926#[gpui::test]
11927async fn test_edits_around_expanded_deletion_hunks(
11928 executor: BackgroundExecutor,
11929 cx: &mut gpui::TestAppContext,
11930) {
11931 init_test(cx, |_| {});
11932
11933 let mut cx = EditorTestContext::new(cx).await;
11934
11935 let diff_base = r#"
11936 use some::mod1;
11937 use some::mod2;
11938
11939 const A: u32 = 42;
11940 const B: u32 = 42;
11941 const C: u32 = 42;
11942
11943
11944 fn main() {
11945 println!("hello");
11946
11947 println!("world");
11948 }
11949 "#
11950 .unindent();
11951 executor.run_until_parked();
11952 cx.set_state(
11953 &r#"
11954 use some::mod1;
11955 use some::mod2;
11956
11957 ˇconst B: u32 = 42;
11958 const C: u32 = 42;
11959
11960
11961 fn main() {
11962 println!("hello");
11963
11964 println!("world");
11965 }
11966 "#
11967 .unindent(),
11968 );
11969
11970 cx.set_diff_base(Some(&diff_base));
11971 executor.run_until_parked();
11972
11973 cx.update_editor(|editor, cx| {
11974 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11975 });
11976 executor.run_until_parked();
11977
11978 cx.assert_diff_hunks(
11979 r#"
11980 use some::mod1;
11981 use some::mod2;
11982
11983 - const A: u32 = 42;
11984 const B: u32 = 42;
11985 const C: u32 = 42;
11986
11987
11988 fn main() {
11989 println!("hello");
11990
11991 println!("world");
11992 }
11993 "#
11994 .unindent(),
11995 );
11996
11997 cx.update_editor(|editor, cx| {
11998 editor.delete_line(&DeleteLine, cx);
11999 });
12000 executor.run_until_parked();
12001 cx.assert_editor_state(
12002 &r#"
12003 use some::mod1;
12004 use some::mod2;
12005
12006 ˇconst C: u32 = 42;
12007
12008
12009 fn main() {
12010 println!("hello");
12011
12012 println!("world");
12013 }
12014 "#
12015 .unindent(),
12016 );
12017 cx.assert_diff_hunks(
12018 r#"
12019 use some::mod1;
12020 use some::mod2;
12021
12022 - const A: u32 = 42;
12023 - const B: u32 = 42;
12024 const C: u32 = 42;
12025
12026
12027 fn main() {
12028 println!("hello");
12029
12030 println!("world");
12031 }
12032 "#
12033 .unindent(),
12034 );
12035
12036 cx.update_editor(|editor, cx| {
12037 editor.delete_line(&DeleteLine, cx);
12038 });
12039 executor.run_until_parked();
12040 cx.assert_editor_state(
12041 &r#"
12042 use some::mod1;
12043 use some::mod2;
12044
12045 ˇ
12046
12047 fn main() {
12048 println!("hello");
12049
12050 println!("world");
12051 }
12052 "#
12053 .unindent(),
12054 );
12055 cx.assert_diff_hunks(
12056 r#"
12057 use some::mod1;
12058 use some::mod2;
12059
12060 - const A: u32 = 42;
12061 - const B: u32 = 42;
12062 - const C: u32 = 42;
12063
12064
12065 fn main() {
12066 println!("hello");
12067
12068 println!("world");
12069 }
12070 "#
12071 .unindent(),
12072 );
12073
12074 cx.update_editor(|editor, cx| {
12075 editor.handle_input("replacement", cx);
12076 });
12077 executor.run_until_parked();
12078 cx.assert_editor_state(
12079 &r#"
12080 use some::mod1;
12081 use some::mod2;
12082
12083 replacementˇ
12084
12085 fn main() {
12086 println!("hello");
12087
12088 println!("world");
12089 }
12090 "#
12091 .unindent(),
12092 );
12093 cx.assert_diff_hunks(
12094 r#"
12095 use some::mod1;
12096 use some::mod2;
12097
12098 - const A: u32 = 42;
12099 - const B: u32 = 42;
12100 - const C: u32 = 42;
12101 -
12102 + replacement
12103
12104 fn main() {
12105 println!("hello");
12106
12107 println!("world");
12108 }
12109 "#
12110 .unindent(),
12111 );
12112}
12113
12114#[gpui::test]
12115async fn test_edit_after_expanded_modification_hunk(
12116 executor: BackgroundExecutor,
12117 cx: &mut gpui::TestAppContext,
12118) {
12119 init_test(cx, |_| {});
12120
12121 let mut cx = EditorTestContext::new(cx).await;
12122
12123 let diff_base = r#"
12124 use some::mod1;
12125 use some::mod2;
12126
12127 const A: u32 = 42;
12128 const B: u32 = 42;
12129 const C: u32 = 42;
12130 const D: u32 = 42;
12131
12132
12133 fn main() {
12134 println!("hello");
12135
12136 println!("world");
12137 }"#
12138 .unindent();
12139
12140 cx.set_state(
12141 &r#"
12142 use some::mod1;
12143 use some::mod2;
12144
12145 const A: u32 = 42;
12146 const B: u32 = 42;
12147 const C: u32 = 43ˇ
12148 const D: u32 = 42;
12149
12150
12151 fn main() {
12152 println!("hello");
12153
12154 println!("world");
12155 }"#
12156 .unindent(),
12157 );
12158
12159 cx.set_diff_base(Some(&diff_base));
12160 executor.run_until_parked();
12161 cx.update_editor(|editor, cx| {
12162 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12163 });
12164 executor.run_until_parked();
12165
12166 cx.assert_diff_hunks(
12167 r#"
12168 use some::mod1;
12169 use some::mod2;
12170
12171 const A: u32 = 42;
12172 const B: u32 = 42;
12173 - const C: u32 = 42;
12174 + const C: u32 = 43
12175 const D: u32 = 42;
12176
12177
12178 fn main() {
12179 println!("hello");
12180
12181 println!("world");
12182 }"#
12183 .unindent(),
12184 );
12185
12186 cx.update_editor(|editor, cx| {
12187 editor.handle_input("\nnew_line\n", cx);
12188 });
12189 executor.run_until_parked();
12190
12191 cx.assert_diff_hunks(
12192 r#"
12193 use some::mod1;
12194 use some::mod2;
12195
12196 const A: u32 = 42;
12197 const B: u32 = 42;
12198 - const C: u32 = 42;
12199 + const C: u32 = 43
12200 + new_line
12201 +
12202 const D: u32 = 42;
12203
12204
12205 fn main() {
12206 println!("hello");
12207
12208 println!("world");
12209 }"#
12210 .unindent(),
12211 );
12212}
12213
12214async fn setup_indent_guides_editor(
12215 text: &str,
12216 cx: &mut gpui::TestAppContext,
12217) -> (BufferId, EditorTestContext) {
12218 init_test(cx, |_| {});
12219
12220 let mut cx = EditorTestContext::new(cx).await;
12221
12222 let buffer_id = cx.update_editor(|editor, cx| {
12223 editor.set_text(text, cx);
12224 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12225
12226 buffer_ids[0]
12227 });
12228
12229 (buffer_id, cx)
12230}
12231
12232fn assert_indent_guides(
12233 range: Range<u32>,
12234 expected: Vec<IndentGuide>,
12235 active_indices: Option<Vec<usize>>,
12236 cx: &mut EditorTestContext,
12237) {
12238 let indent_guides = cx.update_editor(|editor, cx| {
12239 let snapshot = editor.snapshot(cx).display_snapshot;
12240 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12241 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12242 true,
12243 &snapshot,
12244 cx,
12245 );
12246
12247 indent_guides.sort_by(|a, b| {
12248 a.depth.cmp(&b.depth).then(
12249 a.start_row
12250 .cmp(&b.start_row)
12251 .then(a.end_row.cmp(&b.end_row)),
12252 )
12253 });
12254 indent_guides
12255 });
12256
12257 if let Some(expected) = active_indices {
12258 let active_indices = cx.update_editor(|editor, cx| {
12259 let snapshot = editor.snapshot(cx).display_snapshot;
12260 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12261 });
12262
12263 assert_eq!(
12264 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12265 expected,
12266 "Active indent guide indices do not match"
12267 );
12268 }
12269
12270 let expected: Vec<_> = expected
12271 .into_iter()
12272 .map(|guide| MultiBufferIndentGuide {
12273 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12274 buffer: guide,
12275 })
12276 .collect();
12277
12278 assert_eq!(indent_guides, expected, "Indent guides do not match");
12279}
12280
12281fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12282 IndentGuide {
12283 buffer_id,
12284 start_row,
12285 end_row,
12286 depth,
12287 tab_size: 4,
12288 settings: IndentGuideSettings {
12289 enabled: true,
12290 line_width: 1,
12291 active_line_width: 1,
12292 ..Default::default()
12293 },
12294 }
12295}
12296
12297#[gpui::test]
12298async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12299 let (buffer_id, mut cx) = setup_indent_guides_editor(
12300 &"
12301 fn main() {
12302 let a = 1;
12303 }"
12304 .unindent(),
12305 cx,
12306 )
12307 .await;
12308
12309 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12310}
12311
12312#[gpui::test]
12313async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12314 let (buffer_id, mut cx) = setup_indent_guides_editor(
12315 &"
12316 fn main() {
12317 let a = 1;
12318 let b = 2;
12319 }"
12320 .unindent(),
12321 cx,
12322 )
12323 .await;
12324
12325 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12326}
12327
12328#[gpui::test]
12329async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12330 let (buffer_id, mut cx) = setup_indent_guides_editor(
12331 &"
12332 fn main() {
12333 let a = 1;
12334 if a == 3 {
12335 let b = 2;
12336 } else {
12337 let c = 3;
12338 }
12339 }"
12340 .unindent(),
12341 cx,
12342 )
12343 .await;
12344
12345 assert_indent_guides(
12346 0..8,
12347 vec![
12348 indent_guide(buffer_id, 1, 6, 0),
12349 indent_guide(buffer_id, 3, 3, 1),
12350 indent_guide(buffer_id, 5, 5, 1),
12351 ],
12352 None,
12353 &mut cx,
12354 );
12355}
12356
12357#[gpui::test]
12358async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12359 let (buffer_id, mut cx) = setup_indent_guides_editor(
12360 &"
12361 fn main() {
12362 let a = 1;
12363 let b = 2;
12364 let c = 3;
12365 }"
12366 .unindent(),
12367 cx,
12368 )
12369 .await;
12370
12371 assert_indent_guides(
12372 0..5,
12373 vec![
12374 indent_guide(buffer_id, 1, 3, 0),
12375 indent_guide(buffer_id, 2, 2, 1),
12376 ],
12377 None,
12378 &mut cx,
12379 );
12380}
12381
12382#[gpui::test]
12383async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12384 let (buffer_id, mut cx) = setup_indent_guides_editor(
12385 &"
12386 fn main() {
12387 let a = 1;
12388
12389 let c = 3;
12390 }"
12391 .unindent(),
12392 cx,
12393 )
12394 .await;
12395
12396 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12397}
12398
12399#[gpui::test]
12400async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12401 let (buffer_id, mut cx) = setup_indent_guides_editor(
12402 &"
12403 fn main() {
12404 let a = 1;
12405
12406 let c = 3;
12407
12408 if a == 3 {
12409 let b = 2;
12410 } else {
12411 let c = 3;
12412 }
12413 }"
12414 .unindent(),
12415 cx,
12416 )
12417 .await;
12418
12419 assert_indent_guides(
12420 0..11,
12421 vec![
12422 indent_guide(buffer_id, 1, 9, 0),
12423 indent_guide(buffer_id, 6, 6, 1),
12424 indent_guide(buffer_id, 8, 8, 1),
12425 ],
12426 None,
12427 &mut cx,
12428 );
12429}
12430
12431#[gpui::test]
12432async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12433 let (buffer_id, mut cx) = setup_indent_guides_editor(
12434 &"
12435 fn main() {
12436 let a = 1;
12437
12438 let c = 3;
12439
12440 if a == 3 {
12441 let b = 2;
12442 } else {
12443 let c = 3;
12444 }
12445 }"
12446 .unindent(),
12447 cx,
12448 )
12449 .await;
12450
12451 assert_indent_guides(
12452 1..11,
12453 vec![
12454 indent_guide(buffer_id, 1, 9, 0),
12455 indent_guide(buffer_id, 6, 6, 1),
12456 indent_guide(buffer_id, 8, 8, 1),
12457 ],
12458 None,
12459 &mut cx,
12460 );
12461}
12462
12463#[gpui::test]
12464async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12465 let (buffer_id, mut cx) = setup_indent_guides_editor(
12466 &"
12467 fn main() {
12468 let a = 1;
12469
12470 let c = 3;
12471
12472 if a == 3 {
12473 let b = 2;
12474 } else {
12475 let c = 3;
12476 }
12477 }"
12478 .unindent(),
12479 cx,
12480 )
12481 .await;
12482
12483 assert_indent_guides(
12484 1..10,
12485 vec![
12486 indent_guide(buffer_id, 1, 9, 0),
12487 indent_guide(buffer_id, 6, 6, 1),
12488 indent_guide(buffer_id, 8, 8, 1),
12489 ],
12490 None,
12491 &mut cx,
12492 );
12493}
12494
12495#[gpui::test]
12496async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12497 let (buffer_id, mut cx) = setup_indent_guides_editor(
12498 &"
12499 block1
12500 block2
12501 block3
12502 block4
12503 block2
12504 block1
12505 block1"
12506 .unindent(),
12507 cx,
12508 )
12509 .await;
12510
12511 assert_indent_guides(
12512 1..10,
12513 vec![
12514 indent_guide(buffer_id, 1, 4, 0),
12515 indent_guide(buffer_id, 2, 3, 1),
12516 indent_guide(buffer_id, 3, 3, 2),
12517 ],
12518 None,
12519 &mut cx,
12520 );
12521}
12522
12523#[gpui::test]
12524async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12525 let (buffer_id, mut cx) = setup_indent_guides_editor(
12526 &"
12527 block1
12528 block2
12529 block3
12530
12531 block1
12532 block1"
12533 .unindent(),
12534 cx,
12535 )
12536 .await;
12537
12538 assert_indent_guides(
12539 0..6,
12540 vec![
12541 indent_guide(buffer_id, 1, 2, 0),
12542 indent_guide(buffer_id, 2, 2, 1),
12543 ],
12544 None,
12545 &mut cx,
12546 );
12547}
12548
12549#[gpui::test]
12550async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12551 let (buffer_id, mut cx) = setup_indent_guides_editor(
12552 &"
12553 block1
12554
12555
12556
12557 block2
12558 "
12559 .unindent(),
12560 cx,
12561 )
12562 .await;
12563
12564 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12565}
12566
12567#[gpui::test]
12568async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12569 let (buffer_id, mut cx) = setup_indent_guides_editor(
12570 &"
12571 def a:
12572 \tb = 3
12573 \tif True:
12574 \t\tc = 4
12575 \t\td = 5
12576 \tprint(b)
12577 "
12578 .unindent(),
12579 cx,
12580 )
12581 .await;
12582
12583 assert_indent_guides(
12584 0..6,
12585 vec![
12586 indent_guide(buffer_id, 1, 6, 0),
12587 indent_guide(buffer_id, 3, 4, 1),
12588 ],
12589 None,
12590 &mut cx,
12591 );
12592}
12593
12594#[gpui::test]
12595async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12596 let (buffer_id, mut cx) = setup_indent_guides_editor(
12597 &"
12598 fn main() {
12599 let a = 1;
12600 }"
12601 .unindent(),
12602 cx,
12603 )
12604 .await;
12605
12606 cx.update_editor(|editor, cx| {
12607 editor.change_selections(None, cx, |s| {
12608 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12609 });
12610 });
12611
12612 assert_indent_guides(
12613 0..3,
12614 vec![indent_guide(buffer_id, 1, 1, 0)],
12615 Some(vec![0]),
12616 &mut cx,
12617 );
12618}
12619
12620#[gpui::test]
12621async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12622 let (buffer_id, mut cx) = setup_indent_guides_editor(
12623 &"
12624 fn main() {
12625 if 1 == 2 {
12626 let a = 1;
12627 }
12628 }"
12629 .unindent(),
12630 cx,
12631 )
12632 .await;
12633
12634 cx.update_editor(|editor, cx| {
12635 editor.change_selections(None, cx, |s| {
12636 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12637 });
12638 });
12639
12640 assert_indent_guides(
12641 0..4,
12642 vec![
12643 indent_guide(buffer_id, 1, 3, 0),
12644 indent_guide(buffer_id, 2, 2, 1),
12645 ],
12646 Some(vec![1]),
12647 &mut cx,
12648 );
12649
12650 cx.update_editor(|editor, cx| {
12651 editor.change_selections(None, cx, |s| {
12652 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12653 });
12654 });
12655
12656 assert_indent_guides(
12657 0..4,
12658 vec![
12659 indent_guide(buffer_id, 1, 3, 0),
12660 indent_guide(buffer_id, 2, 2, 1),
12661 ],
12662 Some(vec![1]),
12663 &mut cx,
12664 );
12665
12666 cx.update_editor(|editor, cx| {
12667 editor.change_selections(None, cx, |s| {
12668 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12669 });
12670 });
12671
12672 assert_indent_guides(
12673 0..4,
12674 vec![
12675 indent_guide(buffer_id, 1, 3, 0),
12676 indent_guide(buffer_id, 2, 2, 1),
12677 ],
12678 Some(vec![0]),
12679 &mut cx,
12680 );
12681}
12682
12683#[gpui::test]
12684async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12685 let (buffer_id, mut cx) = setup_indent_guides_editor(
12686 &"
12687 fn main() {
12688 let a = 1;
12689
12690 let b = 2;
12691 }"
12692 .unindent(),
12693 cx,
12694 )
12695 .await;
12696
12697 cx.update_editor(|editor, cx| {
12698 editor.change_selections(None, cx, |s| {
12699 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12700 });
12701 });
12702
12703 assert_indent_guides(
12704 0..5,
12705 vec![indent_guide(buffer_id, 1, 3, 0)],
12706 Some(vec![0]),
12707 &mut cx,
12708 );
12709}
12710
12711#[gpui::test]
12712async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12713 let (buffer_id, mut cx) = setup_indent_guides_editor(
12714 &"
12715 def m:
12716 a = 1
12717 pass"
12718 .unindent(),
12719 cx,
12720 )
12721 .await;
12722
12723 cx.update_editor(|editor, cx| {
12724 editor.change_selections(None, cx, |s| {
12725 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12726 });
12727 });
12728
12729 assert_indent_guides(
12730 0..3,
12731 vec![indent_guide(buffer_id, 1, 2, 0)],
12732 Some(vec![0]),
12733 &mut cx,
12734 );
12735}
12736
12737#[gpui::test]
12738fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12739 init_test(cx, |_| {});
12740
12741 let editor = cx.add_window(|cx| {
12742 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12743 build_editor(buffer, cx)
12744 });
12745
12746 let render_args = Arc::new(Mutex::new(None));
12747 let snapshot = editor
12748 .update(cx, |editor, cx| {
12749 let snapshot = editor.buffer().read(cx).snapshot(cx);
12750 let range =
12751 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12752
12753 struct RenderArgs {
12754 row: MultiBufferRow,
12755 folded: bool,
12756 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12757 }
12758
12759 let crease = Crease::new(
12760 range,
12761 FoldPlaceholder::test(),
12762 {
12763 let toggle_callback = render_args.clone();
12764 move |row, folded, callback, _cx| {
12765 *toggle_callback.lock() = Some(RenderArgs {
12766 row,
12767 folded,
12768 callback,
12769 });
12770 div()
12771 }
12772 },
12773 |_row, _folded, _cx| div(),
12774 );
12775
12776 editor.insert_creases(Some(crease), cx);
12777 let snapshot = editor.snapshot(cx);
12778 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12779 snapshot
12780 })
12781 .unwrap();
12782
12783 let render_args = render_args.lock().take().unwrap();
12784 assert_eq!(render_args.row, MultiBufferRow(1));
12785 assert!(!render_args.folded);
12786 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12787
12788 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12789 .unwrap();
12790 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12791 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12792
12793 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12794 .unwrap();
12795 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12796 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12797}
12798
12799#[gpui::test]
12800async fn test_input_text(cx: &mut gpui::TestAppContext) {
12801 init_test(cx, |_| {});
12802 let mut cx = EditorTestContext::new(cx).await;
12803
12804 cx.set_state(
12805 &r#"ˇone
12806 two
12807
12808 three
12809 fourˇ
12810 five
12811
12812 siˇx"#
12813 .unindent(),
12814 );
12815
12816 cx.dispatch_action(HandleInput(String::new()));
12817 cx.assert_editor_state(
12818 &r#"ˇone
12819 two
12820
12821 three
12822 fourˇ
12823 five
12824
12825 siˇx"#
12826 .unindent(),
12827 );
12828
12829 cx.dispatch_action(HandleInput("AAAA".to_string()));
12830 cx.assert_editor_state(
12831 &r#"AAAAˇone
12832 two
12833
12834 three
12835 fourAAAAˇ
12836 five
12837
12838 siAAAAˇx"#
12839 .unindent(),
12840 );
12841}
12842
12843#[gpui::test]
12844async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
12845 init_test(cx, |_| {});
12846
12847 let mut cx = EditorTestContext::new(cx).await;
12848 cx.set_state(
12849 r#"let foo = 1;
12850let foo = 2;
12851let foo = 3;
12852let fooˇ = 4;
12853let foo = 5;
12854let foo = 6;
12855let foo = 7;
12856let foo = 8;
12857let foo = 9;
12858let foo = 10;
12859let foo = 11;
12860let foo = 12;
12861let foo = 13;
12862let foo = 14;
12863let foo = 15;"#,
12864 );
12865
12866 cx.update_editor(|e, cx| {
12867 assert_eq!(
12868 e.next_scroll_position,
12869 NextScrollCursorCenterTopBottom::Center,
12870 "Default next scroll direction is center",
12871 );
12872
12873 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12874 assert_eq!(
12875 e.next_scroll_position,
12876 NextScrollCursorCenterTopBottom::Top,
12877 "After center, next scroll direction should be top",
12878 );
12879
12880 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12881 assert_eq!(
12882 e.next_scroll_position,
12883 NextScrollCursorCenterTopBottom::Bottom,
12884 "After top, next scroll direction should be bottom",
12885 );
12886
12887 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12888 assert_eq!(
12889 e.next_scroll_position,
12890 NextScrollCursorCenterTopBottom::Center,
12891 "After bottom, scrolling should start over",
12892 );
12893
12894 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
12895 assert_eq!(
12896 e.next_scroll_position,
12897 NextScrollCursorCenterTopBottom::Top,
12898 "Scrolling continues if retriggered fast enough"
12899 );
12900 });
12901
12902 cx.executor()
12903 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
12904 cx.executor().run_until_parked();
12905 cx.update_editor(|e, _| {
12906 assert_eq!(
12907 e.next_scroll_position,
12908 NextScrollCursorCenterTopBottom::Center,
12909 "If scrolling is not triggered fast enough, it should reset"
12910 );
12911 });
12912}
12913
12914#[gpui::test]
12915async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
12916 init_test(cx, |_| {});
12917 let mut cx = EditorLspTestContext::new_rust(
12918 lsp::ServerCapabilities {
12919 definition_provider: Some(lsp::OneOf::Left(true)),
12920 references_provider: Some(lsp::OneOf::Left(true)),
12921 ..lsp::ServerCapabilities::default()
12922 },
12923 cx,
12924 )
12925 .await;
12926
12927 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
12928 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
12929 move |params, _| async move {
12930 if empty_go_to_definition {
12931 Ok(None)
12932 } else {
12933 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
12934 uri: params.text_document_position_params.text_document.uri,
12935 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
12936 })))
12937 }
12938 },
12939 );
12940 let references =
12941 cx.lsp
12942 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
12943 Ok(Some(vec![lsp::Location {
12944 uri: params.text_document_position.text_document.uri,
12945 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
12946 }]))
12947 });
12948 (go_to_definition, references)
12949 };
12950
12951 cx.set_state(
12952 &r#"fn one() {
12953 let mut a = ˇtwo();
12954 }
12955
12956 fn two() {}"#
12957 .unindent(),
12958 );
12959 set_up_lsp_handlers(false, &mut cx);
12960 let navigated = cx
12961 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
12962 .await
12963 .expect("Failed to navigate to definition");
12964 assert_eq!(
12965 navigated,
12966 Navigated::Yes,
12967 "Should have navigated to definition from the GetDefinition response"
12968 );
12969 cx.assert_editor_state(
12970 &r#"fn one() {
12971 let mut a = two();
12972 }
12973
12974 fn «twoˇ»() {}"#
12975 .unindent(),
12976 );
12977
12978 let editors = cx.update_workspace(|workspace, cx| {
12979 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
12980 });
12981 cx.update_editor(|_, test_editor_cx| {
12982 assert_eq!(
12983 editors.len(),
12984 1,
12985 "Initially, only one, test, editor should be open in the workspace"
12986 );
12987 assert_eq!(
12988 test_editor_cx.view(),
12989 editors.last().expect("Asserted len is 1")
12990 );
12991 });
12992
12993 set_up_lsp_handlers(true, &mut cx);
12994 let navigated = cx
12995 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
12996 .await
12997 .expect("Failed to navigate to lookup references");
12998 assert_eq!(
12999 navigated,
13000 Navigated::Yes,
13001 "Should have navigated to references as a fallback after empty GoToDefinition response"
13002 );
13003 // We should not change the selections in the existing file,
13004 // if opening another milti buffer with the references
13005 cx.assert_editor_state(
13006 &r#"fn one() {
13007 let mut a = two();
13008 }
13009
13010 fn «twoˇ»() {}"#
13011 .unindent(),
13012 );
13013 let editors = cx.update_workspace(|workspace, cx| {
13014 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13015 });
13016 cx.update_editor(|_, test_editor_cx| {
13017 assert_eq!(
13018 editors.len(),
13019 2,
13020 "After falling back to references search, we open a new editor with the results"
13021 );
13022 let references_fallback_text = editors
13023 .into_iter()
13024 .find(|new_editor| new_editor != test_editor_cx.view())
13025 .expect("Should have one non-test editor now")
13026 .read(test_editor_cx)
13027 .text(test_editor_cx);
13028 assert_eq!(
13029 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13030 "Should use the range from the references response and not the GoToDefinition one"
13031 );
13032 });
13033}
13034
13035fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13036 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13037 point..point
13038}
13039
13040fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13041 let (text, ranges) = marked_text_ranges(marked_text, true);
13042 assert_eq!(view.text(cx), text);
13043 assert_eq!(
13044 view.selections.ranges(cx),
13045 ranges,
13046 "Assert selections are {}",
13047 marked_text
13048 );
13049}
13050
13051pub fn handle_signature_help_request(
13052 cx: &mut EditorLspTestContext,
13053 mocked_response: lsp::SignatureHelp,
13054) -> impl Future<Output = ()> {
13055 let mut request =
13056 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13057 let mocked_response = mocked_response.clone();
13058 async move { Ok(Some(mocked_response)) }
13059 });
13060
13061 async move {
13062 request.next().await;
13063 }
13064}
13065
13066/// Handle completion request passing a marked string specifying where the completion
13067/// should be triggered from using '|' character, what range should be replaced, and what completions
13068/// should be returned using '<' and '>' to delimit the range
13069pub fn handle_completion_request(
13070 cx: &mut EditorLspTestContext,
13071 marked_string: &str,
13072 completions: Vec<&'static str>,
13073 counter: Arc<AtomicUsize>,
13074) -> impl Future<Output = ()> {
13075 let complete_from_marker: TextRangeMarker = '|'.into();
13076 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13077 let (_, mut marked_ranges) = marked_text_ranges_by(
13078 marked_string,
13079 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13080 );
13081
13082 let complete_from_position =
13083 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13084 let replace_range =
13085 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13086
13087 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13088 let completions = completions.clone();
13089 counter.fetch_add(1, atomic::Ordering::Release);
13090 async move {
13091 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13092 assert_eq!(
13093 params.text_document_position.position,
13094 complete_from_position
13095 );
13096 Ok(Some(lsp::CompletionResponse::Array(
13097 completions
13098 .iter()
13099 .map(|completion_text| lsp::CompletionItem {
13100 label: completion_text.to_string(),
13101 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13102 range: replace_range,
13103 new_text: completion_text.to_string(),
13104 })),
13105 ..Default::default()
13106 })
13107 .collect(),
13108 )))
13109 }
13110 });
13111
13112 async move {
13113 request.next().await;
13114 }
13115}
13116
13117fn handle_resolve_completion_request(
13118 cx: &mut EditorLspTestContext,
13119 edits: Option<Vec<(&'static str, &'static str)>>,
13120) -> impl Future<Output = ()> {
13121 let edits = edits.map(|edits| {
13122 edits
13123 .iter()
13124 .map(|(marked_string, new_text)| {
13125 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13126 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13127 lsp::TextEdit::new(replace_range, new_text.to_string())
13128 })
13129 .collect::<Vec<_>>()
13130 });
13131
13132 let mut request =
13133 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13134 let edits = edits.clone();
13135 async move {
13136 Ok(lsp::CompletionItem {
13137 additional_text_edits: edits,
13138 ..Default::default()
13139 })
13140 }
13141 });
13142
13143 async move {
13144 request.next().await;
13145 }
13146}
13147
13148pub(crate) fn update_test_language_settings(
13149 cx: &mut TestAppContext,
13150 f: impl Fn(&mut AllLanguageSettingsContent),
13151) {
13152 cx.update(|cx| {
13153 SettingsStore::update_global(cx, |store, cx| {
13154 store.update_user_settings::<AllLanguageSettings>(cx, f);
13155 });
13156 });
13157}
13158
13159pub(crate) fn update_test_project_settings(
13160 cx: &mut TestAppContext,
13161 f: impl Fn(&mut ProjectSettings),
13162) {
13163 cx.update(|cx| {
13164 SettingsStore::update_global(cx, |store, cx| {
13165 store.update_user_settings::<ProjectSettings>(cx, f);
13166 });
13167 });
13168}
13169
13170pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13171 cx.update(|cx| {
13172 assets::Assets.load_test_fonts(cx);
13173 let store = SettingsStore::test(cx);
13174 cx.set_global(store);
13175 theme::init(theme::LoadThemes::JustBase, cx);
13176 release_channel::init(SemanticVersion::default(), cx);
13177 client::init_settings(cx);
13178 language::init(cx);
13179 Project::init_settings(cx);
13180 workspace::init_settings(cx);
13181 crate::init(cx);
13182 });
13183
13184 update_test_language_settings(cx, f);
13185}
13186
13187pub(crate) fn rust_lang() -> Arc<Language> {
13188 Arc::new(Language::new(
13189 LanguageConfig {
13190 name: "Rust".into(),
13191 matcher: LanguageMatcher {
13192 path_suffixes: vec!["rs".to_string()],
13193 ..Default::default()
13194 },
13195 ..Default::default()
13196 },
13197 Some(tree_sitter_rust::LANGUAGE.into()),
13198 ))
13199}
13200
13201#[track_caller]
13202fn assert_hunk_revert(
13203 not_reverted_text_with_selections: &str,
13204 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13205 expected_reverted_text_with_selections: &str,
13206 base_text: &str,
13207 cx: &mut EditorLspTestContext,
13208) {
13209 cx.set_state(not_reverted_text_with_selections);
13210 cx.update_editor(|editor, cx| {
13211 editor
13212 .buffer()
13213 .read(cx)
13214 .as_singleton()
13215 .unwrap()
13216 .update(cx, |buffer, cx| {
13217 buffer.set_diff_base(Some(base_text.into()), cx);
13218 });
13219 });
13220 cx.executor().run_until_parked();
13221
13222 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13223 let snapshot = editor.buffer().read(cx).snapshot(cx);
13224 let reverted_hunk_statuses = snapshot
13225 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13226 .map(|hunk| hunk_status(&hunk))
13227 .collect::<Vec<_>>();
13228
13229 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13230 reverted_hunk_statuses
13231 });
13232 cx.executor().run_until_parked();
13233 cx.assert_editor_state(expected_reverted_text_with_selections);
13234 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13235}