1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic;
35use std::sync::atomic::AtomicUsize;
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
173 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
174 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
175 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
176
177 _ = editor.update(cx, |editor, cx| {
178 editor.start_transaction_at(now, cx);
179 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
180
181 editor.insert("cd", cx);
182 editor.end_transaction_at(now, cx);
183 assert_eq!(editor.text(cx), "12cd56");
184 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
185
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
188 editor.insert("e", cx);
189 editor.end_transaction_at(now, cx);
190 assert_eq!(editor.text(cx), "12cde6");
191 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
192
193 now += group_interval + Duration::from_millis(1);
194 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
195
196 // Simulate an edit in another editor
197 buffer.update(cx, |buffer, cx| {
198 buffer.start_transaction_at(now, cx);
199 buffer.edit([(0..1, "a")], None, cx);
200 buffer.edit([(1..1, "b")], None, cx);
201 buffer.end_transaction_at(now, cx);
202 });
203
204 assert_eq!(editor.text(cx), "ab2cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
206
207 // Last transaction happened past the group interval in a different editor.
208 // Undo it individually and don't restore selections.
209 editor.undo(&Undo, cx);
210 assert_eq!(editor.text(cx), "12cde6");
211 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
212
213 // First two transactions happened within the group interval in this editor.
214 // Undo them together and restore selections.
215 editor.undo(&Undo, cx);
216 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
217 assert_eq!(editor.text(cx), "123456");
218 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
219
220 // Redo the first two transactions together.
221 editor.redo(&Redo, cx);
222 assert_eq!(editor.text(cx), "12cde6");
223 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
224
225 // Redo the last transaction on its own.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "ab2cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
229
230 // Test empty transactions.
231 editor.start_transaction_at(now, cx);
232 editor.end_transaction_at(now, cx);
233 editor.undo(&Undo, cx);
234 assert_eq!(editor.text(cx), "12cde6");
235 });
236}
237
238#[gpui::test]
239fn test_ime_composition(cx: &mut TestAppContext) {
240 init_test(cx, |_| {});
241
242 let buffer = cx.new_model(|cx| {
243 let mut buffer = language::Buffer::local("abcde", cx);
244 // Ensure automatic grouping doesn't occur.
245 buffer.set_group_interval(Duration::ZERO);
246 buffer
247 });
248
249 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
250 cx.add_window(|cx| {
251 let mut editor = build_editor(buffer.clone(), cx);
252
253 // Start a new IME composition.
254 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
255 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
257 assert_eq!(editor.text(cx), "äbcde");
258 assert_eq!(
259 editor.marked_text_ranges(cx),
260 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
261 );
262
263 // Finalize IME composition.
264 editor.replace_text_in_range(None, "ā", cx);
265 assert_eq!(editor.text(cx), "ābcde");
266 assert_eq!(editor.marked_text_ranges(cx), None);
267
268 // IME composition edits are grouped and are undone/redone at once.
269 editor.undo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "abcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272 editor.redo(&Default::default(), cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // Start a new IME composition.
277 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
281 );
282
283 // Undoing during an IME composition cancels it.
284 editor.undo(&Default::default(), cx);
285 assert_eq!(editor.text(cx), "ābcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
289 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
290 assert_eq!(editor.text(cx), "ābcdè");
291 assert_eq!(
292 editor.marked_text_ranges(cx),
293 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
294 );
295
296 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
297 editor.replace_text_in_range(Some(4..999), "ę", cx);
298 assert_eq!(editor.text(cx), "ābcdę");
299 assert_eq!(editor.marked_text_ranges(cx), None);
300
301 // Start a new IME composition with multiple cursors.
302 editor.change_selections(None, cx, |s| {
303 s.select_ranges([
304 OffsetUtf16(1)..OffsetUtf16(1),
305 OffsetUtf16(3)..OffsetUtf16(3),
306 OffsetUtf16(5)..OffsetUtf16(5),
307 ])
308 });
309 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
310 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
311 assert_eq!(
312 editor.marked_text_ranges(cx),
313 Some(vec![
314 OffsetUtf16(0)..OffsetUtf16(3),
315 OffsetUtf16(4)..OffsetUtf16(7),
316 OffsetUtf16(8)..OffsetUtf16(11)
317 ])
318 );
319
320 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
321 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
322 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
323 assert_eq!(
324 editor.marked_text_ranges(cx),
325 Some(vec![
326 OffsetUtf16(1)..OffsetUtf16(2),
327 OffsetUtf16(5)..OffsetUtf16(6),
328 OffsetUtf16(9)..OffsetUtf16(10)
329 ])
330 );
331
332 // Finalize IME composition with multiple cursors.
333 editor.replace_text_in_range(Some(9..10), "2", cx);
334 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
335 assert_eq!(editor.marked_text_ranges(cx), None);
336
337 editor
338 });
339}
340
341#[gpui::test]
342fn test_selection_with_mouse(cx: &mut TestAppContext) {
343 init_test(cx, |_| {});
344
345 let editor = cx.add_window(|cx| {
346 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
347 build_editor(buffer, cx)
348 });
349
350 _ = editor.update(cx, |view, cx| {
351 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
352 });
353 assert_eq!(
354 editor
355 .update(cx, |view, cx| view.selections.display_ranges(cx))
356 .unwrap(),
357 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
358 );
359
360 _ = editor.update(cx, |view, cx| {
361 view.update_selection(
362 DisplayPoint::new(DisplayRow(3), 3),
363 0,
364 gpui::Point::<f32>::default(),
365 cx,
366 );
367 });
368
369 assert_eq!(
370 editor
371 .update(cx, |view, cx| view.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
374 );
375
376 _ = editor.update(cx, |view, cx| {
377 view.update_selection(
378 DisplayPoint::new(DisplayRow(1), 1),
379 0,
380 gpui::Point::<f32>::default(),
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |view, cx| view.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
390 );
391
392 _ = editor.update(cx, |view, cx| {
393 view.end_selection(cx);
394 view.update_selection(
395 DisplayPoint::new(DisplayRow(3), 3),
396 0,
397 gpui::Point::<f32>::default(),
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |view, cx| view.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |view, cx| {
410 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
411 view.update_selection(
412 DisplayPoint::new(DisplayRow(0), 0),
413 0,
414 gpui::Point::<f32>::default(),
415 cx,
416 );
417 });
418
419 assert_eq!(
420 editor
421 .update(cx, |view, cx| view.selections.display_ranges(cx))
422 .unwrap(),
423 [
424 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
425 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
426 ]
427 );
428
429 _ = editor.update(cx, |view, cx| {
430 view.end_selection(cx);
431 });
432
433 assert_eq!(
434 editor
435 .update(cx, |view, cx| view.selections.display_ranges(cx))
436 .unwrap(),
437 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
438 );
439}
440
441#[gpui::test]
442fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
443 init_test(cx, |_| {});
444
445 let editor = cx.add_window(|cx| {
446 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
447 build_editor(buffer, cx)
448 });
449
450 _ = editor.update(cx, |view, cx| {
451 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.end_selection(cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 assert_eq!(
467 editor
468 .update(cx, |view, cx| view.selections.display_ranges(cx))
469 .unwrap(),
470 [
471 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
472 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
473 ]
474 );
475
476 _ = editor.update(cx, |view, cx| {
477 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
478 });
479
480 _ = editor.update(cx, |view, cx| {
481 view.end_selection(cx);
482 });
483
484 assert_eq!(
485 editor
486 .update(cx, |view, cx| view.selections.display_ranges(cx))
487 .unwrap(),
488 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
489 );
490}
491
492#[gpui::test]
493fn test_canceling_pending_selection(cx: &mut TestAppContext) {
494 init_test(cx, |_| {});
495
496 let view = cx.add_window(|cx| {
497 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
498 build_editor(buffer, cx)
499 });
500
501 _ = view.update(cx, |view, cx| {
502 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
503 assert_eq!(
504 view.selections.display_ranges(cx),
505 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
506 );
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.update_selection(
511 DisplayPoint::new(DisplayRow(3), 3),
512 0,
513 gpui::Point::<f32>::default(),
514 cx,
515 );
516 assert_eq!(
517 view.selections.display_ranges(cx),
518 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
519 );
520 });
521
522 _ = view.update(cx, |view, cx| {
523 view.cancel(&Cancel, cx);
524 view.update_selection(
525 DisplayPoint::new(DisplayRow(1), 1),
526 0,
527 gpui::Point::<f32>::default(),
528 cx,
529 );
530 assert_eq!(
531 view.selections.display_ranges(cx),
532 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
533 );
534 });
535}
536
537#[gpui::test]
538fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
539 init_test(cx, |_| {});
540
541 let view = cx.add_window(|cx| {
542 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
543 build_editor(buffer, cx)
544 });
545
546 _ = view.update(cx, |view, cx| {
547 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
548 assert_eq!(
549 view.selections.display_ranges(cx),
550 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
551 );
552
553 view.move_down(&Default::default(), cx);
554 assert_eq!(
555 view.selections.display_ranges(cx),
556 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
557 );
558
559 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
560 assert_eq!(
561 view.selections.display_ranges(cx),
562 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
563 );
564
565 view.move_up(&Default::default(), cx);
566 assert_eq!(
567 view.selections.display_ranges(cx),
568 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
569 );
570 });
571}
572
573#[gpui::test]
574fn test_clone(cx: &mut TestAppContext) {
575 init_test(cx, |_| {});
576
577 let (text, selection_ranges) = marked_text_ranges(
578 indoc! {"
579 one
580 two
581 threeˇ
582 four
583 fiveˇ
584 "},
585 true,
586 );
587
588 let editor = cx.add_window(|cx| {
589 let buffer = MultiBuffer::build_simple(&text, cx);
590 build_editor(buffer, cx)
591 });
592
593 _ = editor.update(cx, |editor, cx| {
594 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
595 editor.fold_ranges(
596 [
597 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
598 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
599 ],
600 true,
601 cx,
602 );
603 });
604
605 let cloned_editor = editor
606 .update(cx, |editor, cx| {
607 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
608 })
609 .unwrap()
610 .unwrap();
611
612 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
613 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614
615 assert_eq!(
616 cloned_editor
617 .update(cx, |e, cx| e.display_text(cx))
618 .unwrap(),
619 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
620 );
621 assert_eq!(
622 cloned_snapshot
623 .folds_in_range(0..text.len())
624 .collect::<Vec<_>>(),
625 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
626 );
627 assert_set_eq!(
628 cloned_editor
629 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
630 .unwrap(),
631 editor
632 .update(cx, |editor, cx| editor.selections.ranges(cx))
633 .unwrap()
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |e, cx| e.selections.display_ranges(cx))
638 .unwrap(),
639 editor
640 .update(cx, |e, cx| e.selections.display_ranges(cx))
641 .unwrap()
642 );
643}
644
645#[gpui::test]
646async fn test_navigation_history(cx: &mut TestAppContext) {
647 init_test(cx, |_| {});
648
649 use workspace::item::Item;
650
651 let fs = FakeFs::new(cx.executor());
652 let project = Project::test(fs, [], cx).await;
653 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
654 let pane = workspace
655 .update(cx, |workspace, _| workspace.active_pane().clone())
656 .unwrap();
657
658 _ = workspace.update(cx, |_v, cx| {
659 cx.new_view(|cx| {
660 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
661 let mut editor = build_editor(buffer.clone(), cx);
662 let handle = cx.view();
663 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
664
665 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
666 editor.nav_history.as_mut().unwrap().pop_backward(cx)
667 }
668
669 // Move the cursor a small distance.
670 // Nothing is added to the navigation history.
671 editor.change_selections(None, cx, |s| {
672 s.select_display_ranges([
673 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
674 ])
675 });
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
679 ])
680 });
681 assert!(pop_history(&mut editor, cx).is_none());
682
683 // Move the cursor a large distance.
684 // The history can jump back to the previous position.
685 editor.change_selections(None, cx, |s| {
686 s.select_display_ranges([
687 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
688 ])
689 });
690 let nav_entry = pop_history(&mut editor, cx).unwrap();
691 editor.navigate(nav_entry.data.unwrap(), cx);
692 assert_eq!(nav_entry.item.id(), cx.entity_id());
693 assert_eq!(
694 editor.selections.display_ranges(cx),
695 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
696 );
697 assert!(pop_history(&mut editor, cx).is_none());
698
699 // Move the cursor a small distance via the mouse.
700 // Nothing is added to the navigation history.
701 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
702 editor.end_selection(cx);
703 assert_eq!(
704 editor.selections.display_ranges(cx),
705 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
706 );
707 assert!(pop_history(&mut editor, cx).is_none());
708
709 // Move the cursor a large distance via the mouse.
710 // The history can jump back to the previous position.
711 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
712 editor.end_selection(cx);
713 assert_eq!(
714 editor.selections.display_ranges(cx),
715 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
716 );
717 let nav_entry = pop_history(&mut editor, cx).unwrap();
718 editor.navigate(nav_entry.data.unwrap(), cx);
719 assert_eq!(nav_entry.item.id(), cx.entity_id());
720 assert_eq!(
721 editor.selections.display_ranges(cx),
722 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
723 );
724 assert!(pop_history(&mut editor, cx).is_none());
725
726 // Set scroll position to check later
727 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
728 let original_scroll_position = editor.scroll_manager.anchor();
729
730 // Jump to the end of the document and adjust scroll
731 editor.move_to_end(&MoveToEnd, cx);
732 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
733 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
734
735 let nav_entry = pop_history(&mut editor, cx).unwrap();
736 editor.navigate(nav_entry.data.unwrap(), cx);
737 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
740 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
741 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
742 let invalid_point = Point::new(9999, 0);
743 editor.navigate(
744 Box::new(NavigationData {
745 cursor_anchor: invalid_anchor,
746 cursor_position: invalid_point,
747 scroll_anchor: ScrollAnchor {
748 anchor: invalid_anchor,
749 offset: Default::default(),
750 },
751 scroll_top_row: invalid_point.row,
752 }),
753 cx,
754 );
755 assert_eq!(
756 editor.selections.display_ranges(cx),
757 &[editor.max_point(cx)..editor.max_point(cx)]
758 );
759 assert_eq!(
760 editor.scroll_position(cx),
761 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
762 );
763
764 editor
765 })
766 });
767}
768
769#[gpui::test]
770fn test_cancel(cx: &mut TestAppContext) {
771 init_test(cx, |_| {});
772
773 let view = cx.add_window(|cx| {
774 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
775 build_editor(buffer, cx)
776 });
777
778 _ = view.update(cx, |view, cx| {
779 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
780 view.update_selection(
781 DisplayPoint::new(DisplayRow(1), 1),
782 0,
783 gpui::Point::<f32>::default(),
784 cx,
785 );
786 view.end_selection(cx);
787
788 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
789 view.update_selection(
790 DisplayPoint::new(DisplayRow(0), 3),
791 0,
792 gpui::Point::<f32>::default(),
793 cx,
794 );
795 view.end_selection(cx);
796 assert_eq!(
797 view.selections.display_ranges(cx),
798 [
799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
800 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
801 ]
802 );
803 });
804
805 _ = view.update(cx, |view, cx| {
806 view.cancel(&Cancel, cx);
807 assert_eq!(
808 view.selections.display_ranges(cx),
809 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820}
821
822#[gpui::test]
823fn test_fold_action(cx: &mut TestAppContext) {
824 init_test(cx, |_| {});
825
826 let view = cx.add_window(|cx| {
827 let buffer = MultiBuffer::build_simple(
828 &"
829 impl Foo {
830 // Hello!
831
832 fn a() {
833 1
834 }
835
836 fn b() {
837 2
838 }
839
840 fn c() {
841 3
842 }
843 }
844 "
845 .unindent(),
846 cx,
847 );
848 build_editor(buffer.clone(), cx)
849 });
850
851 _ = view.update(cx, |view, cx| {
852 view.change_selections(None, cx, |s| {
853 s.select_display_ranges([
854 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
855 ]);
856 });
857 view.fold(&Fold, cx);
858 assert_eq!(
859 view.display_text(cx),
860 "
861 impl Foo {
862 // Hello!
863
864 fn a() {
865 1
866 }
867
868 fn b() {⋯
869 }
870
871 fn c() {⋯
872 }
873 }
874 "
875 .unindent(),
876 );
877
878 view.fold(&Fold, cx);
879 assert_eq!(
880 view.display_text(cx),
881 "
882 impl Foo {⋯
883 }
884 "
885 .unindent(),
886 );
887
888 view.unfold_lines(&UnfoldLines, cx);
889 assert_eq!(
890 view.display_text(cx),
891 "
892 impl Foo {
893 // Hello!
894
895 fn a() {
896 1
897 }
898
899 fn b() {⋯
900 }
901
902 fn c() {⋯
903 }
904 }
905 "
906 .unindent(),
907 );
908
909 view.unfold_lines(&UnfoldLines, cx);
910 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
911 });
912}
913
914#[gpui::test]
915fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
916 init_test(cx, |_| {});
917
918 let view = cx.add_window(|cx| {
919 let buffer = MultiBuffer::build_simple(
920 &"
921 class Foo:
922 # Hello!
923
924 def a():
925 print(1)
926
927 def b():
928 print(2)
929
930 def c():
931 print(3)
932 "
933 .unindent(),
934 cx,
935 );
936 build_editor(buffer.clone(), cx)
937 });
938
939 _ = view.update(cx, |view, cx| {
940 view.change_selections(None, cx, |s| {
941 s.select_display_ranges([
942 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
943 ]);
944 });
945 view.fold(&Fold, cx);
946 assert_eq!(
947 view.display_text(cx),
948 "
949 class Foo:
950 # Hello!
951
952 def a():
953 print(1)
954
955 def b():⋯
956
957 def c():⋯
958 "
959 .unindent(),
960 );
961
962 view.fold(&Fold, cx);
963 assert_eq!(
964 view.display_text(cx),
965 "
966 class Foo:⋯
967 "
968 .unindent(),
969 );
970
971 view.unfold_lines(&UnfoldLines, cx);
972 assert_eq!(
973 view.display_text(cx),
974 "
975 class Foo:
976 # Hello!
977
978 def a():
979 print(1)
980
981 def b():⋯
982
983 def c():⋯
984 "
985 .unindent(),
986 );
987
988 view.unfold_lines(&UnfoldLines, cx);
989 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
990 });
991}
992
993#[gpui::test]
994fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
995 init_test(cx, |_| {});
996
997 let view = cx.add_window(|cx| {
998 let buffer = MultiBuffer::build_simple(
999 &"
1000 class Foo:
1001 # Hello!
1002
1003 def a():
1004 print(1)
1005
1006 def b():
1007 print(2)
1008
1009
1010 def c():
1011 print(3)
1012
1013
1014 "
1015 .unindent(),
1016 cx,
1017 );
1018 build_editor(buffer.clone(), cx)
1019 });
1020
1021 _ = view.update(cx, |view, cx| {
1022 view.change_selections(None, cx, |s| {
1023 s.select_display_ranges([
1024 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1025 ]);
1026 });
1027 view.fold(&Fold, cx);
1028 assert_eq!(
1029 view.display_text(cx),
1030 "
1031 class Foo:
1032 # Hello!
1033
1034 def a():
1035 print(1)
1036
1037 def b():⋯
1038
1039
1040 def c():⋯
1041
1042
1043 "
1044 .unindent(),
1045 );
1046
1047 view.fold(&Fold, cx);
1048 assert_eq!(
1049 view.display_text(cx),
1050 "
1051 class Foo:⋯
1052
1053
1054 "
1055 .unindent(),
1056 );
1057
1058 view.unfold_lines(&UnfoldLines, cx);
1059 assert_eq!(
1060 view.display_text(cx),
1061 "
1062 class Foo:
1063 # Hello!
1064
1065 def a():
1066 print(1)
1067
1068 def b():⋯
1069
1070
1071 def c():⋯
1072
1073
1074 "
1075 .unindent(),
1076 );
1077
1078 view.unfold_lines(&UnfoldLines, cx);
1079 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1080 });
1081}
1082
1083#[gpui::test]
1084fn test_fold_at_level(cx: &mut TestAppContext) {
1085 init_test(cx, |_| {});
1086
1087 let view = cx.add_window(|cx| {
1088 let buffer = MultiBuffer::build_simple(
1089 &"
1090 class Foo:
1091 # Hello!
1092
1093 def a():
1094 print(1)
1095
1096 def b():
1097 print(2)
1098
1099
1100 class Bar:
1101 # World!
1102
1103 def a():
1104 print(1)
1105
1106 def b():
1107 print(2)
1108
1109
1110 "
1111 .unindent(),
1112 cx,
1113 );
1114 build_editor(buffer.clone(), cx)
1115 });
1116
1117 _ = view.update(cx, |view, cx| {
1118 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1119 assert_eq!(
1120 view.display_text(cx),
1121 "
1122 class Foo:
1123 # Hello!
1124
1125 def a():⋯
1126
1127 def b():⋯
1128
1129
1130 class Bar:
1131 # World!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 "
1139 .unindent(),
1140 );
1141
1142 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1143 assert_eq!(
1144 view.display_text(cx),
1145 "
1146 class Foo:⋯
1147
1148
1149 class Bar:⋯
1150
1151
1152 "
1153 .unindent(),
1154 );
1155
1156 view.unfold_all(&UnfoldAll, cx);
1157 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1158 assert_eq!(
1159 view.display_text(cx),
1160 "
1161 class Foo:
1162 # Hello!
1163
1164 def a():
1165 print(1)
1166
1167 def b():
1168 print(2)
1169
1170
1171 class Bar:
1172 # World!
1173
1174 def a():
1175 print(1)
1176
1177 def b():
1178 print(2)
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1186 });
1187}
1188
1189#[gpui::test]
1190fn test_move_cursor(cx: &mut TestAppContext) {
1191 init_test(cx, |_| {});
1192
1193 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1194 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1195
1196 buffer.update(cx, |buffer, cx| {
1197 buffer.edit(
1198 vec![
1199 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1200 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1201 ],
1202 None,
1203 cx,
1204 );
1205 });
1206 _ = view.update(cx, |view, cx| {
1207 assert_eq!(
1208 view.selections.display_ranges(cx),
1209 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1210 );
1211
1212 view.move_down(&MoveDown, cx);
1213 assert_eq!(
1214 view.selections.display_ranges(cx),
1215 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1216 );
1217
1218 view.move_right(&MoveRight, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1222 );
1223
1224 view.move_left(&MoveLeft, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1228 );
1229
1230 view.move_up(&MoveUp, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1234 );
1235
1236 view.move_to_end(&MoveToEnd, cx);
1237 assert_eq!(
1238 view.selections.display_ranges(cx),
1239 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1240 );
1241
1242 view.move_to_beginning(&MoveToBeginning, cx);
1243 assert_eq!(
1244 view.selections.display_ranges(cx),
1245 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1246 );
1247
1248 view.change_selections(None, cx, |s| {
1249 s.select_display_ranges([
1250 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1251 ]);
1252 });
1253 view.select_to_beginning(&SelectToBeginning, cx);
1254 assert_eq!(
1255 view.selections.display_ranges(cx),
1256 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1257 );
1258
1259 view.select_to_end(&SelectToEnd, cx);
1260 assert_eq!(
1261 view.selections.display_ranges(cx),
1262 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1263 );
1264 });
1265}
1266
1267// TODO: Re-enable this test
1268#[cfg(target_os = "macos")]
1269#[gpui::test]
1270fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1271 init_test(cx, |_| {});
1272
1273 let view = cx.add_window(|cx| {
1274 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1275 build_editor(buffer.clone(), cx)
1276 });
1277
1278 assert_eq!('ⓐ'.len_utf8(), 3);
1279 assert_eq!('α'.len_utf8(), 2);
1280
1281 _ = view.update(cx, |view, cx| {
1282 view.fold_ranges(
1283 vec![
1284 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1285 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1286 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1287 ],
1288 true,
1289 cx,
1290 );
1291 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1292
1293 view.move_right(&MoveRight, cx);
1294 assert_eq!(
1295 view.selections.display_ranges(cx),
1296 &[empty_range(0, "ⓐ".len())]
1297 );
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐⓑ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ⋯".len())]
1307 );
1308
1309 view.move_down(&MoveDown, cx);
1310 assert_eq!(
1311 view.selections.display_ranges(cx),
1312 &[empty_range(1, "ab⋯e".len())]
1313 );
1314 view.move_left(&MoveLeft, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "a".len())]
1328 );
1329
1330 view.move_down(&MoveDown, cx);
1331 assert_eq!(
1332 view.selections.display_ranges(cx),
1333 &[empty_range(2, "α".len())]
1334 );
1335 view.move_right(&MoveRight, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "αβ".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ⋯".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯ε".len())]
1349 );
1350
1351 view.move_up(&MoveUp, cx);
1352 assert_eq!(
1353 view.selections.display_ranges(cx),
1354 &[empty_range(1, "ab⋯e".len())]
1355 );
1356 view.move_down(&MoveDown, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(2, "αβ⋯ε".len())]
1360 );
1361 view.move_up(&MoveUp, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯e".len())]
1365 );
1366
1367 view.move_up(&MoveUp, cx);
1368 assert_eq!(
1369 view.selections.display_ranges(cx),
1370 &[empty_range(0, "ⓐⓑ".len())]
1371 );
1372 view.move_left(&MoveLeft, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "".len())]
1381 );
1382 });
1383}
1384
1385#[gpui::test]
1386fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1387 init_test(cx, |_| {});
1388
1389 let view = cx.add_window(|cx| {
1390 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1391 build_editor(buffer.clone(), cx)
1392 });
1393 _ = view.update(cx, |view, cx| {
1394 view.change_selections(None, cx, |s| {
1395 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1396 });
1397 view.move_down(&MoveDown, cx);
1398 assert_eq!(
1399 view.selections.display_ranges(cx),
1400 &[empty_range(1, "abcd".len())]
1401 );
1402
1403 view.move_down(&MoveDown, cx);
1404 assert_eq!(
1405 view.selections.display_ranges(cx),
1406 &[empty_range(2, "αβγ".len())]
1407 );
1408
1409 view.move_down(&MoveDown, cx);
1410 assert_eq!(
1411 view.selections.display_ranges(cx),
1412 &[empty_range(3, "abcd".len())]
1413 );
1414
1415 view.move_down(&MoveDown, cx);
1416 assert_eq!(
1417 view.selections.display_ranges(cx),
1418 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1419 );
1420
1421 view.move_up(&MoveUp, cx);
1422 assert_eq!(
1423 view.selections.display_ranges(cx),
1424 &[empty_range(3, "abcd".len())]
1425 );
1426
1427 view.move_up(&MoveUp, cx);
1428 assert_eq!(
1429 view.selections.display_ranges(cx),
1430 &[empty_range(2, "αβγ".len())]
1431 );
1432 });
1433}
1434
1435#[gpui::test]
1436fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1437 init_test(cx, |_| {});
1438 let move_to_beg = MoveToBeginningOfLine {
1439 stop_at_soft_wraps: true,
1440 };
1441
1442 let move_to_end = MoveToEndOfLine {
1443 stop_at_soft_wraps: true,
1444 };
1445
1446 let view = cx.add_window(|cx| {
1447 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1448 build_editor(buffer, cx)
1449 });
1450 _ = view.update(cx, |view, cx| {
1451 view.change_selections(None, cx, |s| {
1452 s.select_display_ranges([
1453 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1455 ]);
1456 });
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.move_to_beginning_of_line(&move_to_beg, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[
1464 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1465 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1466 ]
1467 );
1468 });
1469
1470 _ = view.update(cx, |view, cx| {
1471 view.move_to_beginning_of_line(&move_to_beg, cx);
1472 assert_eq!(
1473 view.selections.display_ranges(cx),
1474 &[
1475 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1476 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1477 ]
1478 );
1479 });
1480
1481 _ = view.update(cx, |view, cx| {
1482 view.move_to_beginning_of_line(&move_to_beg, cx);
1483 assert_eq!(
1484 view.selections.display_ranges(cx),
1485 &[
1486 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1487 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1488 ]
1489 );
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_end_of_line(&move_to_end, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1498 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1499 ]
1500 );
1501 });
1502
1503 // Moving to the end of line again is a no-op.
1504 _ = view.update(cx, |view, cx| {
1505 view.move_to_end_of_line(&move_to_end, cx);
1506 assert_eq!(
1507 view.selections.display_ranges(cx),
1508 &[
1509 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1510 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1511 ]
1512 );
1513 });
1514
1515 _ = view.update(cx, |view, cx| {
1516 view.move_left(&MoveLeft, cx);
1517 view.select_to_beginning_of_line(
1518 &SelectToBeginningOfLine {
1519 stop_at_soft_wraps: true,
1520 },
1521 cx,
1522 );
1523 assert_eq!(
1524 view.selections.display_ranges(cx),
1525 &[
1526 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1527 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1528 ]
1529 );
1530 });
1531
1532 _ = view.update(cx, |view, cx| {
1533 view.select_to_beginning_of_line(
1534 &SelectToBeginningOfLine {
1535 stop_at_soft_wraps: true,
1536 },
1537 cx,
1538 );
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1543 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.select_to_beginning_of_line(
1550 &SelectToBeginningOfLine {
1551 stop_at_soft_wraps: true,
1552 },
1553 cx,
1554 );
1555 assert_eq!(
1556 view.selections.display_ranges(cx),
1557 &[
1558 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1559 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1560 ]
1561 );
1562 });
1563
1564 _ = view.update(cx, |view, cx| {
1565 view.select_to_end_of_line(
1566 &SelectToEndOfLine {
1567 stop_at_soft_wraps: true,
1568 },
1569 cx,
1570 );
1571 assert_eq!(
1572 view.selections.display_ranges(cx),
1573 &[
1574 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1582 assert_eq!(view.display_text(cx), "ab\n de");
1583 assert_eq!(
1584 view.selections.display_ranges(cx),
1585 &[
1586 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1587 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1588 ]
1589 );
1590 });
1591
1592 _ = view.update(cx, |view, cx| {
1593 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1594 assert_eq!(view.display_text(cx), "\n");
1595 assert_eq!(
1596 view.selections.display_ranges(cx),
1597 &[
1598 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1599 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1600 ]
1601 );
1602 });
1603}
1604
1605#[gpui::test]
1606fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1607 init_test(cx, |_| {});
1608 let move_to_beg = MoveToBeginningOfLine {
1609 stop_at_soft_wraps: false,
1610 };
1611
1612 let move_to_end = MoveToEndOfLine {
1613 stop_at_soft_wraps: false,
1614 };
1615
1616 let view = cx.add_window(|cx| {
1617 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1618 build_editor(buffer, cx)
1619 });
1620
1621 _ = view.update(cx, |view, cx| {
1622 view.set_wrap_width(Some(140.0.into()), cx);
1623
1624 // We expect the following lines after wrapping
1625 // ```
1626 // thequickbrownfox
1627 // jumpedoverthelazydo
1628 // gs
1629 // ```
1630 // The final `gs` was soft-wrapped onto a new line.
1631 assert_eq!(
1632 "thequickbrownfox\njumpedoverthelaz\nydogs",
1633 view.display_text(cx),
1634 );
1635
1636 // First, let's assert behavior on the first line, that was not soft-wrapped.
1637 // Start the cursor at the `k` on the first line
1638 view.change_selections(None, cx, |s| {
1639 s.select_display_ranges([
1640 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1641 ]);
1642 });
1643
1644 // Moving to the beginning of the line should put us at the beginning of the line.
1645 view.move_to_beginning_of_line(&move_to_beg, cx);
1646 assert_eq!(
1647 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1648 view.selections.display_ranges(cx)
1649 );
1650
1651 // Moving to the end of the line should put us at the end of the line.
1652 view.move_to_end_of_line(&move_to_end, cx);
1653 assert_eq!(
1654 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1655 view.selections.display_ranges(cx)
1656 );
1657
1658 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1659 // Start the cursor at the last line (`y` that was wrapped to a new line)
1660 view.change_selections(None, cx, |s| {
1661 s.select_display_ranges([
1662 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1663 ]);
1664 });
1665
1666 // Moving to the beginning of the line should put us at the start of the second line of
1667 // display text, i.e., the `j`.
1668 view.move_to_beginning_of_line(&move_to_beg, cx);
1669 assert_eq!(
1670 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1671 view.selections.display_ranges(cx)
1672 );
1673
1674 // Moving to the beginning of the line again should be a no-op.
1675 view.move_to_beginning_of_line(&move_to_beg, cx);
1676 assert_eq!(
1677 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1678 view.selections.display_ranges(cx)
1679 );
1680
1681 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1682 // next display line.
1683 view.move_to_end_of_line(&move_to_end, cx);
1684 assert_eq!(
1685 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1686 view.selections.display_ranges(cx)
1687 );
1688
1689 // Moving to the end of the line again should be a no-op.
1690 view.move_to_end_of_line(&move_to_end, cx);
1691 assert_eq!(
1692 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1693 view.selections.display_ranges(cx)
1694 );
1695 });
1696}
1697
1698#[gpui::test]
1699fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1700 init_test(cx, |_| {});
1701
1702 let view = cx.add_window(|cx| {
1703 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1704 build_editor(buffer, cx)
1705 });
1706 _ = view.update(cx, |view, cx| {
1707 view.change_selections(None, cx, |s| {
1708 s.select_display_ranges([
1709 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1710 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1711 ])
1712 });
1713
1714 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1715 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1716
1717 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1718 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1719
1720 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1721 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1722
1723 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1724 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1725
1726 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1727 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1728
1729 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1730 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1731
1732 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1733 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1734
1735 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1736 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1737
1738 view.move_right(&MoveRight, cx);
1739 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1740 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1741
1742 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1743 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1744
1745 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1746 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1747 });
1748}
1749
1750#[gpui::test]
1751fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1752 init_test(cx, |_| {});
1753
1754 let view = cx.add_window(|cx| {
1755 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1756 build_editor(buffer, cx)
1757 });
1758
1759 _ = view.update(cx, |view, cx| {
1760 view.set_wrap_width(Some(140.0.into()), cx);
1761 assert_eq!(
1762 view.display_text(cx),
1763 "use one::{\n two::three::\n four::five\n};"
1764 );
1765
1766 view.change_selections(None, cx, |s| {
1767 s.select_display_ranges([
1768 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1769 ]);
1770 });
1771
1772 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1773 assert_eq!(
1774 view.selections.display_ranges(cx),
1775 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1776 );
1777
1778 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1779 assert_eq!(
1780 view.selections.display_ranges(cx),
1781 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1782 );
1783
1784 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1785 assert_eq!(
1786 view.selections.display_ranges(cx),
1787 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1788 );
1789
1790 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1791 assert_eq!(
1792 view.selections.display_ranges(cx),
1793 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1794 );
1795
1796 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1797 assert_eq!(
1798 view.selections.display_ranges(cx),
1799 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1800 );
1801
1802 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1803 assert_eq!(
1804 view.selections.display_ranges(cx),
1805 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1806 );
1807 });
1808}
1809
1810#[gpui::test]
1811async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814
1815 let line_height = cx.editor(|editor, cx| {
1816 editor
1817 .style()
1818 .unwrap()
1819 .text
1820 .line_height_in_pixels(cx.rem_size())
1821 });
1822 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827
1828 three
1829 fourˇ
1830 five
1831
1832 six"#
1833 .unindent(),
1834 );
1835
1836 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1837 cx.assert_editor_state(
1838 &r#"one
1839 two
1840 ˇ
1841 three
1842 four
1843 five
1844 ˇ
1845 six"#
1846 .unindent(),
1847 );
1848
1849 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1850 cx.assert_editor_state(
1851 &r#"one
1852 two
1853
1854 three
1855 four
1856 five
1857 ˇ
1858 sixˇ"#
1859 .unindent(),
1860 );
1861
1862 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1863 cx.assert_editor_state(
1864 &r#"one
1865 two
1866
1867 three
1868 four
1869 five
1870
1871 sixˇ"#
1872 .unindent(),
1873 );
1874
1875 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1876 cx.assert_editor_state(
1877 &r#"one
1878 two
1879
1880 three
1881 four
1882 five
1883 ˇ
1884 six"#
1885 .unindent(),
1886 );
1887
1888 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1889 cx.assert_editor_state(
1890 &r#"one
1891 two
1892 ˇ
1893 three
1894 four
1895 five
1896
1897 six"#
1898 .unindent(),
1899 );
1900
1901 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1902 cx.assert_editor_state(
1903 &r#"ˇone
1904 two
1905
1906 three
1907 four
1908 five
1909
1910 six"#
1911 .unindent(),
1912 );
1913}
1914
1915#[gpui::test]
1916async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1917 init_test(cx, |_| {});
1918 let mut cx = EditorTestContext::new(cx).await;
1919 let line_height = cx.editor(|editor, cx| {
1920 editor
1921 .style()
1922 .unwrap()
1923 .text
1924 .line_height_in_pixels(cx.rem_size())
1925 });
1926 let window = cx.window;
1927 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1928
1929 cx.set_state(
1930 r#"ˇone
1931 two
1932 three
1933 four
1934 five
1935 six
1936 seven
1937 eight
1938 nine
1939 ten
1940 "#,
1941 );
1942
1943 cx.update_editor(|editor, cx| {
1944 assert_eq!(
1945 editor.snapshot(cx).scroll_position(),
1946 gpui::Point::new(0., 0.)
1947 );
1948 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1949 assert_eq!(
1950 editor.snapshot(cx).scroll_position(),
1951 gpui::Point::new(0., 3.)
1952 );
1953 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1954 assert_eq!(
1955 editor.snapshot(cx).scroll_position(),
1956 gpui::Point::new(0., 6.)
1957 );
1958 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1959 assert_eq!(
1960 editor.snapshot(cx).scroll_position(),
1961 gpui::Point::new(0., 3.)
1962 );
1963
1964 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1965 assert_eq!(
1966 editor.snapshot(cx).scroll_position(),
1967 gpui::Point::new(0., 1.)
1968 );
1969 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1970 assert_eq!(
1971 editor.snapshot(cx).scroll_position(),
1972 gpui::Point::new(0., 3.)
1973 );
1974 });
1975}
1976
1977#[gpui::test]
1978async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1979 init_test(cx, |_| {});
1980 let mut cx = EditorTestContext::new(cx).await;
1981
1982 let line_height = cx.update_editor(|editor, cx| {
1983 editor.set_vertical_scroll_margin(2, cx);
1984 editor
1985 .style()
1986 .unwrap()
1987 .text
1988 .line_height_in_pixels(cx.rem_size())
1989 });
1990 let window = cx.window;
1991 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1992
1993 cx.set_state(
1994 r#"ˇone
1995 two
1996 three
1997 four
1998 five
1999 six
2000 seven
2001 eight
2002 nine
2003 ten
2004 "#,
2005 );
2006 cx.update_editor(|editor, cx| {
2007 assert_eq!(
2008 editor.snapshot(cx).scroll_position(),
2009 gpui::Point::new(0., 0.0)
2010 );
2011 });
2012
2013 // Add a cursor below the visible area. Since both cursors cannot fit
2014 // on screen, the editor autoscrolls to reveal the newest cursor, and
2015 // allows the vertical scroll margin below that cursor.
2016 cx.update_editor(|editor, cx| {
2017 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2018 selections.select_ranges([
2019 Point::new(0, 0)..Point::new(0, 0),
2020 Point::new(6, 0)..Point::new(6, 0),
2021 ]);
2022 })
2023 });
2024 cx.update_editor(|editor, cx| {
2025 assert_eq!(
2026 editor.snapshot(cx).scroll_position(),
2027 gpui::Point::new(0., 3.0)
2028 );
2029 });
2030
2031 // Move down. The editor cursor scrolls down to track the newest cursor.
2032 cx.update_editor(|editor, cx| {
2033 editor.move_down(&Default::default(), cx);
2034 });
2035 cx.update_editor(|editor, cx| {
2036 assert_eq!(
2037 editor.snapshot(cx).scroll_position(),
2038 gpui::Point::new(0., 4.0)
2039 );
2040 });
2041
2042 // Add a cursor above the visible area. Since both cursors fit on screen,
2043 // the editor scrolls to show both.
2044 cx.update_editor(|editor, cx| {
2045 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2046 selections.select_ranges([
2047 Point::new(1, 0)..Point::new(1, 0),
2048 Point::new(6, 0)..Point::new(6, 0),
2049 ]);
2050 })
2051 });
2052 cx.update_editor(|editor, cx| {
2053 assert_eq!(
2054 editor.snapshot(cx).scroll_position(),
2055 gpui::Point::new(0., 1.0)
2056 );
2057 });
2058}
2059
2060#[gpui::test]
2061async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2062 init_test(cx, |_| {});
2063 let mut cx = EditorTestContext::new(cx).await;
2064
2065 let line_height = cx.editor(|editor, cx| {
2066 editor
2067 .style()
2068 .unwrap()
2069 .text
2070 .line_height_in_pixels(cx.rem_size())
2071 });
2072 let window = cx.window;
2073 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2074 cx.set_state(
2075 &r#"
2076 ˇone
2077 two
2078 threeˇ
2079 four
2080 five
2081 six
2082 seven
2083 eight
2084 nine
2085 ten
2086 "#
2087 .unindent(),
2088 );
2089
2090 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2091 cx.assert_editor_state(
2092 &r#"
2093 one
2094 two
2095 three
2096 ˇfour
2097 five
2098 sixˇ
2099 seven
2100 eight
2101 nine
2102 ten
2103 "#
2104 .unindent(),
2105 );
2106
2107 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2108 cx.assert_editor_state(
2109 &r#"
2110 one
2111 two
2112 three
2113 four
2114 five
2115 six
2116 ˇseven
2117 eight
2118 nineˇ
2119 ten
2120 "#
2121 .unindent(),
2122 );
2123
2124 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2125 cx.assert_editor_state(
2126 &r#"
2127 one
2128 two
2129 three
2130 ˇfour
2131 five
2132 sixˇ
2133 seven
2134 eight
2135 nine
2136 ten
2137 "#
2138 .unindent(),
2139 );
2140
2141 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2142 cx.assert_editor_state(
2143 &r#"
2144 ˇone
2145 two
2146 threeˇ
2147 four
2148 five
2149 six
2150 seven
2151 eight
2152 nine
2153 ten
2154 "#
2155 .unindent(),
2156 );
2157
2158 // Test select collapsing
2159 cx.update_editor(|editor, cx| {
2160 editor.move_page_down(&MovePageDown::default(), cx);
2161 editor.move_page_down(&MovePageDown::default(), cx);
2162 editor.move_page_down(&MovePageDown::default(), cx);
2163 });
2164 cx.assert_editor_state(
2165 &r#"
2166 one
2167 two
2168 three
2169 four
2170 five
2171 six
2172 seven
2173 eight
2174 nine
2175 ˇten
2176 ˇ"#
2177 .unindent(),
2178 );
2179}
2180
2181#[gpui::test]
2182async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2183 init_test(cx, |_| {});
2184 let mut cx = EditorTestContext::new(cx).await;
2185 cx.set_state("one «two threeˇ» four");
2186 cx.update_editor(|editor, cx| {
2187 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2188 assert_eq!(editor.text(cx), " four");
2189 });
2190}
2191
2192#[gpui::test]
2193fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2194 init_test(cx, |_| {});
2195
2196 let view = cx.add_window(|cx| {
2197 let buffer = MultiBuffer::build_simple("one two three four", cx);
2198 build_editor(buffer.clone(), cx)
2199 });
2200
2201 _ = view.update(cx, |view, cx| {
2202 view.change_selections(None, cx, |s| {
2203 s.select_display_ranges([
2204 // an empty selection - the preceding word fragment is deleted
2205 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2206 // characters selected - they are deleted
2207 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2208 ])
2209 });
2210 view.delete_to_previous_word_start(
2211 &DeleteToPreviousWordStart {
2212 ignore_newlines: false,
2213 },
2214 cx,
2215 );
2216 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2217 });
2218
2219 _ = view.update(cx, |view, cx| {
2220 view.change_selections(None, cx, |s| {
2221 s.select_display_ranges([
2222 // an empty selection - the following word fragment is deleted
2223 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2224 // characters selected - they are deleted
2225 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2226 ])
2227 });
2228 view.delete_to_next_word_end(
2229 &DeleteToNextWordEnd {
2230 ignore_newlines: false,
2231 },
2232 cx,
2233 );
2234 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2235 });
2236}
2237
2238#[gpui::test]
2239fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2240 init_test(cx, |_| {});
2241
2242 let view = cx.add_window(|cx| {
2243 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2244 build_editor(buffer.clone(), cx)
2245 });
2246 let del_to_prev_word_start = DeleteToPreviousWordStart {
2247 ignore_newlines: false,
2248 };
2249 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2250 ignore_newlines: true,
2251 };
2252
2253 _ = view.update(cx, |view, cx| {
2254 view.change_selections(None, cx, |s| {
2255 s.select_display_ranges([
2256 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2257 ])
2258 });
2259 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2260 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2261 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2262 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2263 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2264 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2265 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2267 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2268 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2269 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_next_word_end = DeleteToNextWordEnd {
2283 ignore_newlines: false,
2284 };
2285 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2293 ])
2294 });
2295 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2296 assert_eq!(
2297 view.buffer.read(cx).read(cx).text(),
2298 "one\n two\nthree\n four"
2299 );
2300 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2301 assert_eq!(
2302 view.buffer.read(cx).read(cx).text(),
2303 "\n two\nthree\n four"
2304 );
2305 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2307 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2308 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2309 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2310 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2311 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2312 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2313 });
2314}
2315
2316#[gpui::test]
2317fn test_newline(cx: &mut TestAppContext) {
2318 init_test(cx, |_| {});
2319
2320 let view = cx.add_window(|cx| {
2321 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2322 build_editor(buffer.clone(), cx)
2323 });
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2329 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2330 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2331 ])
2332 });
2333
2334 view.newline(&Newline, cx);
2335 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2336 });
2337}
2338
2339#[gpui::test]
2340fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2341 init_test(cx, |_| {});
2342
2343 let editor = cx.add_window(|cx| {
2344 let buffer = MultiBuffer::build_simple(
2345 "
2346 a
2347 b(
2348 X
2349 )
2350 c(
2351 X
2352 )
2353 "
2354 .unindent()
2355 .as_str(),
2356 cx,
2357 );
2358 let mut editor = build_editor(buffer.clone(), cx);
2359 editor.change_selections(None, cx, |s| {
2360 s.select_ranges([
2361 Point::new(2, 4)..Point::new(2, 5),
2362 Point::new(5, 4)..Point::new(5, 5),
2363 ])
2364 });
2365 editor
2366 });
2367
2368 _ = editor.update(cx, |editor, cx| {
2369 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2370 editor.buffer.update(cx, |buffer, cx| {
2371 buffer.edit(
2372 [
2373 (Point::new(1, 2)..Point::new(3, 0), ""),
2374 (Point::new(4, 2)..Point::new(6, 0), ""),
2375 ],
2376 None,
2377 cx,
2378 );
2379 assert_eq!(
2380 buffer.read(cx).text(),
2381 "
2382 a
2383 b()
2384 c()
2385 "
2386 .unindent()
2387 );
2388 });
2389 assert_eq!(
2390 editor.selections.ranges(cx),
2391 &[
2392 Point::new(1, 2)..Point::new(1, 2),
2393 Point::new(2, 2)..Point::new(2, 2),
2394 ],
2395 );
2396
2397 editor.newline(&Newline, cx);
2398 assert_eq!(
2399 editor.text(cx),
2400 "
2401 a
2402 b(
2403 )
2404 c(
2405 )
2406 "
2407 .unindent()
2408 );
2409
2410 // The selections are moved after the inserted newlines
2411 assert_eq!(
2412 editor.selections.ranges(cx),
2413 &[
2414 Point::new(2, 0)..Point::new(2, 0),
2415 Point::new(4, 0)..Point::new(4, 0),
2416 ],
2417 );
2418 });
2419}
2420
2421#[gpui::test]
2422async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2423 init_test(cx, |settings| {
2424 settings.defaults.tab_size = NonZeroU32::new(4)
2425 });
2426
2427 let language = Arc::new(
2428 Language::new(
2429 LanguageConfig::default(),
2430 Some(tree_sitter_rust::LANGUAGE.into()),
2431 )
2432 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2433 .unwrap(),
2434 );
2435
2436 let mut cx = EditorTestContext::new(cx).await;
2437 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2438 cx.set_state(indoc! {"
2439 const a: ˇA = (
2440 (ˇ
2441 «const_functionˇ»(ˇ),
2442 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2443 )ˇ
2444 ˇ);ˇ
2445 "});
2446
2447 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2448 cx.assert_editor_state(indoc! {"
2449 ˇ
2450 const a: A = (
2451 ˇ
2452 (
2453 ˇ
2454 ˇ
2455 const_function(),
2456 ˇ
2457 ˇ
2458 ˇ
2459 ˇ
2460 something_else,
2461 ˇ
2462 )
2463 ˇ
2464 ˇ
2465 );
2466 "});
2467}
2468
2469#[gpui::test]
2470async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2471 init_test(cx, |settings| {
2472 settings.defaults.tab_size = NonZeroU32::new(4)
2473 });
2474
2475 let language = Arc::new(
2476 Language::new(
2477 LanguageConfig::default(),
2478 Some(tree_sitter_rust::LANGUAGE.into()),
2479 )
2480 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2481 .unwrap(),
2482 );
2483
2484 let mut cx = EditorTestContext::new(cx).await;
2485 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2486 cx.set_state(indoc! {"
2487 const a: ˇA = (
2488 (ˇ
2489 «const_functionˇ»(ˇ),
2490 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2491 )ˇ
2492 ˇ);ˇ
2493 "});
2494
2495 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2496 cx.assert_editor_state(indoc! {"
2497 const a: A = (
2498 ˇ
2499 (
2500 ˇ
2501 const_function(),
2502 ˇ
2503 ˇ
2504 something_else,
2505 ˇ
2506 ˇ
2507 ˇ
2508 ˇ
2509 )
2510 ˇ
2511 );
2512 ˇ
2513 ˇ
2514 "});
2515}
2516
2517#[gpui::test]
2518async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2519 init_test(cx, |settings| {
2520 settings.defaults.tab_size = NonZeroU32::new(4)
2521 });
2522
2523 let language = Arc::new(Language::new(
2524 LanguageConfig {
2525 line_comments: vec!["//".into()],
2526 ..LanguageConfig::default()
2527 },
2528 None,
2529 ));
2530 {
2531 let mut cx = EditorTestContext::new(cx).await;
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533 cx.set_state(indoc! {"
2534 // Fooˇ
2535 "});
2536
2537 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2538 cx.assert_editor_state(indoc! {"
2539 // Foo
2540 //ˇ
2541 "});
2542 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2543 cx.set_state(indoc! {"
2544 ˇ// Foo
2545 "});
2546 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2547 cx.assert_editor_state(indoc! {"
2548
2549 ˇ// Foo
2550 "});
2551 }
2552 // Ensure that comment continuations can be disabled.
2553 update_test_language_settings(cx, |settings| {
2554 settings.defaults.extend_comment_on_newline = Some(false);
2555 });
2556 let mut cx = EditorTestContext::new(cx).await;
2557 cx.set_state(indoc! {"
2558 // Fooˇ
2559 "});
2560 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2561 cx.assert_editor_state(indoc! {"
2562 // Foo
2563 ˇ
2564 "});
2565}
2566
2567#[gpui::test]
2568fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|cx| {
2572 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2573 let mut editor = build_editor(buffer.clone(), cx);
2574 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2575 editor
2576 });
2577
2578 _ = editor.update(cx, |editor, cx| {
2579 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2580 editor.buffer.update(cx, |buffer, cx| {
2581 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2582 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2583 });
2584 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2585
2586 editor.insert("Z", cx);
2587 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2588
2589 // The selections are moved after the inserted characters
2590 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2591 });
2592}
2593
2594#[gpui::test]
2595async fn test_tab(cx: &mut gpui::TestAppContext) {
2596 init_test(cx, |settings| {
2597 settings.defaults.tab_size = NonZeroU32::new(3)
2598 });
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.set_state(indoc! {"
2602 ˇabˇc
2603 ˇ🏀ˇ🏀ˇefg
2604 dˇ
2605 "});
2606 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2607 cx.assert_editor_state(indoc! {"
2608 ˇab ˇc
2609 ˇ🏀 ˇ🏀 ˇefg
2610 d ˇ
2611 "});
2612
2613 cx.set_state(indoc! {"
2614 a
2615 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2616 "});
2617 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2618 cx.assert_editor_state(indoc! {"
2619 a
2620 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2621 "});
2622}
2623
2624#[gpui::test]
2625async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2626 init_test(cx, |_| {});
2627
2628 let mut cx = EditorTestContext::new(cx).await;
2629 let language = Arc::new(
2630 Language::new(
2631 LanguageConfig::default(),
2632 Some(tree_sitter_rust::LANGUAGE.into()),
2633 )
2634 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2635 .unwrap(),
2636 );
2637 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2638
2639 // cursors that are already at the suggested indent level insert
2640 // a soft tab. cursors that are to the left of the suggested indent
2641 // auto-indent their line.
2642 cx.set_state(indoc! {"
2643 ˇ
2644 const a: B = (
2645 c(
2646 d(
2647 ˇ
2648 )
2649 ˇ
2650 ˇ )
2651 );
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 ˇ
2656 const a: B = (
2657 c(
2658 d(
2659 ˇ
2660 )
2661 ˇ
2662 ˇ)
2663 );
2664 "});
2665
2666 // handle auto-indent when there are multiple cursors on the same line
2667 cx.set_state(indoc! {"
2668 const a: B = (
2669 c(
2670 ˇ ˇ
2671 ˇ )
2672 );
2673 "});
2674 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2675 cx.assert_editor_state(indoc! {"
2676 const a: B = (
2677 c(
2678 ˇ
2679 ˇ)
2680 );
2681 "});
2682}
2683
2684#[gpui::test]
2685async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2686 init_test(cx, |settings| {
2687 settings.defaults.tab_size = NonZeroU32::new(4)
2688 });
2689
2690 let language = Arc::new(
2691 Language::new(
2692 LanguageConfig::default(),
2693 Some(tree_sitter_rust::LANGUAGE.into()),
2694 )
2695 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2696 .unwrap(),
2697 );
2698
2699 let mut cx = EditorTestContext::new(cx).await;
2700 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2701 cx.set_state(indoc! {"
2702 fn a() {
2703 if b {
2704 \t ˇc
2705 }
2706 }
2707 "});
2708
2709 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2710 cx.assert_editor_state(indoc! {"
2711 fn a() {
2712 if b {
2713 ˇc
2714 }
2715 }
2716 "});
2717}
2718
2719#[gpui::test]
2720async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2721 init_test(cx, |settings| {
2722 settings.defaults.tab_size = NonZeroU32::new(4);
2723 });
2724
2725 let mut cx = EditorTestContext::new(cx).await;
2726
2727 cx.set_state(indoc! {"
2728 «oneˇ» «twoˇ»
2729 three
2730 four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 «oneˇ» «twoˇ»
2735 three
2736 four
2737 "});
2738
2739 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2740 cx.assert_editor_state(indoc! {"
2741 «oneˇ» «twoˇ»
2742 three
2743 four
2744 "});
2745
2746 // select across line ending
2747 cx.set_state(indoc! {"
2748 one two
2749 t«hree
2750 ˇ» four
2751 "});
2752 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2753 cx.assert_editor_state(indoc! {"
2754 one two
2755 t«hree
2756 ˇ» four
2757 "});
2758
2759 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2760 cx.assert_editor_state(indoc! {"
2761 one two
2762 t«hree
2763 ˇ» four
2764 "});
2765
2766 // Ensure that indenting/outdenting works when the cursor is at column 0.
2767 cx.set_state(indoc! {"
2768 one two
2769 ˇthree
2770 four
2771 "});
2772 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2773 cx.assert_editor_state(indoc! {"
2774 one two
2775 ˇthree
2776 four
2777 "});
2778
2779 cx.set_state(indoc! {"
2780 one two
2781 ˇ three
2782 four
2783 "});
2784 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 ˇthree
2788 four
2789 "});
2790}
2791
2792#[gpui::test]
2793async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2794 init_test(cx, |settings| {
2795 settings.defaults.hard_tabs = Some(true);
2796 });
2797
2798 let mut cx = EditorTestContext::new(cx).await;
2799
2800 // select two ranges on one line
2801 cx.set_state(indoc! {"
2802 «oneˇ» «twoˇ»
2803 three
2804 four
2805 "});
2806 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2807 cx.assert_editor_state(indoc! {"
2808 \t«oneˇ» «twoˇ»
2809 three
2810 four
2811 "});
2812 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2813 cx.assert_editor_state(indoc! {"
2814 \t\t«oneˇ» «twoˇ»
2815 three
2816 four
2817 "});
2818 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2819 cx.assert_editor_state(indoc! {"
2820 \t«oneˇ» «twoˇ»
2821 three
2822 four
2823 "});
2824 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2825 cx.assert_editor_state(indoc! {"
2826 «oneˇ» «twoˇ»
2827 three
2828 four
2829 "});
2830
2831 // select across a line ending
2832 cx.set_state(indoc! {"
2833 one two
2834 t«hree
2835 ˇ»four
2836 "});
2837 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2838 cx.assert_editor_state(indoc! {"
2839 one two
2840 \tt«hree
2841 ˇ»four
2842 "});
2843 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2844 cx.assert_editor_state(indoc! {"
2845 one two
2846 \t\tt«hree
2847 ˇ»four
2848 "});
2849 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2850 cx.assert_editor_state(indoc! {"
2851 one two
2852 \tt«hree
2853 ˇ»four
2854 "});
2855 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2856 cx.assert_editor_state(indoc! {"
2857 one two
2858 t«hree
2859 ˇ»four
2860 "});
2861
2862 // Ensure that indenting/outdenting works when the cursor is at column 0.
2863 cx.set_state(indoc! {"
2864 one two
2865 ˇthree
2866 four
2867 "});
2868 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2869 cx.assert_editor_state(indoc! {"
2870 one two
2871 ˇthree
2872 four
2873 "});
2874 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2875 cx.assert_editor_state(indoc! {"
2876 one two
2877 \tˇthree
2878 four
2879 "});
2880 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2881 cx.assert_editor_state(indoc! {"
2882 one two
2883 ˇthree
2884 four
2885 "});
2886}
2887
2888#[gpui::test]
2889fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2890 init_test(cx, |settings| {
2891 settings.languages.extend([
2892 (
2893 "TOML".into(),
2894 LanguageSettingsContent {
2895 tab_size: NonZeroU32::new(2),
2896 ..Default::default()
2897 },
2898 ),
2899 (
2900 "Rust".into(),
2901 LanguageSettingsContent {
2902 tab_size: NonZeroU32::new(4),
2903 ..Default::default()
2904 },
2905 ),
2906 ]);
2907 });
2908
2909 let toml_language = Arc::new(Language::new(
2910 LanguageConfig {
2911 name: "TOML".into(),
2912 ..Default::default()
2913 },
2914 None,
2915 ));
2916 let rust_language = Arc::new(Language::new(
2917 LanguageConfig {
2918 name: "Rust".into(),
2919 ..Default::default()
2920 },
2921 None,
2922 ));
2923
2924 let toml_buffer =
2925 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2926 let rust_buffer = cx.new_model(|cx| {
2927 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2928 });
2929 let multibuffer = cx.new_model(|cx| {
2930 let mut multibuffer = MultiBuffer::new(ReadWrite);
2931 multibuffer.push_excerpts(
2932 toml_buffer.clone(),
2933 [ExcerptRange {
2934 context: Point::new(0, 0)..Point::new(2, 0),
2935 primary: None,
2936 }],
2937 cx,
2938 );
2939 multibuffer.push_excerpts(
2940 rust_buffer.clone(),
2941 [ExcerptRange {
2942 context: Point::new(0, 0)..Point::new(1, 0),
2943 primary: None,
2944 }],
2945 cx,
2946 );
2947 multibuffer
2948 });
2949
2950 cx.add_window(|cx| {
2951 let mut editor = build_editor(multibuffer, cx);
2952
2953 assert_eq!(
2954 editor.text(cx),
2955 indoc! {"
2956 a = 1
2957 b = 2
2958
2959 const c: usize = 3;
2960 "}
2961 );
2962
2963 select_ranges(
2964 &mut editor,
2965 indoc! {"
2966 «aˇ» = 1
2967 b = 2
2968
2969 «const c:ˇ» usize = 3;
2970 "},
2971 cx,
2972 );
2973
2974 editor.tab(&Tab, cx);
2975 assert_text_with_selections(
2976 &mut editor,
2977 indoc! {"
2978 «aˇ» = 1
2979 b = 2
2980
2981 «const c:ˇ» usize = 3;
2982 "},
2983 cx,
2984 );
2985 editor.tab_prev(&TabPrev, cx);
2986 assert_text_with_selections(
2987 &mut editor,
2988 indoc! {"
2989 «aˇ» = 1
2990 b = 2
2991
2992 «const c:ˇ» usize = 3;
2993 "},
2994 cx,
2995 );
2996
2997 editor
2998 });
2999}
3000
3001#[gpui::test]
3002async fn test_backspace(cx: &mut gpui::TestAppContext) {
3003 init_test(cx, |_| {});
3004
3005 let mut cx = EditorTestContext::new(cx).await;
3006
3007 // Basic backspace
3008 cx.set_state(indoc! {"
3009 onˇe two three
3010 fou«rˇ» five six
3011 seven «ˇeight nine
3012 »ten
3013 "});
3014 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3015 cx.assert_editor_state(indoc! {"
3016 oˇe two three
3017 fouˇ five six
3018 seven ˇten
3019 "});
3020
3021 // Test backspace inside and around indents
3022 cx.set_state(indoc! {"
3023 zero
3024 ˇone
3025 ˇtwo
3026 ˇ ˇ ˇ three
3027 ˇ ˇ four
3028 "});
3029 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3030 cx.assert_editor_state(indoc! {"
3031 zero
3032 ˇone
3033 ˇtwo
3034 ˇ threeˇ four
3035 "});
3036
3037 // Test backspace with line_mode set to true
3038 cx.update_editor(|e, _| e.selections.line_mode = true);
3039 cx.set_state(indoc! {"
3040 The ˇquick ˇbrown
3041 fox jumps over
3042 the lazy dog
3043 ˇThe qu«ick bˇ»rown"});
3044 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3045 cx.assert_editor_state(indoc! {"
3046 ˇfox jumps over
3047 the lazy dogˇ"});
3048}
3049
3050#[gpui::test]
3051async fn test_delete(cx: &mut gpui::TestAppContext) {
3052 init_test(cx, |_| {});
3053
3054 let mut cx = EditorTestContext::new(cx).await;
3055 cx.set_state(indoc! {"
3056 onˇe two three
3057 fou«rˇ» five six
3058 seven «ˇeight nine
3059 »ten
3060 "});
3061 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3062 cx.assert_editor_state(indoc! {"
3063 onˇ two three
3064 fouˇ five six
3065 seven ˇten
3066 "});
3067
3068 // Test backspace with line_mode set to true
3069 cx.update_editor(|e, _| e.selections.line_mode = true);
3070 cx.set_state(indoc! {"
3071 The ˇquick ˇbrown
3072 fox «ˇjum»ps over
3073 the lazy dog
3074 ˇThe qu«ick bˇ»rown"});
3075 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3076 cx.assert_editor_state("ˇthe lazy dogˇ");
3077}
3078
3079#[gpui::test]
3080fn test_delete_line(cx: &mut TestAppContext) {
3081 init_test(cx, |_| {});
3082
3083 let view = cx.add_window(|cx| {
3084 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3085 build_editor(buffer, cx)
3086 });
3087 _ = view.update(cx, |view, cx| {
3088 view.change_selections(None, cx, |s| {
3089 s.select_display_ranges([
3090 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3091 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3092 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3093 ])
3094 });
3095 view.delete_line(&DeleteLine, cx);
3096 assert_eq!(view.display_text(cx), "ghi");
3097 assert_eq!(
3098 view.selections.display_ranges(cx),
3099 vec![
3100 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3101 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3102 ]
3103 );
3104 });
3105
3106 let view = cx.add_window(|cx| {
3107 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3108 build_editor(buffer, cx)
3109 });
3110 _ = view.update(cx, |view, cx| {
3111 view.change_selections(None, cx, |s| {
3112 s.select_display_ranges([
3113 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3114 ])
3115 });
3116 view.delete_line(&DeleteLine, cx);
3117 assert_eq!(view.display_text(cx), "ghi\n");
3118 assert_eq!(
3119 view.selections.display_ranges(cx),
3120 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3121 );
3122 });
3123}
3124
3125#[gpui::test]
3126fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3127 init_test(cx, |_| {});
3128
3129 cx.add_window(|cx| {
3130 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3131 let mut editor = build_editor(buffer.clone(), cx);
3132 let buffer = buffer.read(cx).as_singleton().unwrap();
3133
3134 assert_eq!(
3135 editor.selections.ranges::<Point>(cx),
3136 &[Point::new(0, 0)..Point::new(0, 0)]
3137 );
3138
3139 // When on single line, replace newline at end by space
3140 editor.join_lines(&JoinLines, cx);
3141 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3142 assert_eq!(
3143 editor.selections.ranges::<Point>(cx),
3144 &[Point::new(0, 3)..Point::new(0, 3)]
3145 );
3146
3147 // When multiple lines are selected, remove newlines that are spanned by the selection
3148 editor.change_selections(None, cx, |s| {
3149 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3150 });
3151 editor.join_lines(&JoinLines, cx);
3152 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3153 assert_eq!(
3154 editor.selections.ranges::<Point>(cx),
3155 &[Point::new(0, 11)..Point::new(0, 11)]
3156 );
3157
3158 // Undo should be transactional
3159 editor.undo(&Undo, cx);
3160 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3161 assert_eq!(
3162 editor.selections.ranges::<Point>(cx),
3163 &[Point::new(0, 5)..Point::new(2, 2)]
3164 );
3165
3166 // When joining an empty line don't insert a space
3167 editor.change_selections(None, cx, |s| {
3168 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3169 });
3170 editor.join_lines(&JoinLines, cx);
3171 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3172 assert_eq!(
3173 editor.selections.ranges::<Point>(cx),
3174 [Point::new(2, 3)..Point::new(2, 3)]
3175 );
3176
3177 // We can remove trailing newlines
3178 editor.join_lines(&JoinLines, cx);
3179 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3180 assert_eq!(
3181 editor.selections.ranges::<Point>(cx),
3182 [Point::new(2, 3)..Point::new(2, 3)]
3183 );
3184
3185 // We don't blow up on the last line
3186 editor.join_lines(&JoinLines, cx);
3187 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3188 assert_eq!(
3189 editor.selections.ranges::<Point>(cx),
3190 [Point::new(2, 3)..Point::new(2, 3)]
3191 );
3192
3193 // reset to test indentation
3194 editor.buffer.update(cx, |buffer, cx| {
3195 buffer.edit(
3196 [
3197 (Point::new(1, 0)..Point::new(1, 2), " "),
3198 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3199 ],
3200 None,
3201 cx,
3202 )
3203 });
3204
3205 // We remove any leading spaces
3206 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3207 editor.change_selections(None, cx, |s| {
3208 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3209 });
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3212
3213 // We don't insert a space for a line containing only spaces
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3216
3217 // We ignore any leading tabs
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3220
3221 editor
3222 });
3223}
3224
3225#[gpui::test]
3226fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3227 init_test(cx, |_| {});
3228
3229 cx.add_window(|cx| {
3230 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3231 let mut editor = build_editor(buffer.clone(), cx);
3232 let buffer = buffer.read(cx).as_singleton().unwrap();
3233
3234 editor.change_selections(None, cx, |s| {
3235 s.select_ranges([
3236 Point::new(0, 2)..Point::new(1, 1),
3237 Point::new(1, 2)..Point::new(1, 2),
3238 Point::new(3, 1)..Point::new(3, 2),
3239 ])
3240 });
3241
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3244
3245 assert_eq!(
3246 editor.selections.ranges::<Point>(cx),
3247 [
3248 Point::new(0, 7)..Point::new(0, 7),
3249 Point::new(1, 3)..Point::new(1, 3)
3250 ]
3251 );
3252 editor
3253 });
3254}
3255
3256#[gpui::test]
3257async fn test_join_lines_with_git_diff_base(
3258 executor: BackgroundExecutor,
3259 cx: &mut gpui::TestAppContext,
3260) {
3261 init_test(cx, |_| {});
3262
3263 let mut cx = EditorTestContext::new(cx).await;
3264
3265 let diff_base = r#"
3266 Line 0
3267 Line 1
3268 Line 2
3269 Line 3
3270 "#
3271 .unindent();
3272
3273 cx.set_state(
3274 &r#"
3275 ˇLine 0
3276 Line 1
3277 Line 2
3278 Line 3
3279 "#
3280 .unindent(),
3281 );
3282
3283 cx.set_diff_base(Some(&diff_base));
3284 executor.run_until_parked();
3285
3286 // Join lines
3287 cx.update_editor(|editor, cx| {
3288 editor.join_lines(&JoinLines, cx);
3289 });
3290 executor.run_until_parked();
3291
3292 cx.assert_editor_state(
3293 &r#"
3294 Line 0ˇ Line 1
3295 Line 2
3296 Line 3
3297 "#
3298 .unindent(),
3299 );
3300 // Join again
3301 cx.update_editor(|editor, cx| {
3302 editor.join_lines(&JoinLines, cx);
3303 });
3304 executor.run_until_parked();
3305
3306 cx.assert_editor_state(
3307 &r#"
3308 Line 0 Line 1ˇ Line 2
3309 Line 3
3310 "#
3311 .unindent(),
3312 );
3313}
3314
3315#[gpui::test]
3316async fn test_custom_newlines_cause_no_false_positive_diffs(
3317 executor: BackgroundExecutor,
3318 cx: &mut gpui::TestAppContext,
3319) {
3320 init_test(cx, |_| {});
3321 let mut cx = EditorTestContext::new(cx).await;
3322 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3323 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3324 executor.run_until_parked();
3325
3326 cx.update_editor(|editor, cx| {
3327 assert_eq!(
3328 editor
3329 .buffer()
3330 .read(cx)
3331 .snapshot(cx)
3332 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3333 .collect::<Vec<_>>(),
3334 Vec::new(),
3335 "Should not have any diffs for files with custom newlines"
3336 );
3337 });
3338}
3339
3340#[gpui::test]
3341async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 // Test sort_lines_case_insensitive()
3347 cx.set_state(indoc! {"
3348 «z
3349 y
3350 x
3351 Z
3352 Y
3353 Xˇ»
3354 "});
3355 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3356 cx.assert_editor_state(indoc! {"
3357 «x
3358 X
3359 y
3360 Y
3361 z
3362 Zˇ»
3363 "});
3364
3365 // Test reverse_lines()
3366 cx.set_state(indoc! {"
3367 «5
3368 4
3369 3
3370 2
3371 1ˇ»
3372 "});
3373 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3374 cx.assert_editor_state(indoc! {"
3375 «1
3376 2
3377 3
3378 4
3379 5ˇ»
3380 "});
3381
3382 // Skip testing shuffle_line()
3383
3384 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3385 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3386
3387 // Don't manipulate when cursor is on single line, but expand the selection
3388 cx.set_state(indoc! {"
3389 ddˇdd
3390 ccc
3391 bb
3392 a
3393 "});
3394 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3395 cx.assert_editor_state(indoc! {"
3396 «ddddˇ»
3397 ccc
3398 bb
3399 a
3400 "});
3401
3402 // Basic manipulate case
3403 // Start selection moves to column 0
3404 // End of selection shrinks to fit shorter line
3405 cx.set_state(indoc! {"
3406 dd«d
3407 ccc
3408 bb
3409 aaaaaˇ»
3410 "});
3411 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3412 cx.assert_editor_state(indoc! {"
3413 «aaaaa
3414 bb
3415 ccc
3416 dddˇ»
3417 "});
3418
3419 // Manipulate case with newlines
3420 cx.set_state(indoc! {"
3421 dd«d
3422 ccc
3423
3424 bb
3425 aaaaa
3426
3427 ˇ»
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «
3432
3433 aaaaa
3434 bb
3435 ccc
3436 dddˇ»
3437
3438 "});
3439
3440 // Adding new line
3441 cx.set_state(indoc! {"
3442 aa«a
3443 bbˇ»b
3444 "});
3445 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3446 cx.assert_editor_state(indoc! {"
3447 «aaa
3448 bbb
3449 added_lineˇ»
3450 "});
3451
3452 // Removing line
3453 cx.set_state(indoc! {"
3454 aa«a
3455 bbbˇ»
3456 "});
3457 cx.update_editor(|e, cx| {
3458 e.manipulate_lines(cx, |lines| {
3459 lines.pop();
3460 })
3461 });
3462 cx.assert_editor_state(indoc! {"
3463 «aaaˇ»
3464 "});
3465
3466 // Removing all lines
3467 cx.set_state(indoc! {"
3468 aa«a
3469 bbbˇ»
3470 "});
3471 cx.update_editor(|e, cx| {
3472 e.manipulate_lines(cx, |lines| {
3473 lines.drain(..);
3474 })
3475 });
3476 cx.assert_editor_state(indoc! {"
3477 ˇ
3478 "});
3479}
3480
3481#[gpui::test]
3482async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3483 init_test(cx, |_| {});
3484
3485 let mut cx = EditorTestContext::new(cx).await;
3486
3487 // Consider continuous selection as single selection
3488 cx.set_state(indoc! {"
3489 Aaa«aa
3490 cˇ»c«c
3491 bb
3492 aaaˇ»aa
3493 "});
3494 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3495 cx.assert_editor_state(indoc! {"
3496 «Aaaaa
3497 ccc
3498 bb
3499 aaaaaˇ»
3500 "});
3501
3502 cx.set_state(indoc! {"
3503 Aaa«aa
3504 cˇ»c«c
3505 bb
3506 aaaˇ»aa
3507 "});
3508 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3509 cx.assert_editor_state(indoc! {"
3510 «Aaaaa
3511 ccc
3512 bbˇ»
3513 "});
3514
3515 // Consider non continuous selection as distinct dedup operations
3516 cx.set_state(indoc! {"
3517 «aaaaa
3518 bb
3519 aaaaa
3520 aaaaaˇ»
3521
3522 aaa«aaˇ»
3523 "});
3524 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3525 cx.assert_editor_state(indoc! {"
3526 «aaaaa
3527 bbˇ»
3528
3529 «aaaaaˇ»
3530 "});
3531}
3532
3533#[gpui::test]
3534async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3535 init_test(cx, |_| {});
3536
3537 let mut cx = EditorTestContext::new(cx).await;
3538
3539 cx.set_state(indoc! {"
3540 «Aaa
3541 aAa
3542 Aaaˇ»
3543 "});
3544 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3545 cx.assert_editor_state(indoc! {"
3546 «Aaa
3547 aAaˇ»
3548 "});
3549
3550 cx.set_state(indoc! {"
3551 «Aaa
3552 aAa
3553 aaAˇ»
3554 "});
3555 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «Aaaˇ»
3558 "});
3559}
3560
3561#[gpui::test]
3562async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3563 init_test(cx, |_| {});
3564
3565 let mut cx = EditorTestContext::new(cx).await;
3566
3567 // Manipulate with multiple selections on a single line
3568 cx.set_state(indoc! {"
3569 dd«dd
3570 cˇ»c«c
3571 bb
3572 aaaˇ»aa
3573 "});
3574 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «aaaaa
3577 bb
3578 ccc
3579 ddddˇ»
3580 "});
3581
3582 // Manipulate with multiple disjoin selections
3583 cx.set_state(indoc! {"
3584 5«
3585 4
3586 3
3587 2
3588 1ˇ»
3589
3590 dd«dd
3591 ccc
3592 bb
3593 aaaˇ»aa
3594 "});
3595 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3596 cx.assert_editor_state(indoc! {"
3597 «1
3598 2
3599 3
3600 4
3601 5ˇ»
3602
3603 «aaaaa
3604 bb
3605 ccc
3606 ddddˇ»
3607 "});
3608
3609 // Adding lines on each selection
3610 cx.set_state(indoc! {"
3611 2«
3612 1ˇ»
3613
3614 bb«bb
3615 aaaˇ»aa
3616 "});
3617 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3618 cx.assert_editor_state(indoc! {"
3619 «2
3620 1
3621 added lineˇ»
3622
3623 «bbbb
3624 aaaaa
3625 added lineˇ»
3626 "});
3627
3628 // Removing lines on each selection
3629 cx.set_state(indoc! {"
3630 2«
3631 1ˇ»
3632
3633 bb«bb
3634 aaaˇ»aa
3635 "});
3636 cx.update_editor(|e, cx| {
3637 e.manipulate_lines(cx, |lines| {
3638 lines.pop();
3639 })
3640 });
3641 cx.assert_editor_state(indoc! {"
3642 «2ˇ»
3643
3644 «bbbbˇ»
3645 "});
3646}
3647
3648#[gpui::test]
3649async fn test_manipulate_text(cx: &mut TestAppContext) {
3650 init_test(cx, |_| {});
3651
3652 let mut cx = EditorTestContext::new(cx).await;
3653
3654 // Test convert_to_upper_case()
3655 cx.set_state(indoc! {"
3656 «hello worldˇ»
3657 "});
3658 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3659 cx.assert_editor_state(indoc! {"
3660 «HELLO WORLDˇ»
3661 "});
3662
3663 // Test convert_to_lower_case()
3664 cx.set_state(indoc! {"
3665 «HELLO WORLDˇ»
3666 "});
3667 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3668 cx.assert_editor_state(indoc! {"
3669 «hello worldˇ»
3670 "});
3671
3672 // Test multiple line, single selection case
3673 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3674 cx.set_state(indoc! {"
3675 «The quick brown
3676 fox jumps over
3677 the lazy dogˇ»
3678 "});
3679 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3680 cx.assert_editor_state(indoc! {"
3681 «The Quick Brown
3682 Fox Jumps Over
3683 The Lazy Dogˇ»
3684 "});
3685
3686 // Test multiple line, single selection case
3687 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3688 cx.set_state(indoc! {"
3689 «The quick brown
3690 fox jumps over
3691 the lazy dogˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «TheQuickBrown
3696 FoxJumpsOver
3697 TheLazyDogˇ»
3698 "});
3699
3700 // From here on out, test more complex cases of manipulate_text()
3701
3702 // Test no selection case - should affect words cursors are in
3703 // Cursor at beginning, middle, and end of word
3704 cx.set_state(indoc! {"
3705 ˇhello big beauˇtiful worldˇ
3706 "});
3707 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3708 cx.assert_editor_state(indoc! {"
3709 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3710 "});
3711
3712 // Test multiple selections on a single line and across multiple lines
3713 cx.set_state(indoc! {"
3714 «Theˇ» quick «brown
3715 foxˇ» jumps «overˇ»
3716 the «lazyˇ» dog
3717 "});
3718 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3719 cx.assert_editor_state(indoc! {"
3720 «THEˇ» quick «BROWN
3721 FOXˇ» jumps «OVERˇ»
3722 the «LAZYˇ» dog
3723 "});
3724
3725 // Test case where text length grows
3726 cx.set_state(indoc! {"
3727 «tschüߡ»
3728 "});
3729 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3730 cx.assert_editor_state(indoc! {"
3731 «TSCHÜSSˇ»
3732 "});
3733
3734 // Test to make sure we don't crash when text shrinks
3735 cx.set_state(indoc! {"
3736 aaa_bbbˇ
3737 "});
3738 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3739 cx.assert_editor_state(indoc! {"
3740 «aaaBbbˇ»
3741 "});
3742
3743 // Test to make sure we all aware of the fact that each word can grow and shrink
3744 // Final selections should be aware of this fact
3745 cx.set_state(indoc! {"
3746 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3747 "});
3748 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3749 cx.assert_editor_state(indoc! {"
3750 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3751 "});
3752
3753 cx.set_state(indoc! {"
3754 «hElLo, WoRld!ˇ»
3755 "});
3756 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3757 cx.assert_editor_state(indoc! {"
3758 «HeLlO, wOrLD!ˇ»
3759 "});
3760}
3761
3762#[gpui::test]
3763fn test_duplicate_line(cx: &mut TestAppContext) {
3764 init_test(cx, |_| {});
3765
3766 let view = cx.add_window(|cx| {
3767 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3768 build_editor(buffer, cx)
3769 });
3770 _ = view.update(cx, |view, cx| {
3771 view.change_selections(None, cx, |s| {
3772 s.select_display_ranges([
3773 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3774 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3775 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3776 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3777 ])
3778 });
3779 view.duplicate_line_down(&DuplicateLineDown, cx);
3780 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3781 assert_eq!(
3782 view.selections.display_ranges(cx),
3783 vec![
3784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3785 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3786 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3787 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3788 ]
3789 );
3790 });
3791
3792 let view = cx.add_window(|cx| {
3793 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3794 build_editor(buffer, cx)
3795 });
3796 _ = view.update(cx, |view, cx| {
3797 view.change_selections(None, cx, |s| {
3798 s.select_display_ranges([
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3800 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3801 ])
3802 });
3803 view.duplicate_line_down(&DuplicateLineDown, cx);
3804 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3805 assert_eq!(
3806 view.selections.display_ranges(cx),
3807 vec![
3808 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3809 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3810 ]
3811 );
3812 });
3813
3814 // With `move_upwards` the selections stay in place, except for
3815 // the lines inserted above them
3816 let view = cx.add_window(|cx| {
3817 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3818 build_editor(buffer, cx)
3819 });
3820 _ = view.update(cx, |view, cx| {
3821 view.change_selections(None, cx, |s| {
3822 s.select_display_ranges([
3823 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3824 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3825 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3826 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3827 ])
3828 });
3829 view.duplicate_line_up(&DuplicateLineUp, cx);
3830 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3831 assert_eq!(
3832 view.selections.display_ranges(cx),
3833 vec![
3834 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3835 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3836 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3837 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3838 ]
3839 );
3840 });
3841
3842 let view = cx.add_window(|cx| {
3843 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3844 build_editor(buffer, cx)
3845 });
3846 _ = view.update(cx, |view, cx| {
3847 view.change_selections(None, cx, |s| {
3848 s.select_display_ranges([
3849 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3851 ])
3852 });
3853 view.duplicate_line_up(&DuplicateLineUp, cx);
3854 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3855 assert_eq!(
3856 view.selections.display_ranges(cx),
3857 vec![
3858 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3859 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3860 ]
3861 );
3862 });
3863}
3864
3865#[gpui::test]
3866fn test_move_line_up_down(cx: &mut TestAppContext) {
3867 init_test(cx, |_| {});
3868
3869 let view = cx.add_window(|cx| {
3870 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3871 build_editor(buffer, cx)
3872 });
3873 _ = view.update(cx, |view, cx| {
3874 view.fold_ranges(
3875 vec![
3876 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3877 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3878 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3879 ],
3880 true,
3881 cx,
3882 );
3883 view.change_selections(None, cx, |s| {
3884 s.select_display_ranges([
3885 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3886 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3887 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3888 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3889 ])
3890 });
3891 assert_eq!(
3892 view.display_text(cx),
3893 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3894 );
3895
3896 view.move_line_up(&MoveLineUp, cx);
3897 assert_eq!(
3898 view.display_text(cx),
3899 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3900 );
3901 assert_eq!(
3902 view.selections.display_ranges(cx),
3903 vec![
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3905 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3906 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3907 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3908 ]
3909 );
3910 });
3911
3912 _ = view.update(cx, |view, cx| {
3913 view.move_line_down(&MoveLineDown, cx);
3914 assert_eq!(
3915 view.display_text(cx),
3916 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3917 );
3918 assert_eq!(
3919 view.selections.display_ranges(cx),
3920 vec![
3921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3922 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3923 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3924 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3925 ]
3926 );
3927 });
3928
3929 _ = view.update(cx, |view, cx| {
3930 view.move_line_down(&MoveLineDown, cx);
3931 assert_eq!(
3932 view.display_text(cx),
3933 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3934 );
3935 assert_eq!(
3936 view.selections.display_ranges(cx),
3937 vec![
3938 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3939 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3940 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3941 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3942 ]
3943 );
3944 });
3945
3946 _ = view.update(cx, |view, cx| {
3947 view.move_line_up(&MoveLineUp, cx);
3948 assert_eq!(
3949 view.display_text(cx),
3950 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3951 );
3952 assert_eq!(
3953 view.selections.display_ranges(cx),
3954 vec![
3955 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3956 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3957 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3958 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3959 ]
3960 );
3961 });
3962}
3963
3964#[gpui::test]
3965fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3966 init_test(cx, |_| {});
3967
3968 let editor = cx.add_window(|cx| {
3969 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3970 build_editor(buffer, cx)
3971 });
3972 _ = editor.update(cx, |editor, cx| {
3973 let snapshot = editor.buffer.read(cx).snapshot(cx);
3974 editor.insert_blocks(
3975 [BlockProperties {
3976 style: BlockStyle::Fixed,
3977 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3978 height: 1,
3979 render: Box::new(|_| div().into_any()),
3980 priority: 0,
3981 }],
3982 Some(Autoscroll::fit()),
3983 cx,
3984 );
3985 editor.change_selections(None, cx, |s| {
3986 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3987 });
3988 editor.move_line_down(&MoveLineDown, cx);
3989 });
3990}
3991
3992#[gpui::test]
3993fn test_transpose(cx: &mut TestAppContext) {
3994 init_test(cx, |_| {});
3995
3996 _ = cx.add_window(|cx| {
3997 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3998 editor.set_style(EditorStyle::default(), cx);
3999 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4000 editor.transpose(&Default::default(), cx);
4001 assert_eq!(editor.text(cx), "bac");
4002 assert_eq!(editor.selections.ranges(cx), [2..2]);
4003
4004 editor.transpose(&Default::default(), cx);
4005 assert_eq!(editor.text(cx), "bca");
4006 assert_eq!(editor.selections.ranges(cx), [3..3]);
4007
4008 editor.transpose(&Default::default(), cx);
4009 assert_eq!(editor.text(cx), "bac");
4010 assert_eq!(editor.selections.ranges(cx), [3..3]);
4011
4012 editor
4013 });
4014
4015 _ = cx.add_window(|cx| {
4016 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4017 editor.set_style(EditorStyle::default(), cx);
4018 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4019 editor.transpose(&Default::default(), cx);
4020 assert_eq!(editor.text(cx), "acb\nde");
4021 assert_eq!(editor.selections.ranges(cx), [3..3]);
4022
4023 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4024 editor.transpose(&Default::default(), cx);
4025 assert_eq!(editor.text(cx), "acbd\ne");
4026 assert_eq!(editor.selections.ranges(cx), [5..5]);
4027
4028 editor.transpose(&Default::default(), cx);
4029 assert_eq!(editor.text(cx), "acbde\n");
4030 assert_eq!(editor.selections.ranges(cx), [6..6]);
4031
4032 editor.transpose(&Default::default(), cx);
4033 assert_eq!(editor.text(cx), "acbd\ne");
4034 assert_eq!(editor.selections.ranges(cx), [6..6]);
4035
4036 editor
4037 });
4038
4039 _ = cx.add_window(|cx| {
4040 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4041 editor.set_style(EditorStyle::default(), cx);
4042 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4043 editor.transpose(&Default::default(), cx);
4044 assert_eq!(editor.text(cx), "bacd\ne");
4045 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4046
4047 editor.transpose(&Default::default(), cx);
4048 assert_eq!(editor.text(cx), "bcade\n");
4049 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4050
4051 editor.transpose(&Default::default(), cx);
4052 assert_eq!(editor.text(cx), "bcda\ne");
4053 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4054
4055 editor.transpose(&Default::default(), cx);
4056 assert_eq!(editor.text(cx), "bcade\n");
4057 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4058
4059 editor.transpose(&Default::default(), cx);
4060 assert_eq!(editor.text(cx), "bcaed\n");
4061 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4062
4063 editor
4064 });
4065
4066 _ = cx.add_window(|cx| {
4067 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4068 editor.set_style(EditorStyle::default(), cx);
4069 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4070 editor.transpose(&Default::default(), cx);
4071 assert_eq!(editor.text(cx), "🏀🍐✋");
4072 assert_eq!(editor.selections.ranges(cx), [8..8]);
4073
4074 editor.transpose(&Default::default(), cx);
4075 assert_eq!(editor.text(cx), "🏀✋🍐");
4076 assert_eq!(editor.selections.ranges(cx), [11..11]);
4077
4078 editor.transpose(&Default::default(), cx);
4079 assert_eq!(editor.text(cx), "🏀🍐✋");
4080 assert_eq!(editor.selections.ranges(cx), [11..11]);
4081
4082 editor
4083 });
4084}
4085
4086#[gpui::test]
4087async fn test_rewrap(cx: &mut TestAppContext) {
4088 init_test(cx, |_| {});
4089
4090 let mut cx = EditorTestContext::new(cx).await;
4091
4092 {
4093 let language = Arc::new(Language::new(
4094 LanguageConfig {
4095 line_comments: vec!["// ".into()],
4096 ..LanguageConfig::default()
4097 },
4098 None,
4099 ));
4100 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4101
4102 let unwrapped_text = indoc! {"
4103 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4104 "};
4105
4106 let wrapped_text = indoc! {"
4107 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4108 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4109 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4110 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4111 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4112 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4113 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4114 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4115 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4116 // porttitor id. Aliquam id accumsan eros.ˇ
4117 "};
4118
4119 cx.set_state(unwrapped_text);
4120 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4121 cx.assert_editor_state(wrapped_text);
4122 }
4123
4124 // Test that rewrapping works inside of a selection
4125 {
4126 let language = Arc::new(Language::new(
4127 LanguageConfig {
4128 line_comments: vec!["// ".into()],
4129 ..LanguageConfig::default()
4130 },
4131 None,
4132 ));
4133 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4134
4135 let unwrapped_text = indoc! {"
4136 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4137 "};
4138
4139 let wrapped_text = indoc! {"
4140 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4141 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4142 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4143 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4144 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4145 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4146 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4147 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4148 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4149 // porttitor id. Aliquam id accumsan eros.ˇ
4150 "};
4151
4152 cx.set_state(unwrapped_text);
4153 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4154 cx.assert_editor_state(wrapped_text);
4155 }
4156
4157 // Test that cursors that expand to the same region are collapsed.
4158 {
4159 let language = Arc::new(Language::new(
4160 LanguageConfig {
4161 line_comments: vec!["// ".into()],
4162 ..LanguageConfig::default()
4163 },
4164 None,
4165 ));
4166 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4167
4168 let unwrapped_text = indoc! {"
4169 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4170 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4171 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4172 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4173 "};
4174
4175 let wrapped_text = indoc! {"
4176 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4177 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4178 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4179 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4180 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4181 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4182 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4183 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4184 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4185 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4186 "};
4187
4188 cx.set_state(unwrapped_text);
4189 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4190 cx.assert_editor_state(wrapped_text);
4191 }
4192
4193 // Test that non-contiguous selections are treated separately.
4194 {
4195 let language = Arc::new(Language::new(
4196 LanguageConfig {
4197 line_comments: vec!["// ".into()],
4198 ..LanguageConfig::default()
4199 },
4200 None,
4201 ));
4202 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4203
4204 let unwrapped_text = indoc! {"
4205 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4206 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4207 //
4208 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4209 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4210 "};
4211
4212 let wrapped_text = indoc! {"
4213 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4214 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4215 // auctor, eu lacinia sapien scelerisque.ˇˇ
4216 //
4217 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4218 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4219 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4220 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4221 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4222 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4223 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4224 "};
4225
4226 cx.set_state(unwrapped_text);
4227 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4228 cx.assert_editor_state(wrapped_text);
4229 }
4230
4231 // Test that different comment prefixes are supported.
4232 {
4233 let language = Arc::new(Language::new(
4234 LanguageConfig {
4235 line_comments: vec!["# ".into()],
4236 ..LanguageConfig::default()
4237 },
4238 None,
4239 ));
4240 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4241
4242 let unwrapped_text = indoc! {"
4243 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4244 "};
4245
4246 let wrapped_text = indoc! {"
4247 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4248 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4249 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4250 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4251 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4252 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4253 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4254 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4255 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4256 # accumsan eros.ˇ
4257 "};
4258
4259 cx.set_state(unwrapped_text);
4260 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4261 cx.assert_editor_state(wrapped_text);
4262 }
4263
4264 // Test that rewrapping is ignored outside of comments in most languages.
4265 {
4266 let language = Arc::new(Language::new(
4267 LanguageConfig {
4268 line_comments: vec!["// ".into(), "/// ".into()],
4269 ..LanguageConfig::default()
4270 },
4271 Some(tree_sitter_rust::LANGUAGE.into()),
4272 ));
4273 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4274
4275 let unwrapped_text = indoc! {"
4276 /// Adds two numbers.
4277 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4278 fn add(a: u32, b: u32) -> u32 {
4279 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4280 }
4281 "};
4282
4283 let wrapped_text = indoc! {"
4284 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4285 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4286 fn add(a: u32, b: u32) -> u32 {
4287 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4288 }
4289 "};
4290
4291 cx.set_state(unwrapped_text);
4292 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4293 cx.assert_editor_state(wrapped_text);
4294 }
4295
4296 // Test that rewrapping works in Markdown and Plain Text languages.
4297 {
4298 let markdown_language = Arc::new(Language::new(
4299 LanguageConfig {
4300 name: "Markdown".into(),
4301 ..LanguageConfig::default()
4302 },
4303 None,
4304 ));
4305 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4306
4307 let unwrapped_text = indoc! {"
4308 # Hello
4309
4310 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4311 "};
4312
4313 let wrapped_text = indoc! {"
4314 # Hello
4315
4316 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4317 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4318 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4319 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4320 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4321 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4322 Integer sit amet scelerisque nisi.ˇ
4323 "};
4324
4325 cx.set_state(unwrapped_text);
4326 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4327 cx.assert_editor_state(wrapped_text);
4328
4329 let plaintext_language = Arc::new(Language::new(
4330 LanguageConfig {
4331 name: "Plain Text".into(),
4332 ..LanguageConfig::default()
4333 },
4334 None,
4335 ));
4336 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4337
4338 let unwrapped_text = indoc! {"
4339 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4340 "};
4341
4342 let wrapped_text = indoc! {"
4343 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4344 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4345 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4346 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4347 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4348 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4349 Integer sit amet scelerisque nisi.ˇ
4350 "};
4351
4352 cx.set_state(unwrapped_text);
4353 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4354 cx.assert_editor_state(wrapped_text);
4355 }
4356
4357 // Test rewrapping unaligned comments in a selection.
4358 {
4359 let language = Arc::new(Language::new(
4360 LanguageConfig {
4361 line_comments: vec!["// ".into(), "/// ".into()],
4362 ..LanguageConfig::default()
4363 },
4364 Some(tree_sitter_rust::LANGUAGE.into()),
4365 ));
4366 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4367
4368 let unwrapped_text = indoc! {"
4369 fn foo() {
4370 if true {
4371 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4372 // Praesent semper egestas tellus id dignissim.ˇ»
4373 do_something();
4374 } else {
4375 //
4376 }
4377 }
4378 "};
4379
4380 let wrapped_text = indoc! {"
4381 fn foo() {
4382 if true {
4383 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4384 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4385 // egestas tellus id dignissim.ˇ
4386 do_something();
4387 } else {
4388 //
4389 }
4390 }
4391 "};
4392
4393 cx.set_state(unwrapped_text);
4394 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4395 cx.assert_editor_state(wrapped_text);
4396
4397 let unwrapped_text = indoc! {"
4398 fn foo() {
4399 if true {
4400 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4401 // Praesent semper egestas tellus id dignissim.»
4402 do_something();
4403 } else {
4404 //
4405 }
4406
4407 }
4408 "};
4409
4410 let wrapped_text = indoc! {"
4411 fn foo() {
4412 if true {
4413 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4414 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4415 // egestas tellus id dignissim.ˇ
4416 do_something();
4417 } else {
4418 //
4419 }
4420
4421 }
4422 "};
4423
4424 cx.set_state(unwrapped_text);
4425 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4426 cx.assert_editor_state(wrapped_text);
4427 }
4428}
4429
4430#[gpui::test]
4431async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4432 init_test(cx, |_| {});
4433
4434 let mut cx = EditorTestContext::new(cx).await;
4435
4436 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4437 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4438 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4439
4440 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4441 cx.set_state("two ˇfour ˇsix ˇ");
4442 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4443 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4444
4445 // Paste again but with only two cursors. Since the number of cursors doesn't
4446 // match the number of slices in the clipboard, the entire clipboard text
4447 // is pasted at each cursor.
4448 cx.set_state("ˇtwo one✅ four three six five ˇ");
4449 cx.update_editor(|e, cx| {
4450 e.handle_input("( ", cx);
4451 e.paste(&Paste, cx);
4452 e.handle_input(") ", cx);
4453 });
4454 cx.assert_editor_state(
4455 &([
4456 "( one✅ ",
4457 "three ",
4458 "five ) ˇtwo one✅ four three six five ( one✅ ",
4459 "three ",
4460 "five ) ˇ",
4461 ]
4462 .join("\n")),
4463 );
4464
4465 // Cut with three selections, one of which is full-line.
4466 cx.set_state(indoc! {"
4467 1«2ˇ»3
4468 4ˇ567
4469 «8ˇ»9"});
4470 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4471 cx.assert_editor_state(indoc! {"
4472 1ˇ3
4473 ˇ9"});
4474
4475 // Paste with three selections, noticing how the copied selection that was full-line
4476 // gets inserted before the second cursor.
4477 cx.set_state(indoc! {"
4478 1ˇ3
4479 9ˇ
4480 «oˇ»ne"});
4481 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4482 cx.assert_editor_state(indoc! {"
4483 12ˇ3
4484 4567
4485 9ˇ
4486 8ˇne"});
4487
4488 // Copy with a single cursor only, which writes the whole line into the clipboard.
4489 cx.set_state(indoc! {"
4490 The quick brown
4491 fox juˇmps over
4492 the lazy dog"});
4493 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4494 assert_eq!(
4495 cx.read_from_clipboard()
4496 .and_then(|item| item.text().as_deref().map(str::to_string)),
4497 Some("fox jumps over\n".to_string())
4498 );
4499
4500 // Paste with three selections, noticing how the copied full-line selection is inserted
4501 // before the empty selections but replaces the selection that is non-empty.
4502 cx.set_state(indoc! {"
4503 Tˇhe quick brown
4504 «foˇ»x jumps over
4505 tˇhe lazy dog"});
4506 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4507 cx.assert_editor_state(indoc! {"
4508 fox jumps over
4509 Tˇhe quick brown
4510 fox jumps over
4511 ˇx jumps over
4512 fox jumps over
4513 tˇhe lazy dog"});
4514}
4515
4516#[gpui::test]
4517async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4518 init_test(cx, |_| {});
4519
4520 let mut cx = EditorTestContext::new(cx).await;
4521 let language = Arc::new(Language::new(
4522 LanguageConfig::default(),
4523 Some(tree_sitter_rust::LANGUAGE.into()),
4524 ));
4525 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4526
4527 // Cut an indented block, without the leading whitespace.
4528 cx.set_state(indoc! {"
4529 const a: B = (
4530 c(),
4531 «d(
4532 e,
4533 f
4534 )ˇ»
4535 );
4536 "});
4537 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4538 cx.assert_editor_state(indoc! {"
4539 const a: B = (
4540 c(),
4541 ˇ
4542 );
4543 "});
4544
4545 // Paste it at the same position.
4546 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4547 cx.assert_editor_state(indoc! {"
4548 const a: B = (
4549 c(),
4550 d(
4551 e,
4552 f
4553 )ˇ
4554 );
4555 "});
4556
4557 // Paste it at a line with a lower indent level.
4558 cx.set_state(indoc! {"
4559 ˇ
4560 const a: B = (
4561 c(),
4562 );
4563 "});
4564 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4565 cx.assert_editor_state(indoc! {"
4566 d(
4567 e,
4568 f
4569 )ˇ
4570 const a: B = (
4571 c(),
4572 );
4573 "});
4574
4575 // Cut an indented block, with the leading whitespace.
4576 cx.set_state(indoc! {"
4577 const a: B = (
4578 c(),
4579 « d(
4580 e,
4581 f
4582 )
4583 ˇ»);
4584 "});
4585 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4586 cx.assert_editor_state(indoc! {"
4587 const a: B = (
4588 c(),
4589 ˇ);
4590 "});
4591
4592 // Paste it at the same position.
4593 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4594 cx.assert_editor_state(indoc! {"
4595 const a: B = (
4596 c(),
4597 d(
4598 e,
4599 f
4600 )
4601 ˇ);
4602 "});
4603
4604 // Paste it at a line with a higher indent level.
4605 cx.set_state(indoc! {"
4606 const a: B = (
4607 c(),
4608 d(
4609 e,
4610 fˇ
4611 )
4612 );
4613 "});
4614 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4615 cx.assert_editor_state(indoc! {"
4616 const a: B = (
4617 c(),
4618 d(
4619 e,
4620 f d(
4621 e,
4622 f
4623 )
4624 ˇ
4625 )
4626 );
4627 "});
4628}
4629
4630#[gpui::test]
4631fn test_select_all(cx: &mut TestAppContext) {
4632 init_test(cx, |_| {});
4633
4634 let view = cx.add_window(|cx| {
4635 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4636 build_editor(buffer, cx)
4637 });
4638 _ = view.update(cx, |view, cx| {
4639 view.select_all(&SelectAll, cx);
4640 assert_eq!(
4641 view.selections.display_ranges(cx),
4642 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4643 );
4644 });
4645}
4646
4647#[gpui::test]
4648fn test_select_line(cx: &mut TestAppContext) {
4649 init_test(cx, |_| {});
4650
4651 let view = cx.add_window(|cx| {
4652 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4653 build_editor(buffer, cx)
4654 });
4655 _ = view.update(cx, |view, cx| {
4656 view.change_selections(None, cx, |s| {
4657 s.select_display_ranges([
4658 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4659 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4660 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4661 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4662 ])
4663 });
4664 view.select_line(&SelectLine, cx);
4665 assert_eq!(
4666 view.selections.display_ranges(cx),
4667 vec![
4668 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4669 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4670 ]
4671 );
4672 });
4673
4674 _ = view.update(cx, |view, cx| {
4675 view.select_line(&SelectLine, cx);
4676 assert_eq!(
4677 view.selections.display_ranges(cx),
4678 vec![
4679 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4680 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4681 ]
4682 );
4683 });
4684
4685 _ = view.update(cx, |view, cx| {
4686 view.select_line(&SelectLine, cx);
4687 assert_eq!(
4688 view.selections.display_ranges(cx),
4689 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4690 );
4691 });
4692}
4693
4694#[gpui::test]
4695fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4696 init_test(cx, |_| {});
4697
4698 let view = cx.add_window(|cx| {
4699 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4700 build_editor(buffer, cx)
4701 });
4702 _ = view.update(cx, |view, cx| {
4703 view.fold_ranges(
4704 vec![
4705 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4706 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4707 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4708 ],
4709 true,
4710 cx,
4711 );
4712 view.change_selections(None, cx, |s| {
4713 s.select_display_ranges([
4714 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4715 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4716 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4717 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4718 ])
4719 });
4720 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4721 });
4722
4723 _ = view.update(cx, |view, cx| {
4724 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4725 assert_eq!(
4726 view.display_text(cx),
4727 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4728 );
4729 assert_eq!(
4730 view.selections.display_ranges(cx),
4731 [
4732 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4733 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4734 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4735 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4736 ]
4737 );
4738 });
4739
4740 _ = view.update(cx, |view, cx| {
4741 view.change_selections(None, cx, |s| {
4742 s.select_display_ranges([
4743 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4744 ])
4745 });
4746 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4747 assert_eq!(
4748 view.display_text(cx),
4749 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4750 );
4751 assert_eq!(
4752 view.selections.display_ranges(cx),
4753 [
4754 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4755 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4756 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4757 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4758 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4759 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4760 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4761 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4762 ]
4763 );
4764 });
4765}
4766
4767#[gpui::test]
4768async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4769 init_test(cx, |_| {});
4770
4771 let mut cx = EditorTestContext::new(cx).await;
4772
4773 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4774 cx.set_state(indoc!(
4775 r#"abc
4776 defˇghi
4777
4778 jk
4779 nlmo
4780 "#
4781 ));
4782
4783 cx.update_editor(|editor, cx| {
4784 editor.add_selection_above(&Default::default(), cx);
4785 });
4786
4787 cx.assert_editor_state(indoc!(
4788 r#"abcˇ
4789 defˇghi
4790
4791 jk
4792 nlmo
4793 "#
4794 ));
4795
4796 cx.update_editor(|editor, cx| {
4797 editor.add_selection_above(&Default::default(), cx);
4798 });
4799
4800 cx.assert_editor_state(indoc!(
4801 r#"abcˇ
4802 defˇghi
4803
4804 jk
4805 nlmo
4806 "#
4807 ));
4808
4809 cx.update_editor(|view, cx| {
4810 view.add_selection_below(&Default::default(), cx);
4811 });
4812
4813 cx.assert_editor_state(indoc!(
4814 r#"abc
4815 defˇghi
4816
4817 jk
4818 nlmo
4819 "#
4820 ));
4821
4822 cx.update_editor(|view, cx| {
4823 view.undo_selection(&Default::default(), cx);
4824 });
4825
4826 cx.assert_editor_state(indoc!(
4827 r#"abcˇ
4828 defˇghi
4829
4830 jk
4831 nlmo
4832 "#
4833 ));
4834
4835 cx.update_editor(|view, cx| {
4836 view.redo_selection(&Default::default(), cx);
4837 });
4838
4839 cx.assert_editor_state(indoc!(
4840 r#"abc
4841 defˇghi
4842
4843 jk
4844 nlmo
4845 "#
4846 ));
4847
4848 cx.update_editor(|view, cx| {
4849 view.add_selection_below(&Default::default(), cx);
4850 });
4851
4852 cx.assert_editor_state(indoc!(
4853 r#"abc
4854 defˇghi
4855
4856 jk
4857 nlmˇo
4858 "#
4859 ));
4860
4861 cx.update_editor(|view, cx| {
4862 view.add_selection_below(&Default::default(), cx);
4863 });
4864
4865 cx.assert_editor_state(indoc!(
4866 r#"abc
4867 defˇghi
4868
4869 jk
4870 nlmˇo
4871 "#
4872 ));
4873
4874 // change selections
4875 cx.set_state(indoc!(
4876 r#"abc
4877 def«ˇg»hi
4878
4879 jk
4880 nlmo
4881 "#
4882 ));
4883
4884 cx.update_editor(|view, cx| {
4885 view.add_selection_below(&Default::default(), cx);
4886 });
4887
4888 cx.assert_editor_state(indoc!(
4889 r#"abc
4890 def«ˇg»hi
4891
4892 jk
4893 nlm«ˇo»
4894 "#
4895 ));
4896
4897 cx.update_editor(|view, cx| {
4898 view.add_selection_below(&Default::default(), cx);
4899 });
4900
4901 cx.assert_editor_state(indoc!(
4902 r#"abc
4903 def«ˇg»hi
4904
4905 jk
4906 nlm«ˇo»
4907 "#
4908 ));
4909
4910 cx.update_editor(|view, cx| {
4911 view.add_selection_above(&Default::default(), cx);
4912 });
4913
4914 cx.assert_editor_state(indoc!(
4915 r#"abc
4916 def«ˇg»hi
4917
4918 jk
4919 nlmo
4920 "#
4921 ));
4922
4923 cx.update_editor(|view, cx| {
4924 view.add_selection_above(&Default::default(), cx);
4925 });
4926
4927 cx.assert_editor_state(indoc!(
4928 r#"abc
4929 def«ˇg»hi
4930
4931 jk
4932 nlmo
4933 "#
4934 ));
4935
4936 // Change selections again
4937 cx.set_state(indoc!(
4938 r#"a«bc
4939 defgˇ»hi
4940
4941 jk
4942 nlmo
4943 "#
4944 ));
4945
4946 cx.update_editor(|view, cx| {
4947 view.add_selection_below(&Default::default(), cx);
4948 });
4949
4950 cx.assert_editor_state(indoc!(
4951 r#"a«bcˇ»
4952 d«efgˇ»hi
4953
4954 j«kˇ»
4955 nlmo
4956 "#
4957 ));
4958
4959 cx.update_editor(|view, cx| {
4960 view.add_selection_below(&Default::default(), cx);
4961 });
4962 cx.assert_editor_state(indoc!(
4963 r#"a«bcˇ»
4964 d«efgˇ»hi
4965
4966 j«kˇ»
4967 n«lmoˇ»
4968 "#
4969 ));
4970 cx.update_editor(|view, cx| {
4971 view.add_selection_above(&Default::default(), cx);
4972 });
4973
4974 cx.assert_editor_state(indoc!(
4975 r#"a«bcˇ»
4976 d«efgˇ»hi
4977
4978 j«kˇ»
4979 nlmo
4980 "#
4981 ));
4982
4983 // Change selections again
4984 cx.set_state(indoc!(
4985 r#"abc
4986 d«ˇefghi
4987
4988 jk
4989 nlm»o
4990 "#
4991 ));
4992
4993 cx.update_editor(|view, cx| {
4994 view.add_selection_above(&Default::default(), cx);
4995 });
4996
4997 cx.assert_editor_state(indoc!(
4998 r#"a«ˇbc»
4999 d«ˇef»ghi
5000
5001 j«ˇk»
5002 n«ˇlm»o
5003 "#
5004 ));
5005
5006 cx.update_editor(|view, cx| {
5007 view.add_selection_below(&Default::default(), cx);
5008 });
5009
5010 cx.assert_editor_state(indoc!(
5011 r#"abc
5012 d«ˇef»ghi
5013
5014 j«ˇk»
5015 n«ˇlm»o
5016 "#
5017 ));
5018}
5019
5020#[gpui::test]
5021async fn test_select_next(cx: &mut gpui::TestAppContext) {
5022 init_test(cx, |_| {});
5023
5024 let mut cx = EditorTestContext::new(cx).await;
5025 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5026
5027 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5028 .unwrap();
5029 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5030
5031 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5032 .unwrap();
5033 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5034
5035 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5036 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5037
5038 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5039 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5040
5041 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5042 .unwrap();
5043 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5044
5045 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5046 .unwrap();
5047 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5048}
5049
5050#[gpui::test]
5051async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5052 init_test(cx, |_| {});
5053
5054 let mut cx = EditorTestContext::new(cx).await;
5055 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5056
5057 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5058 .unwrap();
5059 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5060}
5061
5062#[gpui::test]
5063async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5064 init_test(cx, |_| {});
5065
5066 let mut cx = EditorTestContext::new(cx).await;
5067 cx.set_state(
5068 r#"let foo = 2;
5069lˇet foo = 2;
5070let fooˇ = 2;
5071let foo = 2;
5072let foo = ˇ2;"#,
5073 );
5074
5075 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5076 .unwrap();
5077 cx.assert_editor_state(
5078 r#"let foo = 2;
5079«letˇ» foo = 2;
5080let «fooˇ» = 2;
5081let foo = 2;
5082let foo = «2ˇ»;"#,
5083 );
5084
5085 // noop for multiple selections with different contents
5086 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5087 .unwrap();
5088 cx.assert_editor_state(
5089 r#"let foo = 2;
5090«letˇ» foo = 2;
5091let «fooˇ» = 2;
5092let foo = 2;
5093let foo = «2ˇ»;"#,
5094 );
5095}
5096
5097#[gpui::test]
5098async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5099 init_test(cx, |_| {});
5100
5101 let mut cx =
5102 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5103
5104 cx.assert_editor_state(indoc! {"
5105 ˇbbb
5106 ccc
5107
5108 bbb
5109 ccc
5110 "});
5111 cx.dispatch_action(SelectPrevious::default());
5112 cx.assert_editor_state(indoc! {"
5113 «bbbˇ»
5114 ccc
5115
5116 bbb
5117 ccc
5118 "});
5119 cx.dispatch_action(SelectPrevious::default());
5120 cx.assert_editor_state(indoc! {"
5121 «bbbˇ»
5122 ccc
5123
5124 «bbbˇ»
5125 ccc
5126 "});
5127}
5128
5129#[gpui::test]
5130async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5131 init_test(cx, |_| {});
5132
5133 let mut cx = EditorTestContext::new(cx).await;
5134 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5135
5136 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5137 .unwrap();
5138 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5139
5140 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5141 .unwrap();
5142 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5143
5144 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5145 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5146
5147 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5148 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5149
5150 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5151 .unwrap();
5152 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5153
5154 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5155 .unwrap();
5156 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5157
5158 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5159 .unwrap();
5160 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5161}
5162
5163#[gpui::test]
5164async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5165 init_test(cx, |_| {});
5166
5167 let mut cx = EditorTestContext::new(cx).await;
5168 cx.set_state(
5169 r#"let foo = 2;
5170lˇet foo = 2;
5171let fooˇ = 2;
5172let foo = 2;
5173let foo = ˇ2;"#,
5174 );
5175
5176 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5177 .unwrap();
5178 cx.assert_editor_state(
5179 r#"let foo = 2;
5180«letˇ» foo = 2;
5181let «fooˇ» = 2;
5182let foo = 2;
5183let foo = «2ˇ»;"#,
5184 );
5185
5186 // noop for multiple selections with different contents
5187 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5188 .unwrap();
5189 cx.assert_editor_state(
5190 r#"let foo = 2;
5191«letˇ» foo = 2;
5192let «fooˇ» = 2;
5193let foo = 2;
5194let foo = «2ˇ»;"#,
5195 );
5196}
5197
5198#[gpui::test]
5199async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5200 init_test(cx, |_| {});
5201
5202 let mut cx = EditorTestContext::new(cx).await;
5203 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5204
5205 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5206 .unwrap();
5207 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5208
5209 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5210 .unwrap();
5211 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5212
5213 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5214 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5215
5216 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5217 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5218
5219 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5220 .unwrap();
5221 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5222
5223 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5224 .unwrap();
5225 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5226}
5227
5228#[gpui::test]
5229async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5230 init_test(cx, |_| {});
5231
5232 let language = Arc::new(Language::new(
5233 LanguageConfig::default(),
5234 Some(tree_sitter_rust::LANGUAGE.into()),
5235 ));
5236
5237 let text = r#"
5238 use mod1::mod2::{mod3, mod4};
5239
5240 fn fn_1(param1: bool, param2: &str) {
5241 let var1 = "text";
5242 }
5243 "#
5244 .unindent();
5245
5246 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5247 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5248 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5249
5250 editor
5251 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5252 .await;
5253
5254 editor.update(cx, |view, cx| {
5255 view.change_selections(None, cx, |s| {
5256 s.select_display_ranges([
5257 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5258 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5259 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5260 ]);
5261 });
5262 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5263 });
5264 editor.update(cx, |editor, cx| {
5265 assert_text_with_selections(
5266 editor,
5267 indoc! {r#"
5268 use mod1::mod2::{mod3, «mod4ˇ»};
5269
5270 fn fn_1«ˇ(param1: bool, param2: &str)» {
5271 let var1 = "«textˇ»";
5272 }
5273 "#},
5274 cx,
5275 );
5276 });
5277
5278 editor.update(cx, |view, cx| {
5279 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5280 });
5281 editor.update(cx, |editor, cx| {
5282 assert_text_with_selections(
5283 editor,
5284 indoc! {r#"
5285 use mod1::mod2::«{mod3, mod4}ˇ»;
5286
5287 «ˇfn fn_1(param1: bool, param2: &str) {
5288 let var1 = "text";
5289 }»
5290 "#},
5291 cx,
5292 );
5293 });
5294
5295 editor.update(cx, |view, cx| {
5296 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5297 });
5298 assert_eq!(
5299 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5300 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5301 );
5302
5303 // Trying to expand the selected syntax node one more time has no effect.
5304 editor.update(cx, |view, cx| {
5305 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5306 });
5307 assert_eq!(
5308 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5309 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5310 );
5311
5312 editor.update(cx, |view, cx| {
5313 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5314 });
5315 editor.update(cx, |editor, cx| {
5316 assert_text_with_selections(
5317 editor,
5318 indoc! {r#"
5319 use mod1::mod2::«{mod3, mod4}ˇ»;
5320
5321 «ˇfn fn_1(param1: bool, param2: &str) {
5322 let var1 = "text";
5323 }»
5324 "#},
5325 cx,
5326 );
5327 });
5328
5329 editor.update(cx, |view, cx| {
5330 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5331 });
5332 editor.update(cx, |editor, cx| {
5333 assert_text_with_selections(
5334 editor,
5335 indoc! {r#"
5336 use mod1::mod2::{mod3, «mod4ˇ»};
5337
5338 fn fn_1«ˇ(param1: bool, param2: &str)» {
5339 let var1 = "«textˇ»";
5340 }
5341 "#},
5342 cx,
5343 );
5344 });
5345
5346 editor.update(cx, |view, cx| {
5347 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5348 });
5349 editor.update(cx, |editor, cx| {
5350 assert_text_with_selections(
5351 editor,
5352 indoc! {r#"
5353 use mod1::mod2::{mod3, mo«ˇ»d4};
5354
5355 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5356 let var1 = "te«ˇ»xt";
5357 }
5358 "#},
5359 cx,
5360 );
5361 });
5362
5363 // Trying to shrink the selected syntax node one more time has no effect.
5364 editor.update(cx, |view, cx| {
5365 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5366 });
5367 editor.update(cx, |editor, cx| {
5368 assert_text_with_selections(
5369 editor,
5370 indoc! {r#"
5371 use mod1::mod2::{mod3, mo«ˇ»d4};
5372
5373 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5374 let var1 = "te«ˇ»xt";
5375 }
5376 "#},
5377 cx,
5378 );
5379 });
5380
5381 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5382 // a fold.
5383 editor.update(cx, |view, cx| {
5384 view.fold_ranges(
5385 vec![
5386 (
5387 Point::new(0, 21)..Point::new(0, 24),
5388 FoldPlaceholder::test(),
5389 ),
5390 (
5391 Point::new(3, 20)..Point::new(3, 22),
5392 FoldPlaceholder::test(),
5393 ),
5394 ],
5395 true,
5396 cx,
5397 );
5398 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5399 });
5400 editor.update(cx, |editor, cx| {
5401 assert_text_with_selections(
5402 editor,
5403 indoc! {r#"
5404 use mod1::mod2::«{mod3, mod4}ˇ»;
5405
5406 fn fn_1«ˇ(param1: bool, param2: &str)» {
5407 «let var1 = "text";ˇ»
5408 }
5409 "#},
5410 cx,
5411 );
5412 });
5413}
5414
5415#[gpui::test]
5416async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5417 init_test(cx, |_| {});
5418
5419 let language = Arc::new(
5420 Language::new(
5421 LanguageConfig {
5422 brackets: BracketPairConfig {
5423 pairs: vec![
5424 BracketPair {
5425 start: "{".to_string(),
5426 end: "}".to_string(),
5427 close: false,
5428 surround: false,
5429 newline: true,
5430 },
5431 BracketPair {
5432 start: "(".to_string(),
5433 end: ")".to_string(),
5434 close: false,
5435 surround: false,
5436 newline: true,
5437 },
5438 ],
5439 ..Default::default()
5440 },
5441 ..Default::default()
5442 },
5443 Some(tree_sitter_rust::LANGUAGE.into()),
5444 )
5445 .with_indents_query(
5446 r#"
5447 (_ "(" ")" @end) @indent
5448 (_ "{" "}" @end) @indent
5449 "#,
5450 )
5451 .unwrap(),
5452 );
5453
5454 let text = "fn a() {}";
5455
5456 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5457 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5458 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5459 editor
5460 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5461 .await;
5462
5463 editor.update(cx, |editor, cx| {
5464 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5465 editor.newline(&Newline, cx);
5466 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5467 assert_eq!(
5468 editor.selections.ranges(cx),
5469 &[
5470 Point::new(1, 4)..Point::new(1, 4),
5471 Point::new(3, 4)..Point::new(3, 4),
5472 Point::new(5, 0)..Point::new(5, 0)
5473 ]
5474 );
5475 });
5476}
5477
5478#[gpui::test]
5479async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5480 init_test(cx, |_| {});
5481
5482 let mut cx = EditorTestContext::new(cx).await;
5483
5484 let language = Arc::new(Language::new(
5485 LanguageConfig {
5486 brackets: BracketPairConfig {
5487 pairs: vec![
5488 BracketPair {
5489 start: "{".to_string(),
5490 end: "}".to_string(),
5491 close: true,
5492 surround: true,
5493 newline: true,
5494 },
5495 BracketPair {
5496 start: "(".to_string(),
5497 end: ")".to_string(),
5498 close: true,
5499 surround: true,
5500 newline: true,
5501 },
5502 BracketPair {
5503 start: "/*".to_string(),
5504 end: " */".to_string(),
5505 close: true,
5506 surround: true,
5507 newline: true,
5508 },
5509 BracketPair {
5510 start: "[".to_string(),
5511 end: "]".to_string(),
5512 close: false,
5513 surround: false,
5514 newline: true,
5515 },
5516 BracketPair {
5517 start: "\"".to_string(),
5518 end: "\"".to_string(),
5519 close: true,
5520 surround: true,
5521 newline: false,
5522 },
5523 BracketPair {
5524 start: "<".to_string(),
5525 end: ">".to_string(),
5526 close: false,
5527 surround: true,
5528 newline: true,
5529 },
5530 ],
5531 ..Default::default()
5532 },
5533 autoclose_before: "})]".to_string(),
5534 ..Default::default()
5535 },
5536 Some(tree_sitter_rust::LANGUAGE.into()),
5537 ));
5538
5539 cx.language_registry().add(language.clone());
5540 cx.update_buffer(|buffer, cx| {
5541 buffer.set_language(Some(language), cx);
5542 });
5543
5544 cx.set_state(
5545 &r#"
5546 🏀ˇ
5547 εˇ
5548 ❤️ˇ
5549 "#
5550 .unindent(),
5551 );
5552
5553 // autoclose multiple nested brackets at multiple cursors
5554 cx.update_editor(|view, cx| {
5555 view.handle_input("{", cx);
5556 view.handle_input("{", cx);
5557 view.handle_input("{", cx);
5558 });
5559 cx.assert_editor_state(
5560 &"
5561 🏀{{{ˇ}}}
5562 ε{{{ˇ}}}
5563 ❤️{{{ˇ}}}
5564 "
5565 .unindent(),
5566 );
5567
5568 // insert a different closing bracket
5569 cx.update_editor(|view, cx| {
5570 view.handle_input(")", cx);
5571 });
5572 cx.assert_editor_state(
5573 &"
5574 🏀{{{)ˇ}}}
5575 ε{{{)ˇ}}}
5576 ❤️{{{)ˇ}}}
5577 "
5578 .unindent(),
5579 );
5580
5581 // skip over the auto-closed brackets when typing a closing bracket
5582 cx.update_editor(|view, cx| {
5583 view.move_right(&MoveRight, cx);
5584 view.handle_input("}", cx);
5585 view.handle_input("}", cx);
5586 view.handle_input("}", cx);
5587 });
5588 cx.assert_editor_state(
5589 &"
5590 🏀{{{)}}}}ˇ
5591 ε{{{)}}}}ˇ
5592 ❤️{{{)}}}}ˇ
5593 "
5594 .unindent(),
5595 );
5596
5597 // autoclose multi-character pairs
5598 cx.set_state(
5599 &"
5600 ˇ
5601 ˇ
5602 "
5603 .unindent(),
5604 );
5605 cx.update_editor(|view, cx| {
5606 view.handle_input("/", cx);
5607 view.handle_input("*", cx);
5608 });
5609 cx.assert_editor_state(
5610 &"
5611 /*ˇ */
5612 /*ˇ */
5613 "
5614 .unindent(),
5615 );
5616
5617 // one cursor autocloses a multi-character pair, one cursor
5618 // does not autoclose.
5619 cx.set_state(
5620 &"
5621 /ˇ
5622 ˇ
5623 "
5624 .unindent(),
5625 );
5626 cx.update_editor(|view, cx| view.handle_input("*", cx));
5627 cx.assert_editor_state(
5628 &"
5629 /*ˇ */
5630 *ˇ
5631 "
5632 .unindent(),
5633 );
5634
5635 // Don't autoclose if the next character isn't whitespace and isn't
5636 // listed in the language's "autoclose_before" section.
5637 cx.set_state("ˇa b");
5638 cx.update_editor(|view, cx| view.handle_input("{", cx));
5639 cx.assert_editor_state("{ˇa b");
5640
5641 // Don't autoclose if `close` is false for the bracket pair
5642 cx.set_state("ˇ");
5643 cx.update_editor(|view, cx| view.handle_input("[", cx));
5644 cx.assert_editor_state("[ˇ");
5645
5646 // Surround with brackets if text is selected
5647 cx.set_state("«aˇ» b");
5648 cx.update_editor(|view, cx| view.handle_input("{", cx));
5649 cx.assert_editor_state("{«aˇ»} b");
5650
5651 // Autclose pair where the start and end characters are the same
5652 cx.set_state("aˇ");
5653 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5654 cx.assert_editor_state("a\"ˇ\"");
5655 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5656 cx.assert_editor_state("a\"\"ˇ");
5657
5658 // Don't autoclose pair if autoclose is disabled
5659 cx.set_state("ˇ");
5660 cx.update_editor(|view, cx| view.handle_input("<", cx));
5661 cx.assert_editor_state("<ˇ");
5662
5663 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5664 cx.set_state("«aˇ» b");
5665 cx.update_editor(|view, cx| view.handle_input("<", cx));
5666 cx.assert_editor_state("<«aˇ»> b");
5667}
5668
5669#[gpui::test]
5670async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5671 init_test(cx, |settings| {
5672 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5673 });
5674
5675 let mut cx = EditorTestContext::new(cx).await;
5676
5677 let language = Arc::new(Language::new(
5678 LanguageConfig {
5679 brackets: BracketPairConfig {
5680 pairs: vec![
5681 BracketPair {
5682 start: "{".to_string(),
5683 end: "}".to_string(),
5684 close: true,
5685 surround: true,
5686 newline: true,
5687 },
5688 BracketPair {
5689 start: "(".to_string(),
5690 end: ")".to_string(),
5691 close: true,
5692 surround: true,
5693 newline: true,
5694 },
5695 BracketPair {
5696 start: "[".to_string(),
5697 end: "]".to_string(),
5698 close: false,
5699 surround: false,
5700 newline: true,
5701 },
5702 ],
5703 ..Default::default()
5704 },
5705 autoclose_before: "})]".to_string(),
5706 ..Default::default()
5707 },
5708 Some(tree_sitter_rust::LANGUAGE.into()),
5709 ));
5710
5711 cx.language_registry().add(language.clone());
5712 cx.update_buffer(|buffer, cx| {
5713 buffer.set_language(Some(language), cx);
5714 });
5715
5716 cx.set_state(
5717 &"
5718 ˇ
5719 ˇ
5720 ˇ
5721 "
5722 .unindent(),
5723 );
5724
5725 // ensure only matching closing brackets are skipped over
5726 cx.update_editor(|view, cx| {
5727 view.handle_input("}", cx);
5728 view.move_left(&MoveLeft, cx);
5729 view.handle_input(")", cx);
5730 view.move_left(&MoveLeft, cx);
5731 });
5732 cx.assert_editor_state(
5733 &"
5734 ˇ)}
5735 ˇ)}
5736 ˇ)}
5737 "
5738 .unindent(),
5739 );
5740
5741 // skip-over closing brackets at multiple cursors
5742 cx.update_editor(|view, cx| {
5743 view.handle_input(")", cx);
5744 view.handle_input("}", cx);
5745 });
5746 cx.assert_editor_state(
5747 &"
5748 )}ˇ
5749 )}ˇ
5750 )}ˇ
5751 "
5752 .unindent(),
5753 );
5754
5755 // ignore non-close brackets
5756 cx.update_editor(|view, cx| {
5757 view.handle_input("]", cx);
5758 view.move_left(&MoveLeft, cx);
5759 view.handle_input("]", cx);
5760 });
5761 cx.assert_editor_state(
5762 &"
5763 )}]ˇ]
5764 )}]ˇ]
5765 )}]ˇ]
5766 "
5767 .unindent(),
5768 );
5769}
5770
5771#[gpui::test]
5772async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5773 init_test(cx, |_| {});
5774
5775 let mut cx = EditorTestContext::new(cx).await;
5776
5777 let html_language = Arc::new(
5778 Language::new(
5779 LanguageConfig {
5780 name: "HTML".into(),
5781 brackets: BracketPairConfig {
5782 pairs: vec![
5783 BracketPair {
5784 start: "<".into(),
5785 end: ">".into(),
5786 close: true,
5787 ..Default::default()
5788 },
5789 BracketPair {
5790 start: "{".into(),
5791 end: "}".into(),
5792 close: true,
5793 ..Default::default()
5794 },
5795 BracketPair {
5796 start: "(".into(),
5797 end: ")".into(),
5798 close: true,
5799 ..Default::default()
5800 },
5801 ],
5802 ..Default::default()
5803 },
5804 autoclose_before: "})]>".into(),
5805 ..Default::default()
5806 },
5807 Some(tree_sitter_html::language()),
5808 )
5809 .with_injection_query(
5810 r#"
5811 (script_element
5812 (raw_text) @content
5813 (#set! "language" "javascript"))
5814 "#,
5815 )
5816 .unwrap(),
5817 );
5818
5819 let javascript_language = Arc::new(Language::new(
5820 LanguageConfig {
5821 name: "JavaScript".into(),
5822 brackets: BracketPairConfig {
5823 pairs: vec![
5824 BracketPair {
5825 start: "/*".into(),
5826 end: " */".into(),
5827 close: true,
5828 ..Default::default()
5829 },
5830 BracketPair {
5831 start: "{".into(),
5832 end: "}".into(),
5833 close: true,
5834 ..Default::default()
5835 },
5836 BracketPair {
5837 start: "(".into(),
5838 end: ")".into(),
5839 close: true,
5840 ..Default::default()
5841 },
5842 ],
5843 ..Default::default()
5844 },
5845 autoclose_before: "})]>".into(),
5846 ..Default::default()
5847 },
5848 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5849 ));
5850
5851 cx.language_registry().add(html_language.clone());
5852 cx.language_registry().add(javascript_language.clone());
5853
5854 cx.update_buffer(|buffer, cx| {
5855 buffer.set_language(Some(html_language), cx);
5856 });
5857
5858 cx.set_state(
5859 &r#"
5860 <body>ˇ
5861 <script>
5862 var x = 1;ˇ
5863 </script>
5864 </body>ˇ
5865 "#
5866 .unindent(),
5867 );
5868
5869 // Precondition: different languages are active at different locations.
5870 cx.update_editor(|editor, cx| {
5871 let snapshot = editor.snapshot(cx);
5872 let cursors = editor.selections.ranges::<usize>(cx);
5873 let languages = cursors
5874 .iter()
5875 .map(|c| snapshot.language_at(c.start).unwrap().name())
5876 .collect::<Vec<_>>();
5877 assert_eq!(
5878 languages,
5879 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5880 );
5881 });
5882
5883 // Angle brackets autoclose in HTML, but not JavaScript.
5884 cx.update_editor(|editor, cx| {
5885 editor.handle_input("<", cx);
5886 editor.handle_input("a", cx);
5887 });
5888 cx.assert_editor_state(
5889 &r#"
5890 <body><aˇ>
5891 <script>
5892 var x = 1;<aˇ
5893 </script>
5894 </body><aˇ>
5895 "#
5896 .unindent(),
5897 );
5898
5899 // Curly braces and parens autoclose in both HTML and JavaScript.
5900 cx.update_editor(|editor, cx| {
5901 editor.handle_input(" b=", cx);
5902 editor.handle_input("{", cx);
5903 editor.handle_input("c", cx);
5904 editor.handle_input("(", cx);
5905 });
5906 cx.assert_editor_state(
5907 &r#"
5908 <body><a b={c(ˇ)}>
5909 <script>
5910 var x = 1;<a b={c(ˇ)}
5911 </script>
5912 </body><a b={c(ˇ)}>
5913 "#
5914 .unindent(),
5915 );
5916
5917 // Brackets that were already autoclosed are skipped.
5918 cx.update_editor(|editor, cx| {
5919 editor.handle_input(")", cx);
5920 editor.handle_input("d", cx);
5921 editor.handle_input("}", cx);
5922 });
5923 cx.assert_editor_state(
5924 &r#"
5925 <body><a b={c()d}ˇ>
5926 <script>
5927 var x = 1;<a b={c()d}ˇ
5928 </script>
5929 </body><a b={c()d}ˇ>
5930 "#
5931 .unindent(),
5932 );
5933 cx.update_editor(|editor, cx| {
5934 editor.handle_input(">", cx);
5935 });
5936 cx.assert_editor_state(
5937 &r#"
5938 <body><a b={c()d}>ˇ
5939 <script>
5940 var x = 1;<a b={c()d}>ˇ
5941 </script>
5942 </body><a b={c()d}>ˇ
5943 "#
5944 .unindent(),
5945 );
5946
5947 // Reset
5948 cx.set_state(
5949 &r#"
5950 <body>ˇ
5951 <script>
5952 var x = 1;ˇ
5953 </script>
5954 </body>ˇ
5955 "#
5956 .unindent(),
5957 );
5958
5959 cx.update_editor(|editor, cx| {
5960 editor.handle_input("<", cx);
5961 });
5962 cx.assert_editor_state(
5963 &r#"
5964 <body><ˇ>
5965 <script>
5966 var x = 1;<ˇ
5967 </script>
5968 </body><ˇ>
5969 "#
5970 .unindent(),
5971 );
5972
5973 // When backspacing, the closing angle brackets are removed.
5974 cx.update_editor(|editor, cx| {
5975 editor.backspace(&Backspace, cx);
5976 });
5977 cx.assert_editor_state(
5978 &r#"
5979 <body>ˇ
5980 <script>
5981 var x = 1;ˇ
5982 </script>
5983 </body>ˇ
5984 "#
5985 .unindent(),
5986 );
5987
5988 // Block comments autoclose in JavaScript, but not HTML.
5989 cx.update_editor(|editor, cx| {
5990 editor.handle_input("/", cx);
5991 editor.handle_input("*", cx);
5992 });
5993 cx.assert_editor_state(
5994 &r#"
5995 <body>/*ˇ
5996 <script>
5997 var x = 1;/*ˇ */
5998 </script>
5999 </body>/*ˇ
6000 "#
6001 .unindent(),
6002 );
6003}
6004
6005#[gpui::test]
6006async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6007 init_test(cx, |_| {});
6008
6009 let mut cx = EditorTestContext::new(cx).await;
6010
6011 let rust_language = Arc::new(
6012 Language::new(
6013 LanguageConfig {
6014 name: "Rust".into(),
6015 brackets: serde_json::from_value(json!([
6016 { "start": "{", "end": "}", "close": true, "newline": true },
6017 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6018 ]))
6019 .unwrap(),
6020 autoclose_before: "})]>".into(),
6021 ..Default::default()
6022 },
6023 Some(tree_sitter_rust::LANGUAGE.into()),
6024 )
6025 .with_override_query("(string_literal) @string")
6026 .unwrap(),
6027 );
6028
6029 cx.language_registry().add(rust_language.clone());
6030 cx.update_buffer(|buffer, cx| {
6031 buffer.set_language(Some(rust_language), cx);
6032 });
6033
6034 cx.set_state(
6035 &r#"
6036 let x = ˇ
6037 "#
6038 .unindent(),
6039 );
6040
6041 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6042 cx.update_editor(|editor, cx| {
6043 editor.handle_input("\"", cx);
6044 });
6045 cx.assert_editor_state(
6046 &r#"
6047 let x = "ˇ"
6048 "#
6049 .unindent(),
6050 );
6051
6052 // Inserting another quotation mark. The cursor moves across the existing
6053 // automatically-inserted quotation mark.
6054 cx.update_editor(|editor, cx| {
6055 editor.handle_input("\"", cx);
6056 });
6057 cx.assert_editor_state(
6058 &r#"
6059 let x = ""ˇ
6060 "#
6061 .unindent(),
6062 );
6063
6064 // Reset
6065 cx.set_state(
6066 &r#"
6067 let x = ˇ
6068 "#
6069 .unindent(),
6070 );
6071
6072 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6073 cx.update_editor(|editor, cx| {
6074 editor.handle_input("\"", cx);
6075 editor.handle_input(" ", cx);
6076 editor.move_left(&Default::default(), cx);
6077 editor.handle_input("\\", cx);
6078 editor.handle_input("\"", cx);
6079 });
6080 cx.assert_editor_state(
6081 &r#"
6082 let x = "\"ˇ "
6083 "#
6084 .unindent(),
6085 );
6086
6087 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6088 // mark. Nothing is inserted.
6089 cx.update_editor(|editor, cx| {
6090 editor.move_right(&Default::default(), cx);
6091 editor.handle_input("\"", cx);
6092 });
6093 cx.assert_editor_state(
6094 &r#"
6095 let x = "\" "ˇ
6096 "#
6097 .unindent(),
6098 );
6099}
6100
6101#[gpui::test]
6102async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6103 init_test(cx, |_| {});
6104
6105 let language = Arc::new(Language::new(
6106 LanguageConfig {
6107 brackets: BracketPairConfig {
6108 pairs: vec![
6109 BracketPair {
6110 start: "{".to_string(),
6111 end: "}".to_string(),
6112 close: true,
6113 surround: true,
6114 newline: true,
6115 },
6116 BracketPair {
6117 start: "/* ".to_string(),
6118 end: "*/".to_string(),
6119 close: true,
6120 surround: true,
6121 ..Default::default()
6122 },
6123 ],
6124 ..Default::default()
6125 },
6126 ..Default::default()
6127 },
6128 Some(tree_sitter_rust::LANGUAGE.into()),
6129 ));
6130
6131 let text = r#"
6132 a
6133 b
6134 c
6135 "#
6136 .unindent();
6137
6138 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6139 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6140 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6141 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6142 .await;
6143
6144 view.update(cx, |view, cx| {
6145 view.change_selections(None, cx, |s| {
6146 s.select_display_ranges([
6147 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6148 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6149 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6150 ])
6151 });
6152
6153 view.handle_input("{", cx);
6154 view.handle_input("{", cx);
6155 view.handle_input("{", cx);
6156 assert_eq!(
6157 view.text(cx),
6158 "
6159 {{{a}}}
6160 {{{b}}}
6161 {{{c}}}
6162 "
6163 .unindent()
6164 );
6165 assert_eq!(
6166 view.selections.display_ranges(cx),
6167 [
6168 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6169 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6170 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6171 ]
6172 );
6173
6174 view.undo(&Undo, cx);
6175 view.undo(&Undo, cx);
6176 view.undo(&Undo, cx);
6177 assert_eq!(
6178 view.text(cx),
6179 "
6180 a
6181 b
6182 c
6183 "
6184 .unindent()
6185 );
6186 assert_eq!(
6187 view.selections.display_ranges(cx),
6188 [
6189 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6190 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6191 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6192 ]
6193 );
6194
6195 // Ensure inserting the first character of a multi-byte bracket pair
6196 // doesn't surround the selections with the bracket.
6197 view.handle_input("/", cx);
6198 assert_eq!(
6199 view.text(cx),
6200 "
6201 /
6202 /
6203 /
6204 "
6205 .unindent()
6206 );
6207 assert_eq!(
6208 view.selections.display_ranges(cx),
6209 [
6210 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6211 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6212 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6213 ]
6214 );
6215
6216 view.undo(&Undo, cx);
6217 assert_eq!(
6218 view.text(cx),
6219 "
6220 a
6221 b
6222 c
6223 "
6224 .unindent()
6225 );
6226 assert_eq!(
6227 view.selections.display_ranges(cx),
6228 [
6229 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6230 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6231 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6232 ]
6233 );
6234
6235 // Ensure inserting the last character of a multi-byte bracket pair
6236 // doesn't surround the selections with the bracket.
6237 view.handle_input("*", cx);
6238 assert_eq!(
6239 view.text(cx),
6240 "
6241 *
6242 *
6243 *
6244 "
6245 .unindent()
6246 );
6247 assert_eq!(
6248 view.selections.display_ranges(cx),
6249 [
6250 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6251 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6252 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6253 ]
6254 );
6255 });
6256}
6257
6258#[gpui::test]
6259async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6260 init_test(cx, |_| {});
6261
6262 let language = Arc::new(Language::new(
6263 LanguageConfig {
6264 brackets: BracketPairConfig {
6265 pairs: vec![BracketPair {
6266 start: "{".to_string(),
6267 end: "}".to_string(),
6268 close: true,
6269 surround: true,
6270 newline: true,
6271 }],
6272 ..Default::default()
6273 },
6274 autoclose_before: "}".to_string(),
6275 ..Default::default()
6276 },
6277 Some(tree_sitter_rust::LANGUAGE.into()),
6278 ));
6279
6280 let text = r#"
6281 a
6282 b
6283 c
6284 "#
6285 .unindent();
6286
6287 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6288 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6289 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6290 editor
6291 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6292 .await;
6293
6294 editor.update(cx, |editor, cx| {
6295 editor.change_selections(None, cx, |s| {
6296 s.select_ranges([
6297 Point::new(0, 1)..Point::new(0, 1),
6298 Point::new(1, 1)..Point::new(1, 1),
6299 Point::new(2, 1)..Point::new(2, 1),
6300 ])
6301 });
6302
6303 editor.handle_input("{", cx);
6304 editor.handle_input("{", cx);
6305 editor.handle_input("_", cx);
6306 assert_eq!(
6307 editor.text(cx),
6308 "
6309 a{{_}}
6310 b{{_}}
6311 c{{_}}
6312 "
6313 .unindent()
6314 );
6315 assert_eq!(
6316 editor.selections.ranges::<Point>(cx),
6317 [
6318 Point::new(0, 4)..Point::new(0, 4),
6319 Point::new(1, 4)..Point::new(1, 4),
6320 Point::new(2, 4)..Point::new(2, 4)
6321 ]
6322 );
6323
6324 editor.backspace(&Default::default(), cx);
6325 editor.backspace(&Default::default(), cx);
6326 assert_eq!(
6327 editor.text(cx),
6328 "
6329 a{}
6330 b{}
6331 c{}
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 editor.selections.ranges::<Point>(cx),
6337 [
6338 Point::new(0, 2)..Point::new(0, 2),
6339 Point::new(1, 2)..Point::new(1, 2),
6340 Point::new(2, 2)..Point::new(2, 2)
6341 ]
6342 );
6343
6344 editor.delete_to_previous_word_start(&Default::default(), cx);
6345 assert_eq!(
6346 editor.text(cx),
6347 "
6348 a
6349 b
6350 c
6351 "
6352 .unindent()
6353 );
6354 assert_eq!(
6355 editor.selections.ranges::<Point>(cx),
6356 [
6357 Point::new(0, 1)..Point::new(0, 1),
6358 Point::new(1, 1)..Point::new(1, 1),
6359 Point::new(2, 1)..Point::new(2, 1)
6360 ]
6361 );
6362 });
6363}
6364
6365#[gpui::test]
6366async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6367 init_test(cx, |settings| {
6368 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6369 });
6370
6371 let mut cx = EditorTestContext::new(cx).await;
6372
6373 let language = Arc::new(Language::new(
6374 LanguageConfig {
6375 brackets: BracketPairConfig {
6376 pairs: vec![
6377 BracketPair {
6378 start: "{".to_string(),
6379 end: "}".to_string(),
6380 close: true,
6381 surround: true,
6382 newline: true,
6383 },
6384 BracketPair {
6385 start: "(".to_string(),
6386 end: ")".to_string(),
6387 close: true,
6388 surround: true,
6389 newline: true,
6390 },
6391 BracketPair {
6392 start: "[".to_string(),
6393 end: "]".to_string(),
6394 close: false,
6395 surround: true,
6396 newline: true,
6397 },
6398 ],
6399 ..Default::default()
6400 },
6401 autoclose_before: "})]".to_string(),
6402 ..Default::default()
6403 },
6404 Some(tree_sitter_rust::LANGUAGE.into()),
6405 ));
6406
6407 cx.language_registry().add(language.clone());
6408 cx.update_buffer(|buffer, cx| {
6409 buffer.set_language(Some(language), cx);
6410 });
6411
6412 cx.set_state(
6413 &"
6414 {(ˇ)}
6415 [[ˇ]]
6416 {(ˇ)}
6417 "
6418 .unindent(),
6419 );
6420
6421 cx.update_editor(|view, cx| {
6422 view.backspace(&Default::default(), cx);
6423 view.backspace(&Default::default(), cx);
6424 });
6425
6426 cx.assert_editor_state(
6427 &"
6428 ˇ
6429 ˇ]]
6430 ˇ
6431 "
6432 .unindent(),
6433 );
6434
6435 cx.update_editor(|view, cx| {
6436 view.handle_input("{", cx);
6437 view.handle_input("{", cx);
6438 view.move_right(&MoveRight, cx);
6439 view.move_right(&MoveRight, cx);
6440 view.move_left(&MoveLeft, cx);
6441 view.move_left(&MoveLeft, cx);
6442 view.backspace(&Default::default(), cx);
6443 });
6444
6445 cx.assert_editor_state(
6446 &"
6447 {ˇ}
6448 {ˇ}]]
6449 {ˇ}
6450 "
6451 .unindent(),
6452 );
6453
6454 cx.update_editor(|view, cx| {
6455 view.backspace(&Default::default(), cx);
6456 });
6457
6458 cx.assert_editor_state(
6459 &"
6460 ˇ
6461 ˇ]]
6462 ˇ
6463 "
6464 .unindent(),
6465 );
6466}
6467
6468#[gpui::test]
6469async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6470 init_test(cx, |_| {});
6471
6472 let language = Arc::new(Language::new(
6473 LanguageConfig::default(),
6474 Some(tree_sitter_rust::LANGUAGE.into()),
6475 ));
6476
6477 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6478 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6479 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6480 editor
6481 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6482 .await;
6483
6484 editor.update(cx, |editor, cx| {
6485 editor.set_auto_replace_emoji_shortcode(true);
6486
6487 editor.handle_input("Hello ", cx);
6488 editor.handle_input(":wave", cx);
6489 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6490
6491 editor.handle_input(":", cx);
6492 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6493
6494 editor.handle_input(" :smile", cx);
6495 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6496
6497 editor.handle_input(":", cx);
6498 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6499
6500 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6501 editor.handle_input(":wave", cx);
6502 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6503
6504 editor.handle_input(":", cx);
6505 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6506
6507 editor.handle_input(":1", cx);
6508 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6509
6510 editor.handle_input(":", cx);
6511 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6512
6513 // Ensure shortcode does not get replaced when it is part of a word
6514 editor.handle_input(" Test:wave", cx);
6515 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6516
6517 editor.handle_input(":", cx);
6518 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6519
6520 editor.set_auto_replace_emoji_shortcode(false);
6521
6522 // Ensure shortcode does not get replaced when auto replace is off
6523 editor.handle_input(" :wave", cx);
6524 assert_eq!(
6525 editor.text(cx),
6526 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6527 );
6528
6529 editor.handle_input(":", cx);
6530 assert_eq!(
6531 editor.text(cx),
6532 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6533 );
6534 });
6535}
6536
6537#[gpui::test]
6538async fn test_snippets(cx: &mut gpui::TestAppContext) {
6539 init_test(cx, |_| {});
6540
6541 let (text, insertion_ranges) = marked_text_ranges(
6542 indoc! {"
6543 a.ˇ b
6544 a.ˇ b
6545 a.ˇ b
6546 "},
6547 false,
6548 );
6549
6550 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6551 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6552
6553 editor.update(cx, |editor, cx| {
6554 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6555
6556 editor
6557 .insert_snippet(&insertion_ranges, snippet, cx)
6558 .unwrap();
6559
6560 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6561 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6562 assert_eq!(editor.text(cx), expected_text);
6563 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6564 }
6565
6566 assert(
6567 editor,
6568 cx,
6569 indoc! {"
6570 a.f(«one», two, «three») b
6571 a.f(«one», two, «three») b
6572 a.f(«one», two, «three») b
6573 "},
6574 );
6575
6576 // Can't move earlier than the first tab stop
6577 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6578 assert(
6579 editor,
6580 cx,
6581 indoc! {"
6582 a.f(«one», two, «three») b
6583 a.f(«one», two, «three») b
6584 a.f(«one», two, «three») b
6585 "},
6586 );
6587
6588 assert!(editor.move_to_next_snippet_tabstop(cx));
6589 assert(
6590 editor,
6591 cx,
6592 indoc! {"
6593 a.f(one, «two», three) b
6594 a.f(one, «two», three) b
6595 a.f(one, «two», three) b
6596 "},
6597 );
6598
6599 editor.move_to_prev_snippet_tabstop(cx);
6600 assert(
6601 editor,
6602 cx,
6603 indoc! {"
6604 a.f(«one», two, «three») b
6605 a.f(«one», two, «three») b
6606 a.f(«one», two, «three») b
6607 "},
6608 );
6609
6610 assert!(editor.move_to_next_snippet_tabstop(cx));
6611 assert(
6612 editor,
6613 cx,
6614 indoc! {"
6615 a.f(one, «two», three) b
6616 a.f(one, «two», three) b
6617 a.f(one, «two», three) b
6618 "},
6619 );
6620 assert!(editor.move_to_next_snippet_tabstop(cx));
6621 assert(
6622 editor,
6623 cx,
6624 indoc! {"
6625 a.f(one, two, three)ˇ b
6626 a.f(one, two, three)ˇ b
6627 a.f(one, two, three)ˇ b
6628 "},
6629 );
6630
6631 // As soon as the last tab stop is reached, snippet state is gone
6632 editor.move_to_prev_snippet_tabstop(cx);
6633 assert(
6634 editor,
6635 cx,
6636 indoc! {"
6637 a.f(one, two, three)ˇ b
6638 a.f(one, two, three)ˇ b
6639 a.f(one, two, three)ˇ b
6640 "},
6641 );
6642 });
6643}
6644
6645#[gpui::test]
6646async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6647 init_test(cx, |_| {});
6648
6649 let fs = FakeFs::new(cx.executor());
6650 fs.insert_file("/file.rs", Default::default()).await;
6651
6652 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6653
6654 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6655 language_registry.add(rust_lang());
6656 let mut fake_servers = language_registry.register_fake_lsp(
6657 "Rust",
6658 FakeLspAdapter {
6659 capabilities: lsp::ServerCapabilities {
6660 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6661 ..Default::default()
6662 },
6663 ..Default::default()
6664 },
6665 );
6666
6667 let buffer = project
6668 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6669 .await
6670 .unwrap();
6671
6672 cx.executor().start_waiting();
6673 let fake_server = fake_servers.next().await.unwrap();
6674
6675 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6676 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6677 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6678 assert!(cx.read(|cx| editor.is_dirty(cx)));
6679
6680 let save = editor
6681 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6682 .unwrap();
6683 fake_server
6684 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6685 assert_eq!(
6686 params.text_document.uri,
6687 lsp::Url::from_file_path("/file.rs").unwrap()
6688 );
6689 assert_eq!(params.options.tab_size, 4);
6690 Ok(Some(vec![lsp::TextEdit::new(
6691 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6692 ", ".to_string(),
6693 )]))
6694 })
6695 .next()
6696 .await;
6697 cx.executor().start_waiting();
6698 save.await;
6699
6700 assert_eq!(
6701 editor.update(cx, |editor, cx| editor.text(cx)),
6702 "one, two\nthree\n"
6703 );
6704 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6705
6706 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6707 assert!(cx.read(|cx| editor.is_dirty(cx)));
6708
6709 // Ensure we can still save even if formatting hangs.
6710 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6711 assert_eq!(
6712 params.text_document.uri,
6713 lsp::Url::from_file_path("/file.rs").unwrap()
6714 );
6715 futures::future::pending::<()>().await;
6716 unreachable!()
6717 });
6718 let save = editor
6719 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6720 .unwrap();
6721 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6722 cx.executor().start_waiting();
6723 save.await;
6724 assert_eq!(
6725 editor.update(cx, |editor, cx| editor.text(cx)),
6726 "one\ntwo\nthree\n"
6727 );
6728 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6729
6730 // For non-dirty buffer, no formatting request should be sent
6731 let save = editor
6732 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6733 .unwrap();
6734 let _pending_format_request = fake_server
6735 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6736 panic!("Should not be invoked on non-dirty buffer");
6737 })
6738 .next();
6739 cx.executor().start_waiting();
6740 save.await;
6741
6742 // Set rust language override and assert overridden tabsize is sent to language server
6743 update_test_language_settings(cx, |settings| {
6744 settings.languages.insert(
6745 "Rust".into(),
6746 LanguageSettingsContent {
6747 tab_size: NonZeroU32::new(8),
6748 ..Default::default()
6749 },
6750 );
6751 });
6752
6753 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6754 assert!(cx.read(|cx| editor.is_dirty(cx)));
6755 let save = editor
6756 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6757 .unwrap();
6758 fake_server
6759 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6760 assert_eq!(
6761 params.text_document.uri,
6762 lsp::Url::from_file_path("/file.rs").unwrap()
6763 );
6764 assert_eq!(params.options.tab_size, 8);
6765 Ok(Some(vec![]))
6766 })
6767 .next()
6768 .await;
6769 cx.executor().start_waiting();
6770 save.await;
6771}
6772
6773#[gpui::test]
6774async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6775 init_test(cx, |_| {});
6776
6777 let cols = 4;
6778 let rows = 10;
6779 let sample_text_1 = sample_text(rows, cols, 'a');
6780 assert_eq!(
6781 sample_text_1,
6782 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6783 );
6784 let sample_text_2 = sample_text(rows, cols, 'l');
6785 assert_eq!(
6786 sample_text_2,
6787 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6788 );
6789 let sample_text_3 = sample_text(rows, cols, 'v');
6790 assert_eq!(
6791 sample_text_3,
6792 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6793 );
6794
6795 let fs = FakeFs::new(cx.executor());
6796 fs.insert_tree(
6797 "/a",
6798 json!({
6799 "main.rs": sample_text_1,
6800 "other.rs": sample_text_2,
6801 "lib.rs": sample_text_3,
6802 }),
6803 )
6804 .await;
6805
6806 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6807 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6808 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6809
6810 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6811 language_registry.add(rust_lang());
6812 let mut fake_servers = language_registry.register_fake_lsp(
6813 "Rust",
6814 FakeLspAdapter {
6815 capabilities: lsp::ServerCapabilities {
6816 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6817 ..Default::default()
6818 },
6819 ..Default::default()
6820 },
6821 );
6822
6823 let worktree = project.update(cx, |project, cx| {
6824 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6825 assert_eq!(worktrees.len(), 1);
6826 worktrees.pop().unwrap()
6827 });
6828 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6829
6830 let buffer_1 = project
6831 .update(cx, |project, cx| {
6832 project.open_buffer((worktree_id, "main.rs"), cx)
6833 })
6834 .await
6835 .unwrap();
6836 let buffer_2 = project
6837 .update(cx, |project, cx| {
6838 project.open_buffer((worktree_id, "other.rs"), cx)
6839 })
6840 .await
6841 .unwrap();
6842 let buffer_3 = project
6843 .update(cx, |project, cx| {
6844 project.open_buffer((worktree_id, "lib.rs"), cx)
6845 })
6846 .await
6847 .unwrap();
6848
6849 let multi_buffer = cx.new_model(|cx| {
6850 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6851 multi_buffer.push_excerpts(
6852 buffer_1.clone(),
6853 [
6854 ExcerptRange {
6855 context: Point::new(0, 0)..Point::new(3, 0),
6856 primary: None,
6857 },
6858 ExcerptRange {
6859 context: Point::new(5, 0)..Point::new(7, 0),
6860 primary: None,
6861 },
6862 ExcerptRange {
6863 context: Point::new(9, 0)..Point::new(10, 4),
6864 primary: None,
6865 },
6866 ],
6867 cx,
6868 );
6869 multi_buffer.push_excerpts(
6870 buffer_2.clone(),
6871 [
6872 ExcerptRange {
6873 context: Point::new(0, 0)..Point::new(3, 0),
6874 primary: None,
6875 },
6876 ExcerptRange {
6877 context: Point::new(5, 0)..Point::new(7, 0),
6878 primary: None,
6879 },
6880 ExcerptRange {
6881 context: Point::new(9, 0)..Point::new(10, 4),
6882 primary: None,
6883 },
6884 ],
6885 cx,
6886 );
6887 multi_buffer.push_excerpts(
6888 buffer_3.clone(),
6889 [
6890 ExcerptRange {
6891 context: Point::new(0, 0)..Point::new(3, 0),
6892 primary: None,
6893 },
6894 ExcerptRange {
6895 context: Point::new(5, 0)..Point::new(7, 0),
6896 primary: None,
6897 },
6898 ExcerptRange {
6899 context: Point::new(9, 0)..Point::new(10, 4),
6900 primary: None,
6901 },
6902 ],
6903 cx,
6904 );
6905 multi_buffer
6906 });
6907 let multi_buffer_editor = cx.new_view(|cx| {
6908 Editor::new(
6909 EditorMode::Full,
6910 multi_buffer,
6911 Some(project.clone()),
6912 true,
6913 cx,
6914 )
6915 });
6916
6917 multi_buffer_editor.update(cx, |editor, cx| {
6918 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6919 editor.insert("|one|two|three|", cx);
6920 });
6921 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6922 multi_buffer_editor.update(cx, |editor, cx| {
6923 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6924 s.select_ranges(Some(60..70))
6925 });
6926 editor.insert("|four|five|six|", cx);
6927 });
6928 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6929
6930 // First two buffers should be edited, but not the third one.
6931 assert_eq!(
6932 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6933 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6934 );
6935 buffer_1.update(cx, |buffer, _| {
6936 assert!(buffer.is_dirty());
6937 assert_eq!(
6938 buffer.text(),
6939 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6940 )
6941 });
6942 buffer_2.update(cx, |buffer, _| {
6943 assert!(buffer.is_dirty());
6944 assert_eq!(
6945 buffer.text(),
6946 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6947 )
6948 });
6949 buffer_3.update(cx, |buffer, _| {
6950 assert!(!buffer.is_dirty());
6951 assert_eq!(buffer.text(), sample_text_3,)
6952 });
6953
6954 cx.executor().start_waiting();
6955 let save = multi_buffer_editor
6956 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6957 .unwrap();
6958
6959 let fake_server = fake_servers.next().await.unwrap();
6960 fake_server
6961 .server
6962 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6963 Ok(Some(vec![lsp::TextEdit::new(
6964 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6965 format!("[{} formatted]", params.text_document.uri),
6966 )]))
6967 })
6968 .detach();
6969 save.await;
6970
6971 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6972 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6973 assert_eq!(
6974 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6975 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6976 );
6977 buffer_1.update(cx, |buffer, _| {
6978 assert!(!buffer.is_dirty());
6979 assert_eq!(
6980 buffer.text(),
6981 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6982 )
6983 });
6984 buffer_2.update(cx, |buffer, _| {
6985 assert!(!buffer.is_dirty());
6986 assert_eq!(
6987 buffer.text(),
6988 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6989 )
6990 });
6991 buffer_3.update(cx, |buffer, _| {
6992 assert!(!buffer.is_dirty());
6993 assert_eq!(buffer.text(), sample_text_3,)
6994 });
6995}
6996
6997#[gpui::test]
6998async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6999 init_test(cx, |_| {});
7000
7001 let fs = FakeFs::new(cx.executor());
7002 fs.insert_file("/file.rs", Default::default()).await;
7003
7004 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7005
7006 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7007 language_registry.add(rust_lang());
7008 let mut fake_servers = language_registry.register_fake_lsp(
7009 "Rust",
7010 FakeLspAdapter {
7011 capabilities: lsp::ServerCapabilities {
7012 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7013 ..Default::default()
7014 },
7015 ..Default::default()
7016 },
7017 );
7018
7019 let buffer = project
7020 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7021 .await
7022 .unwrap();
7023
7024 cx.executor().start_waiting();
7025 let fake_server = fake_servers.next().await.unwrap();
7026
7027 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7028 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7029 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7030 assert!(cx.read(|cx| editor.is_dirty(cx)));
7031
7032 let save = editor
7033 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7034 .unwrap();
7035 fake_server
7036 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7037 assert_eq!(
7038 params.text_document.uri,
7039 lsp::Url::from_file_path("/file.rs").unwrap()
7040 );
7041 assert_eq!(params.options.tab_size, 4);
7042 Ok(Some(vec![lsp::TextEdit::new(
7043 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7044 ", ".to_string(),
7045 )]))
7046 })
7047 .next()
7048 .await;
7049 cx.executor().start_waiting();
7050 save.await;
7051 assert_eq!(
7052 editor.update(cx, |editor, cx| editor.text(cx)),
7053 "one, two\nthree\n"
7054 );
7055 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7056
7057 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7058 assert!(cx.read(|cx| editor.is_dirty(cx)));
7059
7060 // Ensure we can still save even if formatting hangs.
7061 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7062 move |params, _| async move {
7063 assert_eq!(
7064 params.text_document.uri,
7065 lsp::Url::from_file_path("/file.rs").unwrap()
7066 );
7067 futures::future::pending::<()>().await;
7068 unreachable!()
7069 },
7070 );
7071 let save = editor
7072 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7073 .unwrap();
7074 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7075 cx.executor().start_waiting();
7076 save.await;
7077 assert_eq!(
7078 editor.update(cx, |editor, cx| editor.text(cx)),
7079 "one\ntwo\nthree\n"
7080 );
7081 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7082
7083 // For non-dirty buffer, no formatting request should be sent
7084 let save = editor
7085 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7086 .unwrap();
7087 let _pending_format_request = fake_server
7088 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7089 panic!("Should not be invoked on non-dirty buffer");
7090 })
7091 .next();
7092 cx.executor().start_waiting();
7093 save.await;
7094
7095 // Set Rust language override and assert overridden tabsize is sent to language server
7096 update_test_language_settings(cx, |settings| {
7097 settings.languages.insert(
7098 "Rust".into(),
7099 LanguageSettingsContent {
7100 tab_size: NonZeroU32::new(8),
7101 ..Default::default()
7102 },
7103 );
7104 });
7105
7106 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7107 assert!(cx.read(|cx| editor.is_dirty(cx)));
7108 let save = editor
7109 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7110 .unwrap();
7111 fake_server
7112 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7113 assert_eq!(
7114 params.text_document.uri,
7115 lsp::Url::from_file_path("/file.rs").unwrap()
7116 );
7117 assert_eq!(params.options.tab_size, 8);
7118 Ok(Some(vec![]))
7119 })
7120 .next()
7121 .await;
7122 cx.executor().start_waiting();
7123 save.await;
7124}
7125
7126#[gpui::test]
7127async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7128 init_test(cx, |settings| {
7129 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7130 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7131 ))
7132 });
7133
7134 let fs = FakeFs::new(cx.executor());
7135 fs.insert_file("/file.rs", Default::default()).await;
7136
7137 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7138
7139 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7140 language_registry.add(Arc::new(Language::new(
7141 LanguageConfig {
7142 name: "Rust".into(),
7143 matcher: LanguageMatcher {
7144 path_suffixes: vec!["rs".to_string()],
7145 ..Default::default()
7146 },
7147 ..LanguageConfig::default()
7148 },
7149 Some(tree_sitter_rust::LANGUAGE.into()),
7150 )));
7151 update_test_language_settings(cx, |settings| {
7152 // Enable Prettier formatting for the same buffer, and ensure
7153 // LSP is called instead of Prettier.
7154 settings.defaults.prettier = Some(PrettierSettings {
7155 allowed: true,
7156 ..PrettierSettings::default()
7157 });
7158 });
7159 let mut fake_servers = language_registry.register_fake_lsp(
7160 "Rust",
7161 FakeLspAdapter {
7162 capabilities: lsp::ServerCapabilities {
7163 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7164 ..Default::default()
7165 },
7166 ..Default::default()
7167 },
7168 );
7169
7170 let buffer = project
7171 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7172 .await
7173 .unwrap();
7174
7175 cx.executor().start_waiting();
7176 let fake_server = fake_servers.next().await.unwrap();
7177
7178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7179 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7180 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7181
7182 let format = editor
7183 .update(cx, |editor, cx| {
7184 editor.perform_format(
7185 project.clone(),
7186 FormatTrigger::Manual,
7187 FormatTarget::Buffer,
7188 cx,
7189 )
7190 })
7191 .unwrap();
7192 fake_server
7193 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7194 assert_eq!(
7195 params.text_document.uri,
7196 lsp::Url::from_file_path("/file.rs").unwrap()
7197 );
7198 assert_eq!(params.options.tab_size, 4);
7199 Ok(Some(vec![lsp::TextEdit::new(
7200 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7201 ", ".to_string(),
7202 )]))
7203 })
7204 .next()
7205 .await;
7206 cx.executor().start_waiting();
7207 format.await;
7208 assert_eq!(
7209 editor.update(cx, |editor, cx| editor.text(cx)),
7210 "one, two\nthree\n"
7211 );
7212
7213 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7214 // Ensure we don't lock if formatting hangs.
7215 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7216 assert_eq!(
7217 params.text_document.uri,
7218 lsp::Url::from_file_path("/file.rs").unwrap()
7219 );
7220 futures::future::pending::<()>().await;
7221 unreachable!()
7222 });
7223 let format = editor
7224 .update(cx, |editor, cx| {
7225 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7226 })
7227 .unwrap();
7228 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7229 cx.executor().start_waiting();
7230 format.await;
7231 assert_eq!(
7232 editor.update(cx, |editor, cx| editor.text(cx)),
7233 "one\ntwo\nthree\n"
7234 );
7235}
7236
7237#[gpui::test]
7238async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7239 init_test(cx, |_| {});
7240
7241 let mut cx = EditorLspTestContext::new_rust(
7242 lsp::ServerCapabilities {
7243 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7244 ..Default::default()
7245 },
7246 cx,
7247 )
7248 .await;
7249
7250 cx.set_state(indoc! {"
7251 one.twoˇ
7252 "});
7253
7254 // The format request takes a long time. When it completes, it inserts
7255 // a newline and an indent before the `.`
7256 cx.lsp
7257 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7258 let executor = cx.background_executor().clone();
7259 async move {
7260 executor.timer(Duration::from_millis(100)).await;
7261 Ok(Some(vec![lsp::TextEdit {
7262 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7263 new_text: "\n ".into(),
7264 }]))
7265 }
7266 });
7267
7268 // Submit a format request.
7269 let format_1 = cx
7270 .update_editor(|editor, cx| editor.format(&Format, cx))
7271 .unwrap();
7272 cx.executor().run_until_parked();
7273
7274 // Submit a second format request.
7275 let format_2 = cx
7276 .update_editor(|editor, cx| editor.format(&Format, cx))
7277 .unwrap();
7278 cx.executor().run_until_parked();
7279
7280 // Wait for both format requests to complete
7281 cx.executor().advance_clock(Duration::from_millis(200));
7282 cx.executor().start_waiting();
7283 format_1.await.unwrap();
7284 cx.executor().start_waiting();
7285 format_2.await.unwrap();
7286
7287 // The formatting edits only happens once.
7288 cx.assert_editor_state(indoc! {"
7289 one
7290 .twoˇ
7291 "});
7292}
7293
7294#[gpui::test]
7295async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7296 init_test(cx, |settings| {
7297 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7298 });
7299
7300 let mut cx = EditorLspTestContext::new_rust(
7301 lsp::ServerCapabilities {
7302 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7303 ..Default::default()
7304 },
7305 cx,
7306 )
7307 .await;
7308
7309 // Set up a buffer white some trailing whitespace and no trailing newline.
7310 cx.set_state(
7311 &[
7312 "one ", //
7313 "twoˇ", //
7314 "three ", //
7315 "four", //
7316 ]
7317 .join("\n"),
7318 );
7319
7320 // Submit a format request.
7321 let format = cx
7322 .update_editor(|editor, cx| editor.format(&Format, cx))
7323 .unwrap();
7324
7325 // Record which buffer changes have been sent to the language server
7326 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7327 cx.lsp
7328 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7329 let buffer_changes = buffer_changes.clone();
7330 move |params, _| {
7331 buffer_changes.lock().extend(
7332 params
7333 .content_changes
7334 .into_iter()
7335 .map(|e| (e.range.unwrap(), e.text)),
7336 );
7337 }
7338 });
7339
7340 // Handle formatting requests to the language server.
7341 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7342 let buffer_changes = buffer_changes.clone();
7343 move |_, _| {
7344 // When formatting is requested, trailing whitespace has already been stripped,
7345 // and the trailing newline has already been added.
7346 assert_eq!(
7347 &buffer_changes.lock()[1..],
7348 &[
7349 (
7350 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7351 "".into()
7352 ),
7353 (
7354 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7355 "".into()
7356 ),
7357 (
7358 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7359 "\n".into()
7360 ),
7361 ]
7362 );
7363
7364 // Insert blank lines between each line of the buffer.
7365 async move {
7366 Ok(Some(vec![
7367 lsp::TextEdit {
7368 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7369 new_text: "\n".into(),
7370 },
7371 lsp::TextEdit {
7372 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7373 new_text: "\n".into(),
7374 },
7375 ]))
7376 }
7377 }
7378 });
7379
7380 // After formatting the buffer, the trailing whitespace is stripped,
7381 // a newline is appended, and the edits provided by the language server
7382 // have been applied.
7383 format.await.unwrap();
7384 cx.assert_editor_state(
7385 &[
7386 "one", //
7387 "", //
7388 "twoˇ", //
7389 "", //
7390 "three", //
7391 "four", //
7392 "", //
7393 ]
7394 .join("\n"),
7395 );
7396
7397 // Undoing the formatting undoes the trailing whitespace removal, the
7398 // trailing newline, and the LSP edits.
7399 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7400 cx.assert_editor_state(
7401 &[
7402 "one ", //
7403 "twoˇ", //
7404 "three ", //
7405 "four", //
7406 ]
7407 .join("\n"),
7408 );
7409}
7410
7411#[gpui::test]
7412async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7413 cx: &mut gpui::TestAppContext,
7414) {
7415 init_test(cx, |_| {});
7416
7417 cx.update(|cx| {
7418 cx.update_global::<SettingsStore, _>(|settings, cx| {
7419 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7420 settings.auto_signature_help = Some(true);
7421 });
7422 });
7423 });
7424
7425 let mut cx = EditorLspTestContext::new_rust(
7426 lsp::ServerCapabilities {
7427 signature_help_provider: Some(lsp::SignatureHelpOptions {
7428 ..Default::default()
7429 }),
7430 ..Default::default()
7431 },
7432 cx,
7433 )
7434 .await;
7435
7436 let language = Language::new(
7437 LanguageConfig {
7438 name: "Rust".into(),
7439 brackets: BracketPairConfig {
7440 pairs: vec![
7441 BracketPair {
7442 start: "{".to_string(),
7443 end: "}".to_string(),
7444 close: true,
7445 surround: true,
7446 newline: true,
7447 },
7448 BracketPair {
7449 start: "(".to_string(),
7450 end: ")".to_string(),
7451 close: true,
7452 surround: true,
7453 newline: true,
7454 },
7455 BracketPair {
7456 start: "/*".to_string(),
7457 end: " */".to_string(),
7458 close: true,
7459 surround: true,
7460 newline: true,
7461 },
7462 BracketPair {
7463 start: "[".to_string(),
7464 end: "]".to_string(),
7465 close: false,
7466 surround: false,
7467 newline: true,
7468 },
7469 BracketPair {
7470 start: "\"".to_string(),
7471 end: "\"".to_string(),
7472 close: true,
7473 surround: true,
7474 newline: false,
7475 },
7476 BracketPair {
7477 start: "<".to_string(),
7478 end: ">".to_string(),
7479 close: false,
7480 surround: true,
7481 newline: true,
7482 },
7483 ],
7484 ..Default::default()
7485 },
7486 autoclose_before: "})]".to_string(),
7487 ..Default::default()
7488 },
7489 Some(tree_sitter_rust::LANGUAGE.into()),
7490 );
7491 let language = Arc::new(language);
7492
7493 cx.language_registry().add(language.clone());
7494 cx.update_buffer(|buffer, cx| {
7495 buffer.set_language(Some(language), cx);
7496 });
7497
7498 cx.set_state(
7499 &r#"
7500 fn main() {
7501 sampleˇ
7502 }
7503 "#
7504 .unindent(),
7505 );
7506
7507 cx.update_editor(|view, cx| {
7508 view.handle_input("(", cx);
7509 });
7510 cx.assert_editor_state(
7511 &"
7512 fn main() {
7513 sample(ˇ)
7514 }
7515 "
7516 .unindent(),
7517 );
7518
7519 let mocked_response = lsp::SignatureHelp {
7520 signatures: vec![lsp::SignatureInformation {
7521 label: "fn sample(param1: u8, param2: u8)".to_string(),
7522 documentation: None,
7523 parameters: Some(vec![
7524 lsp::ParameterInformation {
7525 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7526 documentation: None,
7527 },
7528 lsp::ParameterInformation {
7529 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7530 documentation: None,
7531 },
7532 ]),
7533 active_parameter: None,
7534 }],
7535 active_signature: Some(0),
7536 active_parameter: Some(0),
7537 };
7538 handle_signature_help_request(&mut cx, mocked_response).await;
7539
7540 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7541 .await;
7542
7543 cx.editor(|editor, _| {
7544 let signature_help_state = editor.signature_help_state.popover().cloned();
7545 assert!(signature_help_state.is_some());
7546 let ParsedMarkdown {
7547 text, highlights, ..
7548 } = signature_help_state.unwrap().parsed_content;
7549 assert_eq!(text, "param1: u8, param2: u8");
7550 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7551 });
7552}
7553
7554#[gpui::test]
7555async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7556 init_test(cx, |_| {});
7557
7558 cx.update(|cx| {
7559 cx.update_global::<SettingsStore, _>(|settings, cx| {
7560 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7561 settings.auto_signature_help = Some(false);
7562 settings.show_signature_help_after_edits = Some(false);
7563 });
7564 });
7565 });
7566
7567 let mut cx = EditorLspTestContext::new_rust(
7568 lsp::ServerCapabilities {
7569 signature_help_provider: Some(lsp::SignatureHelpOptions {
7570 ..Default::default()
7571 }),
7572 ..Default::default()
7573 },
7574 cx,
7575 )
7576 .await;
7577
7578 let language = Language::new(
7579 LanguageConfig {
7580 name: "Rust".into(),
7581 brackets: BracketPairConfig {
7582 pairs: vec![
7583 BracketPair {
7584 start: "{".to_string(),
7585 end: "}".to_string(),
7586 close: true,
7587 surround: true,
7588 newline: true,
7589 },
7590 BracketPair {
7591 start: "(".to_string(),
7592 end: ")".to_string(),
7593 close: true,
7594 surround: true,
7595 newline: true,
7596 },
7597 BracketPair {
7598 start: "/*".to_string(),
7599 end: " */".to_string(),
7600 close: true,
7601 surround: true,
7602 newline: true,
7603 },
7604 BracketPair {
7605 start: "[".to_string(),
7606 end: "]".to_string(),
7607 close: false,
7608 surround: false,
7609 newline: true,
7610 },
7611 BracketPair {
7612 start: "\"".to_string(),
7613 end: "\"".to_string(),
7614 close: true,
7615 surround: true,
7616 newline: false,
7617 },
7618 BracketPair {
7619 start: "<".to_string(),
7620 end: ">".to_string(),
7621 close: false,
7622 surround: true,
7623 newline: true,
7624 },
7625 ],
7626 ..Default::default()
7627 },
7628 autoclose_before: "})]".to_string(),
7629 ..Default::default()
7630 },
7631 Some(tree_sitter_rust::LANGUAGE.into()),
7632 );
7633 let language = Arc::new(language);
7634
7635 cx.language_registry().add(language.clone());
7636 cx.update_buffer(|buffer, cx| {
7637 buffer.set_language(Some(language), cx);
7638 });
7639
7640 // Ensure that signature_help is not called when no signature help is enabled.
7641 cx.set_state(
7642 &r#"
7643 fn main() {
7644 sampleˇ
7645 }
7646 "#
7647 .unindent(),
7648 );
7649 cx.update_editor(|view, cx| {
7650 view.handle_input("(", cx);
7651 });
7652 cx.assert_editor_state(
7653 &"
7654 fn main() {
7655 sample(ˇ)
7656 }
7657 "
7658 .unindent(),
7659 );
7660 cx.editor(|editor, _| {
7661 assert!(editor.signature_help_state.task().is_none());
7662 });
7663
7664 let mocked_response = lsp::SignatureHelp {
7665 signatures: vec![lsp::SignatureInformation {
7666 label: "fn sample(param1: u8, param2: u8)".to_string(),
7667 documentation: None,
7668 parameters: Some(vec![
7669 lsp::ParameterInformation {
7670 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7671 documentation: None,
7672 },
7673 lsp::ParameterInformation {
7674 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7675 documentation: None,
7676 },
7677 ]),
7678 active_parameter: None,
7679 }],
7680 active_signature: Some(0),
7681 active_parameter: Some(0),
7682 };
7683
7684 // Ensure that signature_help is called when enabled afte edits
7685 cx.update(|cx| {
7686 cx.update_global::<SettingsStore, _>(|settings, cx| {
7687 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7688 settings.auto_signature_help = Some(false);
7689 settings.show_signature_help_after_edits = Some(true);
7690 });
7691 });
7692 });
7693 cx.set_state(
7694 &r#"
7695 fn main() {
7696 sampleˇ
7697 }
7698 "#
7699 .unindent(),
7700 );
7701 cx.update_editor(|view, cx| {
7702 view.handle_input("(", cx);
7703 });
7704 cx.assert_editor_state(
7705 &"
7706 fn main() {
7707 sample(ˇ)
7708 }
7709 "
7710 .unindent(),
7711 );
7712 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7713 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7714 .await;
7715 cx.update_editor(|editor, _| {
7716 let signature_help_state = editor.signature_help_state.popover().cloned();
7717 assert!(signature_help_state.is_some());
7718 let ParsedMarkdown {
7719 text, highlights, ..
7720 } = signature_help_state.unwrap().parsed_content;
7721 assert_eq!(text, "param1: u8, param2: u8");
7722 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7723 editor.signature_help_state = SignatureHelpState::default();
7724 });
7725
7726 // Ensure that signature_help is called when auto signature help override is enabled
7727 cx.update(|cx| {
7728 cx.update_global::<SettingsStore, _>(|settings, cx| {
7729 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7730 settings.auto_signature_help = Some(true);
7731 settings.show_signature_help_after_edits = Some(false);
7732 });
7733 });
7734 });
7735 cx.set_state(
7736 &r#"
7737 fn main() {
7738 sampleˇ
7739 }
7740 "#
7741 .unindent(),
7742 );
7743 cx.update_editor(|view, cx| {
7744 view.handle_input("(", cx);
7745 });
7746 cx.assert_editor_state(
7747 &"
7748 fn main() {
7749 sample(ˇ)
7750 }
7751 "
7752 .unindent(),
7753 );
7754 handle_signature_help_request(&mut cx, mocked_response).await;
7755 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7756 .await;
7757 cx.editor(|editor, _| {
7758 let signature_help_state = editor.signature_help_state.popover().cloned();
7759 assert!(signature_help_state.is_some());
7760 let ParsedMarkdown {
7761 text, highlights, ..
7762 } = signature_help_state.unwrap().parsed_content;
7763 assert_eq!(text, "param1: u8, param2: u8");
7764 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7765 });
7766}
7767
7768#[gpui::test]
7769async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7770 init_test(cx, |_| {});
7771 cx.update(|cx| {
7772 cx.update_global::<SettingsStore, _>(|settings, cx| {
7773 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7774 settings.auto_signature_help = Some(true);
7775 });
7776 });
7777 });
7778
7779 let mut cx = EditorLspTestContext::new_rust(
7780 lsp::ServerCapabilities {
7781 signature_help_provider: Some(lsp::SignatureHelpOptions {
7782 ..Default::default()
7783 }),
7784 ..Default::default()
7785 },
7786 cx,
7787 )
7788 .await;
7789
7790 // A test that directly calls `show_signature_help`
7791 cx.update_editor(|editor, cx| {
7792 editor.show_signature_help(&ShowSignatureHelp, cx);
7793 });
7794
7795 let mocked_response = lsp::SignatureHelp {
7796 signatures: vec![lsp::SignatureInformation {
7797 label: "fn sample(param1: u8, param2: u8)".to_string(),
7798 documentation: None,
7799 parameters: Some(vec![
7800 lsp::ParameterInformation {
7801 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7802 documentation: None,
7803 },
7804 lsp::ParameterInformation {
7805 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7806 documentation: None,
7807 },
7808 ]),
7809 active_parameter: None,
7810 }],
7811 active_signature: Some(0),
7812 active_parameter: Some(0),
7813 };
7814 handle_signature_help_request(&mut cx, mocked_response).await;
7815
7816 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7817 .await;
7818
7819 cx.editor(|editor, _| {
7820 let signature_help_state = editor.signature_help_state.popover().cloned();
7821 assert!(signature_help_state.is_some());
7822 let ParsedMarkdown {
7823 text, highlights, ..
7824 } = signature_help_state.unwrap().parsed_content;
7825 assert_eq!(text, "param1: u8, param2: u8");
7826 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7827 });
7828
7829 // When exiting outside from inside the brackets, `signature_help` is closed.
7830 cx.set_state(indoc! {"
7831 fn main() {
7832 sample(ˇ);
7833 }
7834
7835 fn sample(param1: u8, param2: u8) {}
7836 "});
7837
7838 cx.update_editor(|editor, cx| {
7839 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7840 });
7841
7842 let mocked_response = lsp::SignatureHelp {
7843 signatures: Vec::new(),
7844 active_signature: None,
7845 active_parameter: None,
7846 };
7847 handle_signature_help_request(&mut cx, mocked_response).await;
7848
7849 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7850 .await;
7851
7852 cx.editor(|editor, _| {
7853 assert!(!editor.signature_help_state.is_shown());
7854 });
7855
7856 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7857 cx.set_state(indoc! {"
7858 fn main() {
7859 sample(ˇ);
7860 }
7861
7862 fn sample(param1: u8, param2: u8) {}
7863 "});
7864
7865 let mocked_response = lsp::SignatureHelp {
7866 signatures: vec![lsp::SignatureInformation {
7867 label: "fn sample(param1: u8, param2: u8)".to_string(),
7868 documentation: None,
7869 parameters: Some(vec![
7870 lsp::ParameterInformation {
7871 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7872 documentation: None,
7873 },
7874 lsp::ParameterInformation {
7875 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7876 documentation: None,
7877 },
7878 ]),
7879 active_parameter: None,
7880 }],
7881 active_signature: Some(0),
7882 active_parameter: Some(0),
7883 };
7884 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7885 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7886 .await;
7887 cx.editor(|editor, _| {
7888 assert!(editor.signature_help_state.is_shown());
7889 });
7890
7891 // Restore the popover with more parameter input
7892 cx.set_state(indoc! {"
7893 fn main() {
7894 sample(param1, param2ˇ);
7895 }
7896
7897 fn sample(param1: u8, param2: u8) {}
7898 "});
7899
7900 let mocked_response = lsp::SignatureHelp {
7901 signatures: vec![lsp::SignatureInformation {
7902 label: "fn sample(param1: u8, param2: u8)".to_string(),
7903 documentation: None,
7904 parameters: Some(vec![
7905 lsp::ParameterInformation {
7906 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7907 documentation: None,
7908 },
7909 lsp::ParameterInformation {
7910 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7911 documentation: None,
7912 },
7913 ]),
7914 active_parameter: None,
7915 }],
7916 active_signature: Some(0),
7917 active_parameter: Some(1),
7918 };
7919 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7920 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7921 .await;
7922
7923 // When selecting a range, the popover is gone.
7924 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7925 cx.update_editor(|editor, cx| {
7926 editor.change_selections(None, cx, |s| {
7927 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7928 })
7929 });
7930 cx.assert_editor_state(indoc! {"
7931 fn main() {
7932 sample(param1, «ˇparam2»);
7933 }
7934
7935 fn sample(param1: u8, param2: u8) {}
7936 "});
7937 cx.editor(|editor, _| {
7938 assert!(!editor.signature_help_state.is_shown());
7939 });
7940
7941 // When unselecting again, the popover is back if within the brackets.
7942 cx.update_editor(|editor, cx| {
7943 editor.change_selections(None, cx, |s| {
7944 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7945 })
7946 });
7947 cx.assert_editor_state(indoc! {"
7948 fn main() {
7949 sample(param1, ˇparam2);
7950 }
7951
7952 fn sample(param1: u8, param2: u8) {}
7953 "});
7954 handle_signature_help_request(&mut cx, mocked_response).await;
7955 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7956 .await;
7957 cx.editor(|editor, _| {
7958 assert!(editor.signature_help_state.is_shown());
7959 });
7960
7961 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7962 cx.update_editor(|editor, cx| {
7963 editor.change_selections(None, cx, |s| {
7964 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7965 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7966 })
7967 });
7968 cx.assert_editor_state(indoc! {"
7969 fn main() {
7970 sample(param1, ˇparam2);
7971 }
7972
7973 fn sample(param1: u8, param2: u8) {}
7974 "});
7975
7976 let mocked_response = lsp::SignatureHelp {
7977 signatures: vec![lsp::SignatureInformation {
7978 label: "fn sample(param1: u8, param2: u8)".to_string(),
7979 documentation: None,
7980 parameters: Some(vec![
7981 lsp::ParameterInformation {
7982 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7983 documentation: None,
7984 },
7985 lsp::ParameterInformation {
7986 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7987 documentation: None,
7988 },
7989 ]),
7990 active_parameter: None,
7991 }],
7992 active_signature: Some(0),
7993 active_parameter: Some(1),
7994 };
7995 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7996 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7997 .await;
7998 cx.update_editor(|editor, cx| {
7999 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8000 });
8001 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8002 .await;
8003 cx.update_editor(|editor, cx| {
8004 editor.change_selections(None, cx, |s| {
8005 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8006 })
8007 });
8008 cx.assert_editor_state(indoc! {"
8009 fn main() {
8010 sample(param1, «ˇparam2»);
8011 }
8012
8013 fn sample(param1: u8, param2: u8) {}
8014 "});
8015 cx.update_editor(|editor, cx| {
8016 editor.change_selections(None, cx, |s| {
8017 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8018 })
8019 });
8020 cx.assert_editor_state(indoc! {"
8021 fn main() {
8022 sample(param1, ˇparam2);
8023 }
8024
8025 fn sample(param1: u8, param2: u8) {}
8026 "});
8027 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8028 .await;
8029}
8030
8031#[gpui::test]
8032async fn test_completion(cx: &mut gpui::TestAppContext) {
8033 init_test(cx, |_| {});
8034
8035 let mut cx = EditorLspTestContext::new_rust(
8036 lsp::ServerCapabilities {
8037 completion_provider: Some(lsp::CompletionOptions {
8038 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8039 resolve_provider: Some(true),
8040 ..Default::default()
8041 }),
8042 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8043 ..Default::default()
8044 },
8045 cx,
8046 )
8047 .await;
8048 let counter = Arc::new(AtomicUsize::new(0));
8049
8050 cx.set_state(indoc! {"
8051 oneˇ
8052 two
8053 three
8054 "});
8055 cx.simulate_keystroke(".");
8056 handle_completion_request(
8057 &mut cx,
8058 indoc! {"
8059 one.|<>
8060 two
8061 three
8062 "},
8063 vec!["first_completion", "second_completion"],
8064 counter.clone(),
8065 )
8066 .await;
8067 cx.condition(|editor, _| editor.context_menu_visible())
8068 .await;
8069 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8070
8071 let _handler = handle_signature_help_request(
8072 &mut cx,
8073 lsp::SignatureHelp {
8074 signatures: vec![lsp::SignatureInformation {
8075 label: "test signature".to_string(),
8076 documentation: None,
8077 parameters: Some(vec![lsp::ParameterInformation {
8078 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8079 documentation: None,
8080 }]),
8081 active_parameter: None,
8082 }],
8083 active_signature: None,
8084 active_parameter: None,
8085 },
8086 );
8087 cx.update_editor(|editor, cx| {
8088 assert!(
8089 !editor.signature_help_state.is_shown(),
8090 "No signature help was called for"
8091 );
8092 editor.show_signature_help(&ShowSignatureHelp, cx);
8093 });
8094 cx.run_until_parked();
8095 cx.update_editor(|editor, _| {
8096 assert!(
8097 !editor.signature_help_state.is_shown(),
8098 "No signature help should be shown when completions menu is open"
8099 );
8100 });
8101
8102 let apply_additional_edits = cx.update_editor(|editor, cx| {
8103 editor.context_menu_next(&Default::default(), cx);
8104 editor
8105 .confirm_completion(&ConfirmCompletion::default(), cx)
8106 .unwrap()
8107 });
8108 cx.assert_editor_state(indoc! {"
8109 one.second_completionˇ
8110 two
8111 three
8112 "});
8113
8114 handle_resolve_completion_request(
8115 &mut cx,
8116 Some(vec![
8117 (
8118 //This overlaps with the primary completion edit which is
8119 //misbehavior from the LSP spec, test that we filter it out
8120 indoc! {"
8121 one.second_ˇcompletion
8122 two
8123 threeˇ
8124 "},
8125 "overlapping additional edit",
8126 ),
8127 (
8128 indoc! {"
8129 one.second_completion
8130 two
8131 threeˇ
8132 "},
8133 "\nadditional edit",
8134 ),
8135 ]),
8136 )
8137 .await;
8138 apply_additional_edits.await.unwrap();
8139 cx.assert_editor_state(indoc! {"
8140 one.second_completionˇ
8141 two
8142 three
8143 additional edit
8144 "});
8145
8146 cx.set_state(indoc! {"
8147 one.second_completion
8148 twoˇ
8149 threeˇ
8150 additional edit
8151 "});
8152 cx.simulate_keystroke(" ");
8153 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8154 cx.simulate_keystroke("s");
8155 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8156
8157 cx.assert_editor_state(indoc! {"
8158 one.second_completion
8159 two sˇ
8160 three sˇ
8161 additional edit
8162 "});
8163 handle_completion_request(
8164 &mut cx,
8165 indoc! {"
8166 one.second_completion
8167 two s
8168 three <s|>
8169 additional edit
8170 "},
8171 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8172 counter.clone(),
8173 )
8174 .await;
8175 cx.condition(|editor, _| editor.context_menu_visible())
8176 .await;
8177 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8178
8179 cx.simulate_keystroke("i");
8180
8181 handle_completion_request(
8182 &mut cx,
8183 indoc! {"
8184 one.second_completion
8185 two si
8186 three <si|>
8187 additional edit
8188 "},
8189 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8190 counter.clone(),
8191 )
8192 .await;
8193 cx.condition(|editor, _| editor.context_menu_visible())
8194 .await;
8195 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8196
8197 let apply_additional_edits = cx.update_editor(|editor, cx| {
8198 editor
8199 .confirm_completion(&ConfirmCompletion::default(), cx)
8200 .unwrap()
8201 });
8202 cx.assert_editor_state(indoc! {"
8203 one.second_completion
8204 two sixth_completionˇ
8205 three sixth_completionˇ
8206 additional edit
8207 "});
8208
8209 handle_resolve_completion_request(&mut cx, None).await;
8210 apply_additional_edits.await.unwrap();
8211
8212 cx.update(|cx| {
8213 cx.update_global::<SettingsStore, _>(|settings, cx| {
8214 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8215 settings.show_completions_on_input = Some(false);
8216 });
8217 })
8218 });
8219 cx.set_state("editorˇ");
8220 cx.simulate_keystroke(".");
8221 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8222 cx.simulate_keystroke("c");
8223 cx.simulate_keystroke("l");
8224 cx.simulate_keystroke("o");
8225 cx.assert_editor_state("editor.cloˇ");
8226 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8227 cx.update_editor(|editor, cx| {
8228 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8229 });
8230 handle_completion_request(
8231 &mut cx,
8232 "editor.<clo|>",
8233 vec!["close", "clobber"],
8234 counter.clone(),
8235 )
8236 .await;
8237 cx.condition(|editor, _| editor.context_menu_visible())
8238 .await;
8239 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8240
8241 let apply_additional_edits = cx.update_editor(|editor, cx| {
8242 editor
8243 .confirm_completion(&ConfirmCompletion::default(), cx)
8244 .unwrap()
8245 });
8246 cx.assert_editor_state("editor.closeˇ");
8247 handle_resolve_completion_request(&mut cx, None).await;
8248 apply_additional_edits.await.unwrap();
8249}
8250
8251#[gpui::test]
8252async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8253 init_test(cx, |_| {});
8254 let mut cx = EditorLspTestContext::new_rust(
8255 lsp::ServerCapabilities {
8256 completion_provider: Some(lsp::CompletionOptions {
8257 trigger_characters: Some(vec![".".to_string()]),
8258 ..Default::default()
8259 }),
8260 ..Default::default()
8261 },
8262 cx,
8263 )
8264 .await;
8265 cx.lsp
8266 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8267 Ok(Some(lsp::CompletionResponse::Array(vec![
8268 lsp::CompletionItem {
8269 label: "first".into(),
8270 ..Default::default()
8271 },
8272 lsp::CompletionItem {
8273 label: "last".into(),
8274 ..Default::default()
8275 },
8276 ])))
8277 });
8278 cx.set_state("variableˇ");
8279 cx.simulate_keystroke(".");
8280 cx.executor().run_until_parked();
8281
8282 cx.update_editor(|editor, _| {
8283 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8284 assert_eq!(
8285 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8286 &["first", "last"]
8287 );
8288 } else {
8289 panic!("expected completion menu to be open");
8290 }
8291 });
8292
8293 cx.update_editor(|editor, cx| {
8294 editor.move_page_down(&MovePageDown::default(), cx);
8295 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8296 assert!(
8297 menu.selected_item == 1,
8298 "expected PageDown to select the last item from the context menu"
8299 );
8300 } else {
8301 panic!("expected completion menu to stay open after PageDown");
8302 }
8303 });
8304
8305 cx.update_editor(|editor, cx| {
8306 editor.move_page_up(&MovePageUp::default(), cx);
8307 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8308 assert!(
8309 menu.selected_item == 0,
8310 "expected PageUp to select the first item from the context menu"
8311 );
8312 } else {
8313 panic!("expected completion menu to stay open after PageUp");
8314 }
8315 });
8316}
8317
8318#[gpui::test]
8319async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8320 init_test(cx, |_| {});
8321
8322 let mut cx = EditorLspTestContext::new_rust(
8323 lsp::ServerCapabilities {
8324 completion_provider: Some(lsp::CompletionOptions {
8325 trigger_characters: Some(vec![".".to_string()]),
8326 resolve_provider: Some(true),
8327 ..Default::default()
8328 }),
8329 ..Default::default()
8330 },
8331 cx,
8332 )
8333 .await;
8334
8335 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8336 cx.simulate_keystroke(".");
8337 let completion_item = lsp::CompletionItem {
8338 label: "Some".into(),
8339 kind: Some(lsp::CompletionItemKind::SNIPPET),
8340 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8341 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8342 kind: lsp::MarkupKind::Markdown,
8343 value: "```rust\nSome(2)\n```".to_string(),
8344 })),
8345 deprecated: Some(false),
8346 sort_text: Some("Some".to_string()),
8347 filter_text: Some("Some".to_string()),
8348 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8349 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8350 range: lsp::Range {
8351 start: lsp::Position {
8352 line: 0,
8353 character: 22,
8354 },
8355 end: lsp::Position {
8356 line: 0,
8357 character: 22,
8358 },
8359 },
8360 new_text: "Some(2)".to_string(),
8361 })),
8362 additional_text_edits: Some(vec![lsp::TextEdit {
8363 range: lsp::Range {
8364 start: lsp::Position {
8365 line: 0,
8366 character: 20,
8367 },
8368 end: lsp::Position {
8369 line: 0,
8370 character: 22,
8371 },
8372 },
8373 new_text: "".to_string(),
8374 }]),
8375 ..Default::default()
8376 };
8377
8378 let closure_completion_item = completion_item.clone();
8379 let counter = Arc::new(AtomicUsize::new(0));
8380 let counter_clone = counter.clone();
8381 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8382 let task_completion_item = closure_completion_item.clone();
8383 counter_clone.fetch_add(1, atomic::Ordering::Release);
8384 async move {
8385 Ok(Some(lsp::CompletionResponse::Array(vec![
8386 task_completion_item,
8387 ])))
8388 }
8389 });
8390
8391 cx.condition(|editor, _| editor.context_menu_visible())
8392 .await;
8393 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8394 assert!(request.next().await.is_some());
8395 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8396
8397 cx.simulate_keystroke("S");
8398 cx.simulate_keystroke("o");
8399 cx.simulate_keystroke("m");
8400 cx.condition(|editor, _| editor.context_menu_visible())
8401 .await;
8402 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8403 assert!(request.next().await.is_some());
8404 assert!(request.next().await.is_some());
8405 assert!(request.next().await.is_some());
8406 request.close();
8407 assert!(request.next().await.is_none());
8408 assert_eq!(
8409 counter.load(atomic::Ordering::Acquire),
8410 4,
8411 "With the completions menu open, only one LSP request should happen per input"
8412 );
8413}
8414
8415#[gpui::test]
8416async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8417 init_test(cx, |_| {});
8418 let mut cx = EditorTestContext::new(cx).await;
8419 let language = Arc::new(Language::new(
8420 LanguageConfig {
8421 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8422 ..Default::default()
8423 },
8424 Some(tree_sitter_rust::LANGUAGE.into()),
8425 ));
8426 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8427
8428 // If multiple selections intersect a line, the line is only toggled once.
8429 cx.set_state(indoc! {"
8430 fn a() {
8431 «//b();
8432 ˇ»// «c();
8433 //ˇ» d();
8434 }
8435 "});
8436
8437 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8438
8439 cx.assert_editor_state(indoc! {"
8440 fn a() {
8441 «b();
8442 c();
8443 ˇ» d();
8444 }
8445 "});
8446
8447 // The comment prefix is inserted at the same column for every line in a
8448 // selection.
8449 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8450
8451 cx.assert_editor_state(indoc! {"
8452 fn a() {
8453 // «b();
8454 // c();
8455 ˇ»// d();
8456 }
8457 "});
8458
8459 // If a selection ends at the beginning of a line, that line is not toggled.
8460 cx.set_selections_state(indoc! {"
8461 fn a() {
8462 // b();
8463 «// c();
8464 ˇ» // d();
8465 }
8466 "});
8467
8468 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8469
8470 cx.assert_editor_state(indoc! {"
8471 fn a() {
8472 // b();
8473 «c();
8474 ˇ» // d();
8475 }
8476 "});
8477
8478 // If a selection span a single line and is empty, the line is toggled.
8479 cx.set_state(indoc! {"
8480 fn a() {
8481 a();
8482 b();
8483 ˇ
8484 }
8485 "});
8486
8487 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8488
8489 cx.assert_editor_state(indoc! {"
8490 fn a() {
8491 a();
8492 b();
8493 //•ˇ
8494 }
8495 "});
8496
8497 // If a selection span multiple lines, empty lines are not toggled.
8498 cx.set_state(indoc! {"
8499 fn a() {
8500 «a();
8501
8502 c();ˇ»
8503 }
8504 "});
8505
8506 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8507
8508 cx.assert_editor_state(indoc! {"
8509 fn a() {
8510 // «a();
8511
8512 // c();ˇ»
8513 }
8514 "});
8515
8516 // If a selection includes multiple comment prefixes, all lines are uncommented.
8517 cx.set_state(indoc! {"
8518 fn a() {
8519 «// a();
8520 /// b();
8521 //! c();ˇ»
8522 }
8523 "});
8524
8525 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8526
8527 cx.assert_editor_state(indoc! {"
8528 fn a() {
8529 «a();
8530 b();
8531 c();ˇ»
8532 }
8533 "});
8534}
8535
8536#[gpui::test]
8537async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8538 init_test(cx, |_| {});
8539 let mut cx = EditorTestContext::new(cx).await;
8540 let language = Arc::new(Language::new(
8541 LanguageConfig {
8542 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8543 ..Default::default()
8544 },
8545 Some(tree_sitter_rust::LANGUAGE.into()),
8546 ));
8547 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8548
8549 let toggle_comments = &ToggleComments {
8550 advance_downwards: false,
8551 ignore_indent: true,
8552 };
8553
8554 // If multiple selections intersect a line, the line is only toggled once.
8555 cx.set_state(indoc! {"
8556 fn a() {
8557 // «b();
8558 // c();
8559 // ˇ» d();
8560 }
8561 "});
8562
8563 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8564
8565 cx.assert_editor_state(indoc! {"
8566 fn a() {
8567 «b();
8568 c();
8569 ˇ» d();
8570 }
8571 "});
8572
8573 // The comment prefix is inserted at the beginning of each line
8574 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8575
8576 cx.assert_editor_state(indoc! {"
8577 fn a() {
8578 // «b();
8579 // c();
8580 // ˇ» d();
8581 }
8582 "});
8583
8584 // If a selection ends at the beginning of a line, that line is not toggled.
8585 cx.set_selections_state(indoc! {"
8586 fn a() {
8587 // b();
8588 // «c();
8589 ˇ»// d();
8590 }
8591 "});
8592
8593 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8594
8595 cx.assert_editor_state(indoc! {"
8596 fn a() {
8597 // b();
8598 «c();
8599 ˇ»// d();
8600 }
8601 "});
8602
8603 // If a selection span a single line and is empty, the line is toggled.
8604 cx.set_state(indoc! {"
8605 fn a() {
8606 a();
8607 b();
8608 ˇ
8609 }
8610 "});
8611
8612 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8613
8614 cx.assert_editor_state(indoc! {"
8615 fn a() {
8616 a();
8617 b();
8618 //ˇ
8619 }
8620 "});
8621
8622 // If a selection span multiple lines, empty lines are not toggled.
8623 cx.set_state(indoc! {"
8624 fn a() {
8625 «a();
8626
8627 c();ˇ»
8628 }
8629 "});
8630
8631 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8632
8633 cx.assert_editor_state(indoc! {"
8634 fn a() {
8635 // «a();
8636
8637 // c();ˇ»
8638 }
8639 "});
8640
8641 // If a selection includes multiple comment prefixes, all lines are uncommented.
8642 cx.set_state(indoc! {"
8643 fn a() {
8644 // «a();
8645 /// b();
8646 //! c();ˇ»
8647 }
8648 "});
8649
8650 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8651
8652 cx.assert_editor_state(indoc! {"
8653 fn a() {
8654 «a();
8655 b();
8656 c();ˇ»
8657 }
8658 "});
8659}
8660
8661#[gpui::test]
8662async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8663 init_test(cx, |_| {});
8664
8665 let language = Arc::new(Language::new(
8666 LanguageConfig {
8667 line_comments: vec!["// ".into()],
8668 ..Default::default()
8669 },
8670 Some(tree_sitter_rust::LANGUAGE.into()),
8671 ));
8672
8673 let mut cx = EditorTestContext::new(cx).await;
8674
8675 cx.language_registry().add(language.clone());
8676 cx.update_buffer(|buffer, cx| {
8677 buffer.set_language(Some(language), cx);
8678 });
8679
8680 let toggle_comments = &ToggleComments {
8681 advance_downwards: true,
8682 ignore_indent: false,
8683 };
8684
8685 // Single cursor on one line -> advance
8686 // Cursor moves horizontally 3 characters as well on non-blank line
8687 cx.set_state(indoc!(
8688 "fn a() {
8689 ˇdog();
8690 cat();
8691 }"
8692 ));
8693 cx.update_editor(|editor, cx| {
8694 editor.toggle_comments(toggle_comments, cx);
8695 });
8696 cx.assert_editor_state(indoc!(
8697 "fn a() {
8698 // dog();
8699 catˇ();
8700 }"
8701 ));
8702
8703 // Single selection on one line -> don't advance
8704 cx.set_state(indoc!(
8705 "fn a() {
8706 «dog()ˇ»;
8707 cat();
8708 }"
8709 ));
8710 cx.update_editor(|editor, cx| {
8711 editor.toggle_comments(toggle_comments, cx);
8712 });
8713 cx.assert_editor_state(indoc!(
8714 "fn a() {
8715 // «dog()ˇ»;
8716 cat();
8717 }"
8718 ));
8719
8720 // Multiple cursors on one line -> advance
8721 cx.set_state(indoc!(
8722 "fn a() {
8723 ˇdˇog();
8724 cat();
8725 }"
8726 ));
8727 cx.update_editor(|editor, cx| {
8728 editor.toggle_comments(toggle_comments, cx);
8729 });
8730 cx.assert_editor_state(indoc!(
8731 "fn a() {
8732 // dog();
8733 catˇ(ˇ);
8734 }"
8735 ));
8736
8737 // Multiple cursors on one line, with selection -> don't advance
8738 cx.set_state(indoc!(
8739 "fn a() {
8740 ˇdˇog«()ˇ»;
8741 cat();
8742 }"
8743 ));
8744 cx.update_editor(|editor, cx| {
8745 editor.toggle_comments(toggle_comments, cx);
8746 });
8747 cx.assert_editor_state(indoc!(
8748 "fn a() {
8749 // ˇdˇog«()ˇ»;
8750 cat();
8751 }"
8752 ));
8753
8754 // Single cursor on one line -> advance
8755 // Cursor moves to column 0 on blank line
8756 cx.set_state(indoc!(
8757 "fn a() {
8758 ˇdog();
8759
8760 cat();
8761 }"
8762 ));
8763 cx.update_editor(|editor, cx| {
8764 editor.toggle_comments(toggle_comments, cx);
8765 });
8766 cx.assert_editor_state(indoc!(
8767 "fn a() {
8768 // dog();
8769 ˇ
8770 cat();
8771 }"
8772 ));
8773
8774 // Single cursor on one line -> advance
8775 // Cursor starts and ends at column 0
8776 cx.set_state(indoc!(
8777 "fn a() {
8778 ˇ dog();
8779 cat();
8780 }"
8781 ));
8782 cx.update_editor(|editor, cx| {
8783 editor.toggle_comments(toggle_comments, cx);
8784 });
8785 cx.assert_editor_state(indoc!(
8786 "fn a() {
8787 // dog();
8788 ˇ cat();
8789 }"
8790 ));
8791}
8792
8793#[gpui::test]
8794async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8795 init_test(cx, |_| {});
8796
8797 let mut cx = EditorTestContext::new(cx).await;
8798
8799 let html_language = Arc::new(
8800 Language::new(
8801 LanguageConfig {
8802 name: "HTML".into(),
8803 block_comment: Some(("<!-- ".into(), " -->".into())),
8804 ..Default::default()
8805 },
8806 Some(tree_sitter_html::language()),
8807 )
8808 .with_injection_query(
8809 r#"
8810 (script_element
8811 (raw_text) @content
8812 (#set! "language" "javascript"))
8813 "#,
8814 )
8815 .unwrap(),
8816 );
8817
8818 let javascript_language = Arc::new(Language::new(
8819 LanguageConfig {
8820 name: "JavaScript".into(),
8821 line_comments: vec!["// ".into()],
8822 ..Default::default()
8823 },
8824 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8825 ));
8826
8827 cx.language_registry().add(html_language.clone());
8828 cx.language_registry().add(javascript_language.clone());
8829 cx.update_buffer(|buffer, cx| {
8830 buffer.set_language(Some(html_language), cx);
8831 });
8832
8833 // Toggle comments for empty selections
8834 cx.set_state(
8835 &r#"
8836 <p>A</p>ˇ
8837 <p>B</p>ˇ
8838 <p>C</p>ˇ
8839 "#
8840 .unindent(),
8841 );
8842 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8843 cx.assert_editor_state(
8844 &r#"
8845 <!-- <p>A</p>ˇ -->
8846 <!-- <p>B</p>ˇ -->
8847 <!-- <p>C</p>ˇ -->
8848 "#
8849 .unindent(),
8850 );
8851 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8852 cx.assert_editor_state(
8853 &r#"
8854 <p>A</p>ˇ
8855 <p>B</p>ˇ
8856 <p>C</p>ˇ
8857 "#
8858 .unindent(),
8859 );
8860
8861 // Toggle comments for mixture of empty and non-empty selections, where
8862 // multiple selections occupy a given line.
8863 cx.set_state(
8864 &r#"
8865 <p>A«</p>
8866 <p>ˇ»B</p>ˇ
8867 <p>C«</p>
8868 <p>ˇ»D</p>ˇ
8869 "#
8870 .unindent(),
8871 );
8872
8873 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8874 cx.assert_editor_state(
8875 &r#"
8876 <!-- <p>A«</p>
8877 <p>ˇ»B</p>ˇ -->
8878 <!-- <p>C«</p>
8879 <p>ˇ»D</p>ˇ -->
8880 "#
8881 .unindent(),
8882 );
8883 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8884 cx.assert_editor_state(
8885 &r#"
8886 <p>A«</p>
8887 <p>ˇ»B</p>ˇ
8888 <p>C«</p>
8889 <p>ˇ»D</p>ˇ
8890 "#
8891 .unindent(),
8892 );
8893
8894 // Toggle comments when different languages are active for different
8895 // selections.
8896 cx.set_state(
8897 &r#"
8898 ˇ<script>
8899 ˇvar x = new Y();
8900 ˇ</script>
8901 "#
8902 .unindent(),
8903 );
8904 cx.executor().run_until_parked();
8905 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8906 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8907 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8908 cx.assert_editor_state(
8909 &r#"
8910 <!-- ˇ<script> -->
8911 // ˇvar x = new Y();
8912 // ˇ</script>
8913 "#
8914 .unindent(),
8915 );
8916}
8917
8918#[gpui::test]
8919fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8920 init_test(cx, |_| {});
8921
8922 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8923 let multibuffer = cx.new_model(|cx| {
8924 let mut multibuffer = MultiBuffer::new(ReadWrite);
8925 multibuffer.push_excerpts(
8926 buffer.clone(),
8927 [
8928 ExcerptRange {
8929 context: Point::new(0, 0)..Point::new(0, 4),
8930 primary: None,
8931 },
8932 ExcerptRange {
8933 context: Point::new(1, 0)..Point::new(1, 4),
8934 primary: None,
8935 },
8936 ],
8937 cx,
8938 );
8939 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8940 multibuffer
8941 });
8942
8943 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8944 view.update(cx, |view, cx| {
8945 assert_eq!(view.text(cx), "aaaa\nbbbb");
8946 view.change_selections(None, cx, |s| {
8947 s.select_ranges([
8948 Point::new(0, 0)..Point::new(0, 0),
8949 Point::new(1, 0)..Point::new(1, 0),
8950 ])
8951 });
8952
8953 view.handle_input("X", cx);
8954 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8955 assert_eq!(
8956 view.selections.ranges(cx),
8957 [
8958 Point::new(0, 1)..Point::new(0, 1),
8959 Point::new(1, 1)..Point::new(1, 1),
8960 ]
8961 );
8962
8963 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8964 view.change_selections(None, cx, |s| {
8965 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8966 });
8967 view.backspace(&Default::default(), cx);
8968 assert_eq!(view.text(cx), "Xa\nbbb");
8969 assert_eq!(
8970 view.selections.ranges(cx),
8971 [Point::new(1, 0)..Point::new(1, 0)]
8972 );
8973
8974 view.change_selections(None, cx, |s| {
8975 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8976 });
8977 view.backspace(&Default::default(), cx);
8978 assert_eq!(view.text(cx), "X\nbb");
8979 assert_eq!(
8980 view.selections.ranges(cx),
8981 [Point::new(0, 1)..Point::new(0, 1)]
8982 );
8983 });
8984}
8985
8986#[gpui::test]
8987fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8988 init_test(cx, |_| {});
8989
8990 let markers = vec![('[', ']').into(), ('(', ')').into()];
8991 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8992 indoc! {"
8993 [aaaa
8994 (bbbb]
8995 cccc)",
8996 },
8997 markers.clone(),
8998 );
8999 let excerpt_ranges = markers.into_iter().map(|marker| {
9000 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9001 ExcerptRange {
9002 context,
9003 primary: None,
9004 }
9005 });
9006 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9007 let multibuffer = cx.new_model(|cx| {
9008 let mut multibuffer = MultiBuffer::new(ReadWrite);
9009 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9010 multibuffer
9011 });
9012
9013 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9014 view.update(cx, |view, cx| {
9015 let (expected_text, selection_ranges) = marked_text_ranges(
9016 indoc! {"
9017 aaaa
9018 bˇbbb
9019 bˇbbˇb
9020 cccc"
9021 },
9022 true,
9023 );
9024 assert_eq!(view.text(cx), expected_text);
9025 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9026
9027 view.handle_input("X", cx);
9028
9029 let (expected_text, expected_selections) = marked_text_ranges(
9030 indoc! {"
9031 aaaa
9032 bXˇbbXb
9033 bXˇbbXˇb
9034 cccc"
9035 },
9036 false,
9037 );
9038 assert_eq!(view.text(cx), expected_text);
9039 assert_eq!(view.selections.ranges(cx), expected_selections);
9040
9041 view.newline(&Newline, cx);
9042 let (expected_text, expected_selections) = marked_text_ranges(
9043 indoc! {"
9044 aaaa
9045 bX
9046 ˇbbX
9047 b
9048 bX
9049 ˇbbX
9050 ˇb
9051 cccc"
9052 },
9053 false,
9054 );
9055 assert_eq!(view.text(cx), expected_text);
9056 assert_eq!(view.selections.ranges(cx), expected_selections);
9057 });
9058}
9059
9060#[gpui::test]
9061fn test_refresh_selections(cx: &mut TestAppContext) {
9062 init_test(cx, |_| {});
9063
9064 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9065 let mut excerpt1_id = None;
9066 let multibuffer = cx.new_model(|cx| {
9067 let mut multibuffer = MultiBuffer::new(ReadWrite);
9068 excerpt1_id = multibuffer
9069 .push_excerpts(
9070 buffer.clone(),
9071 [
9072 ExcerptRange {
9073 context: Point::new(0, 0)..Point::new(1, 4),
9074 primary: None,
9075 },
9076 ExcerptRange {
9077 context: Point::new(1, 0)..Point::new(2, 4),
9078 primary: None,
9079 },
9080 ],
9081 cx,
9082 )
9083 .into_iter()
9084 .next();
9085 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9086 multibuffer
9087 });
9088
9089 let editor = cx.add_window(|cx| {
9090 let mut editor = build_editor(multibuffer.clone(), cx);
9091 let snapshot = editor.snapshot(cx);
9092 editor.change_selections(None, cx, |s| {
9093 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9094 });
9095 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9096 assert_eq!(
9097 editor.selections.ranges(cx),
9098 [
9099 Point::new(1, 3)..Point::new(1, 3),
9100 Point::new(2, 1)..Point::new(2, 1),
9101 ]
9102 );
9103 editor
9104 });
9105
9106 // Refreshing selections is a no-op when excerpts haven't changed.
9107 _ = editor.update(cx, |editor, cx| {
9108 editor.change_selections(None, cx, |s| s.refresh());
9109 assert_eq!(
9110 editor.selections.ranges(cx),
9111 [
9112 Point::new(1, 3)..Point::new(1, 3),
9113 Point::new(2, 1)..Point::new(2, 1),
9114 ]
9115 );
9116 });
9117
9118 multibuffer.update(cx, |multibuffer, cx| {
9119 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9120 });
9121 _ = editor.update(cx, |editor, cx| {
9122 // Removing an excerpt causes the first selection to become degenerate.
9123 assert_eq!(
9124 editor.selections.ranges(cx),
9125 [
9126 Point::new(0, 0)..Point::new(0, 0),
9127 Point::new(0, 1)..Point::new(0, 1)
9128 ]
9129 );
9130
9131 // Refreshing selections will relocate the first selection to the original buffer
9132 // location.
9133 editor.change_selections(None, cx, |s| s.refresh());
9134 assert_eq!(
9135 editor.selections.ranges(cx),
9136 [
9137 Point::new(0, 1)..Point::new(0, 1),
9138 Point::new(0, 3)..Point::new(0, 3)
9139 ]
9140 );
9141 assert!(editor.selections.pending_anchor().is_some());
9142 });
9143}
9144
9145#[gpui::test]
9146fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9147 init_test(cx, |_| {});
9148
9149 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9150 let mut excerpt1_id = None;
9151 let multibuffer = cx.new_model(|cx| {
9152 let mut multibuffer = MultiBuffer::new(ReadWrite);
9153 excerpt1_id = multibuffer
9154 .push_excerpts(
9155 buffer.clone(),
9156 [
9157 ExcerptRange {
9158 context: Point::new(0, 0)..Point::new(1, 4),
9159 primary: None,
9160 },
9161 ExcerptRange {
9162 context: Point::new(1, 0)..Point::new(2, 4),
9163 primary: None,
9164 },
9165 ],
9166 cx,
9167 )
9168 .into_iter()
9169 .next();
9170 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9171 multibuffer
9172 });
9173
9174 let editor = cx.add_window(|cx| {
9175 let mut editor = build_editor(multibuffer.clone(), cx);
9176 let snapshot = editor.snapshot(cx);
9177 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9178 assert_eq!(
9179 editor.selections.ranges(cx),
9180 [Point::new(1, 3)..Point::new(1, 3)]
9181 );
9182 editor
9183 });
9184
9185 multibuffer.update(cx, |multibuffer, cx| {
9186 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9187 });
9188 _ = editor.update(cx, |editor, cx| {
9189 assert_eq!(
9190 editor.selections.ranges(cx),
9191 [Point::new(0, 0)..Point::new(0, 0)]
9192 );
9193
9194 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9195 editor.change_selections(None, cx, |s| s.refresh());
9196 assert_eq!(
9197 editor.selections.ranges(cx),
9198 [Point::new(0, 3)..Point::new(0, 3)]
9199 );
9200 assert!(editor.selections.pending_anchor().is_some());
9201 });
9202}
9203
9204#[gpui::test]
9205async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9206 init_test(cx, |_| {});
9207
9208 let language = Arc::new(
9209 Language::new(
9210 LanguageConfig {
9211 brackets: BracketPairConfig {
9212 pairs: vec![
9213 BracketPair {
9214 start: "{".to_string(),
9215 end: "}".to_string(),
9216 close: true,
9217 surround: true,
9218 newline: true,
9219 },
9220 BracketPair {
9221 start: "/* ".to_string(),
9222 end: " */".to_string(),
9223 close: true,
9224 surround: true,
9225 newline: true,
9226 },
9227 ],
9228 ..Default::default()
9229 },
9230 ..Default::default()
9231 },
9232 Some(tree_sitter_rust::LANGUAGE.into()),
9233 )
9234 .with_indents_query("")
9235 .unwrap(),
9236 );
9237
9238 let text = concat!(
9239 "{ }\n", //
9240 " x\n", //
9241 " /* */\n", //
9242 "x\n", //
9243 "{{} }\n", //
9244 );
9245
9246 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9247 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9248 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9249 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9250 .await;
9251
9252 view.update(cx, |view, cx| {
9253 view.change_selections(None, cx, |s| {
9254 s.select_display_ranges([
9255 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9256 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9257 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9258 ])
9259 });
9260 view.newline(&Newline, cx);
9261
9262 assert_eq!(
9263 view.buffer().read(cx).read(cx).text(),
9264 concat!(
9265 "{ \n", // Suppress rustfmt
9266 "\n", //
9267 "}\n", //
9268 " x\n", //
9269 " /* \n", //
9270 " \n", //
9271 " */\n", //
9272 "x\n", //
9273 "{{} \n", //
9274 "}\n", //
9275 )
9276 );
9277 });
9278}
9279
9280#[gpui::test]
9281fn test_highlighted_ranges(cx: &mut TestAppContext) {
9282 init_test(cx, |_| {});
9283
9284 let editor = cx.add_window(|cx| {
9285 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9286 build_editor(buffer.clone(), cx)
9287 });
9288
9289 _ = editor.update(cx, |editor, cx| {
9290 struct Type1;
9291 struct Type2;
9292
9293 let buffer = editor.buffer.read(cx).snapshot(cx);
9294
9295 let anchor_range =
9296 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9297
9298 editor.highlight_background::<Type1>(
9299 &[
9300 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9301 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9302 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9303 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9304 ],
9305 |_| Hsla::red(),
9306 cx,
9307 );
9308 editor.highlight_background::<Type2>(
9309 &[
9310 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9311 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9312 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9313 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9314 ],
9315 |_| Hsla::green(),
9316 cx,
9317 );
9318
9319 let snapshot = editor.snapshot(cx);
9320 let mut highlighted_ranges = editor.background_highlights_in_range(
9321 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9322 &snapshot,
9323 cx.theme().colors(),
9324 );
9325 // Enforce a consistent ordering based on color without relying on the ordering of the
9326 // highlight's `TypeId` which is non-executor.
9327 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9328 assert_eq!(
9329 highlighted_ranges,
9330 &[
9331 (
9332 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9333 Hsla::red(),
9334 ),
9335 (
9336 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9337 Hsla::red(),
9338 ),
9339 (
9340 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9341 Hsla::green(),
9342 ),
9343 (
9344 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9345 Hsla::green(),
9346 ),
9347 ]
9348 );
9349 assert_eq!(
9350 editor.background_highlights_in_range(
9351 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9352 &snapshot,
9353 cx.theme().colors(),
9354 ),
9355 &[(
9356 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9357 Hsla::red(),
9358 )]
9359 );
9360 });
9361}
9362
9363#[gpui::test]
9364async fn test_following(cx: &mut gpui::TestAppContext) {
9365 init_test(cx, |_| {});
9366
9367 let fs = FakeFs::new(cx.executor());
9368 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9369
9370 let buffer = project.update(cx, |project, cx| {
9371 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9372 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9373 });
9374 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9375 let follower = cx.update(|cx| {
9376 cx.open_window(
9377 WindowOptions {
9378 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9379 gpui::Point::new(px(0.), px(0.)),
9380 gpui::Point::new(px(10.), px(80.)),
9381 ))),
9382 ..Default::default()
9383 },
9384 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9385 )
9386 .unwrap()
9387 });
9388
9389 let is_still_following = Rc::new(RefCell::new(true));
9390 let follower_edit_event_count = Rc::new(RefCell::new(0));
9391 let pending_update = Rc::new(RefCell::new(None));
9392 _ = follower.update(cx, {
9393 let update = pending_update.clone();
9394 let is_still_following = is_still_following.clone();
9395 let follower_edit_event_count = follower_edit_event_count.clone();
9396 |_, cx| {
9397 cx.subscribe(
9398 &leader.root_view(cx).unwrap(),
9399 move |_, leader, event, cx| {
9400 leader
9401 .read(cx)
9402 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9403 },
9404 )
9405 .detach();
9406
9407 cx.subscribe(
9408 &follower.root_view(cx).unwrap(),
9409 move |_, _, event: &EditorEvent, _cx| {
9410 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9411 *is_still_following.borrow_mut() = false;
9412 }
9413
9414 if let EditorEvent::BufferEdited = event {
9415 *follower_edit_event_count.borrow_mut() += 1;
9416 }
9417 },
9418 )
9419 .detach();
9420 }
9421 });
9422
9423 // Update the selections only
9424 _ = leader.update(cx, |leader, cx| {
9425 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9426 });
9427 follower
9428 .update(cx, |follower, cx| {
9429 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9430 })
9431 .unwrap()
9432 .await
9433 .unwrap();
9434 _ = follower.update(cx, |follower, cx| {
9435 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9436 });
9437 assert!(*is_still_following.borrow());
9438 assert_eq!(*follower_edit_event_count.borrow(), 0);
9439
9440 // Update the scroll position only
9441 _ = leader.update(cx, |leader, cx| {
9442 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9443 });
9444 follower
9445 .update(cx, |follower, cx| {
9446 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9447 })
9448 .unwrap()
9449 .await
9450 .unwrap();
9451 assert_eq!(
9452 follower
9453 .update(cx, |follower, cx| follower.scroll_position(cx))
9454 .unwrap(),
9455 gpui::Point::new(1.5, 3.5)
9456 );
9457 assert!(*is_still_following.borrow());
9458 assert_eq!(*follower_edit_event_count.borrow(), 0);
9459
9460 // Update the selections and scroll position. The follower's scroll position is updated
9461 // via autoscroll, not via the leader's exact scroll position.
9462 _ = leader.update(cx, |leader, cx| {
9463 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9464 leader.request_autoscroll(Autoscroll::newest(), cx);
9465 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9466 });
9467 follower
9468 .update(cx, |follower, cx| {
9469 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9470 })
9471 .unwrap()
9472 .await
9473 .unwrap();
9474 _ = follower.update(cx, |follower, cx| {
9475 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9476 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9477 });
9478 assert!(*is_still_following.borrow());
9479
9480 // Creating a pending selection that precedes another selection
9481 _ = leader.update(cx, |leader, cx| {
9482 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9483 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9484 });
9485 follower
9486 .update(cx, |follower, cx| {
9487 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9488 })
9489 .unwrap()
9490 .await
9491 .unwrap();
9492 _ = follower.update(cx, |follower, cx| {
9493 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9494 });
9495 assert!(*is_still_following.borrow());
9496
9497 // Extend the pending selection so that it surrounds another selection
9498 _ = leader.update(cx, |leader, cx| {
9499 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9500 });
9501 follower
9502 .update(cx, |follower, cx| {
9503 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9504 })
9505 .unwrap()
9506 .await
9507 .unwrap();
9508 _ = follower.update(cx, |follower, cx| {
9509 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9510 });
9511
9512 // Scrolling locally breaks the follow
9513 _ = follower.update(cx, |follower, cx| {
9514 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9515 follower.set_scroll_anchor(
9516 ScrollAnchor {
9517 anchor: top_anchor,
9518 offset: gpui::Point::new(0.0, 0.5),
9519 },
9520 cx,
9521 );
9522 });
9523 assert!(!(*is_still_following.borrow()));
9524}
9525
9526#[gpui::test]
9527async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9528 init_test(cx, |_| {});
9529
9530 let fs = FakeFs::new(cx.executor());
9531 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9532 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9533 let pane = workspace
9534 .update(cx, |workspace, _| workspace.active_pane().clone())
9535 .unwrap();
9536
9537 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9538
9539 let leader = pane.update(cx, |_, cx| {
9540 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9541 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9542 });
9543
9544 // Start following the editor when it has no excerpts.
9545 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9546 let follower_1 = cx
9547 .update_window(*workspace.deref(), |_, cx| {
9548 Editor::from_state_proto(
9549 workspace.root_view(cx).unwrap(),
9550 ViewId {
9551 creator: Default::default(),
9552 id: 0,
9553 },
9554 &mut state_message,
9555 cx,
9556 )
9557 })
9558 .unwrap()
9559 .unwrap()
9560 .await
9561 .unwrap();
9562
9563 let update_message = Rc::new(RefCell::new(None));
9564 follower_1.update(cx, {
9565 let update = update_message.clone();
9566 |_, cx| {
9567 cx.subscribe(&leader, move |_, leader, event, cx| {
9568 leader
9569 .read(cx)
9570 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9571 })
9572 .detach();
9573 }
9574 });
9575
9576 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9577 (
9578 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9579 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9580 )
9581 });
9582
9583 // Insert some excerpts.
9584 leader.update(cx, |leader, cx| {
9585 leader.buffer.update(cx, |multibuffer, cx| {
9586 let excerpt_ids = multibuffer.push_excerpts(
9587 buffer_1.clone(),
9588 [
9589 ExcerptRange {
9590 context: 1..6,
9591 primary: None,
9592 },
9593 ExcerptRange {
9594 context: 12..15,
9595 primary: None,
9596 },
9597 ExcerptRange {
9598 context: 0..3,
9599 primary: None,
9600 },
9601 ],
9602 cx,
9603 );
9604 multibuffer.insert_excerpts_after(
9605 excerpt_ids[0],
9606 buffer_2.clone(),
9607 [
9608 ExcerptRange {
9609 context: 8..12,
9610 primary: None,
9611 },
9612 ExcerptRange {
9613 context: 0..6,
9614 primary: None,
9615 },
9616 ],
9617 cx,
9618 );
9619 });
9620 });
9621
9622 // Apply the update of adding the excerpts.
9623 follower_1
9624 .update(cx, |follower, cx| {
9625 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9626 })
9627 .await
9628 .unwrap();
9629 assert_eq!(
9630 follower_1.update(cx, |editor, cx| editor.text(cx)),
9631 leader.update(cx, |editor, cx| editor.text(cx))
9632 );
9633 update_message.borrow_mut().take();
9634
9635 // Start following separately after it already has excerpts.
9636 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9637 let follower_2 = cx
9638 .update_window(*workspace.deref(), |_, cx| {
9639 Editor::from_state_proto(
9640 workspace.root_view(cx).unwrap().clone(),
9641 ViewId {
9642 creator: Default::default(),
9643 id: 0,
9644 },
9645 &mut state_message,
9646 cx,
9647 )
9648 })
9649 .unwrap()
9650 .unwrap()
9651 .await
9652 .unwrap();
9653 assert_eq!(
9654 follower_2.update(cx, |editor, cx| editor.text(cx)),
9655 leader.update(cx, |editor, cx| editor.text(cx))
9656 );
9657
9658 // Remove some excerpts.
9659 leader.update(cx, |leader, cx| {
9660 leader.buffer.update(cx, |multibuffer, cx| {
9661 let excerpt_ids = multibuffer.excerpt_ids();
9662 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9663 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9664 });
9665 });
9666
9667 // Apply the update of removing the excerpts.
9668 follower_1
9669 .update(cx, |follower, cx| {
9670 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9671 })
9672 .await
9673 .unwrap();
9674 follower_2
9675 .update(cx, |follower, cx| {
9676 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9677 })
9678 .await
9679 .unwrap();
9680 update_message.borrow_mut().take();
9681 assert_eq!(
9682 follower_1.update(cx, |editor, cx| editor.text(cx)),
9683 leader.update(cx, |editor, cx| editor.text(cx))
9684 );
9685}
9686
9687#[gpui::test]
9688async fn go_to_prev_overlapping_diagnostic(
9689 executor: BackgroundExecutor,
9690 cx: &mut gpui::TestAppContext,
9691) {
9692 init_test(cx, |_| {});
9693
9694 let mut cx = EditorTestContext::new(cx).await;
9695 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9696
9697 cx.set_state(indoc! {"
9698 ˇfn func(abc def: i32) -> u32 {
9699 }
9700 "});
9701
9702 cx.update(|cx| {
9703 project.update(cx, |project, cx| {
9704 project
9705 .update_diagnostics(
9706 LanguageServerId(0),
9707 lsp::PublishDiagnosticsParams {
9708 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9709 version: None,
9710 diagnostics: vec![
9711 lsp::Diagnostic {
9712 range: lsp::Range::new(
9713 lsp::Position::new(0, 11),
9714 lsp::Position::new(0, 12),
9715 ),
9716 severity: Some(lsp::DiagnosticSeverity::ERROR),
9717 ..Default::default()
9718 },
9719 lsp::Diagnostic {
9720 range: lsp::Range::new(
9721 lsp::Position::new(0, 12),
9722 lsp::Position::new(0, 15),
9723 ),
9724 severity: Some(lsp::DiagnosticSeverity::ERROR),
9725 ..Default::default()
9726 },
9727 lsp::Diagnostic {
9728 range: lsp::Range::new(
9729 lsp::Position::new(0, 25),
9730 lsp::Position::new(0, 28),
9731 ),
9732 severity: Some(lsp::DiagnosticSeverity::ERROR),
9733 ..Default::default()
9734 },
9735 ],
9736 },
9737 &[],
9738 cx,
9739 )
9740 .unwrap()
9741 });
9742 });
9743
9744 executor.run_until_parked();
9745
9746 cx.update_editor(|editor, cx| {
9747 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9748 });
9749
9750 cx.assert_editor_state(indoc! {"
9751 fn func(abc def: i32) -> ˇu32 {
9752 }
9753 "});
9754
9755 cx.update_editor(|editor, cx| {
9756 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9757 });
9758
9759 cx.assert_editor_state(indoc! {"
9760 fn func(abc ˇdef: i32) -> u32 {
9761 }
9762 "});
9763
9764 cx.update_editor(|editor, cx| {
9765 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9766 });
9767
9768 cx.assert_editor_state(indoc! {"
9769 fn func(abcˇ def: i32) -> u32 {
9770 }
9771 "});
9772
9773 cx.update_editor(|editor, cx| {
9774 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9775 });
9776
9777 cx.assert_editor_state(indoc! {"
9778 fn func(abc def: i32) -> ˇu32 {
9779 }
9780 "});
9781}
9782
9783#[gpui::test]
9784async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9785 init_test(cx, |_| {});
9786
9787 let mut cx = EditorTestContext::new(cx).await;
9788
9789 cx.set_state(indoc! {"
9790 fn func(abˇc def: i32) -> u32 {
9791 }
9792 "});
9793 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9794
9795 cx.update(|cx| {
9796 project.update(cx, |project, cx| {
9797 project.update_diagnostics(
9798 LanguageServerId(0),
9799 lsp::PublishDiagnosticsParams {
9800 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9801 version: None,
9802 diagnostics: vec![lsp::Diagnostic {
9803 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9804 severity: Some(lsp::DiagnosticSeverity::ERROR),
9805 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9806 ..Default::default()
9807 }],
9808 },
9809 &[],
9810 cx,
9811 )
9812 })
9813 }).unwrap();
9814 cx.run_until_parked();
9815 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9816 cx.run_until_parked();
9817 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9818}
9819
9820#[gpui::test]
9821async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9822 init_test(cx, |_| {});
9823
9824 let mut cx = EditorTestContext::new(cx).await;
9825
9826 let diff_base = r#"
9827 use some::mod;
9828
9829 const A: u32 = 42;
9830
9831 fn main() {
9832 println!("hello");
9833
9834 println!("world");
9835 }
9836 "#
9837 .unindent();
9838
9839 // Edits are modified, removed, modified, added
9840 cx.set_state(
9841 &r#"
9842 use some::modified;
9843
9844 ˇ
9845 fn main() {
9846 println!("hello there");
9847
9848 println!("around the");
9849 println!("world");
9850 }
9851 "#
9852 .unindent(),
9853 );
9854
9855 cx.set_diff_base(Some(&diff_base));
9856 executor.run_until_parked();
9857
9858 cx.update_editor(|editor, cx| {
9859 //Wrap around the bottom of the buffer
9860 for _ in 0..3 {
9861 editor.go_to_next_hunk(&GoToHunk, cx);
9862 }
9863 });
9864
9865 cx.assert_editor_state(
9866 &r#"
9867 ˇuse some::modified;
9868
9869
9870 fn main() {
9871 println!("hello there");
9872
9873 println!("around the");
9874 println!("world");
9875 }
9876 "#
9877 .unindent(),
9878 );
9879
9880 cx.update_editor(|editor, cx| {
9881 //Wrap around the top of the buffer
9882 for _ in 0..2 {
9883 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9884 }
9885 });
9886
9887 cx.assert_editor_state(
9888 &r#"
9889 use some::modified;
9890
9891
9892 fn main() {
9893 ˇ println!("hello there");
9894
9895 println!("around the");
9896 println!("world");
9897 }
9898 "#
9899 .unindent(),
9900 );
9901
9902 cx.update_editor(|editor, cx| {
9903 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9904 });
9905
9906 cx.assert_editor_state(
9907 &r#"
9908 use some::modified;
9909
9910 ˇ
9911 fn main() {
9912 println!("hello there");
9913
9914 println!("around the");
9915 println!("world");
9916 }
9917 "#
9918 .unindent(),
9919 );
9920
9921 cx.update_editor(|editor, cx| {
9922 for _ in 0..3 {
9923 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9924 }
9925 });
9926
9927 cx.assert_editor_state(
9928 &r#"
9929 use some::modified;
9930
9931
9932 fn main() {
9933 ˇ println!("hello there");
9934
9935 println!("around the");
9936 println!("world");
9937 }
9938 "#
9939 .unindent(),
9940 );
9941
9942 cx.update_editor(|editor, cx| {
9943 editor.fold(&Fold, cx);
9944
9945 //Make sure that the fold only gets one hunk
9946 for _ in 0..4 {
9947 editor.go_to_next_hunk(&GoToHunk, cx);
9948 }
9949 });
9950
9951 cx.assert_editor_state(
9952 &r#"
9953 ˇuse some::modified;
9954
9955
9956 fn main() {
9957 println!("hello there");
9958
9959 println!("around the");
9960 println!("world");
9961 }
9962 "#
9963 .unindent(),
9964 );
9965}
9966
9967#[test]
9968fn test_split_words() {
9969 fn split(text: &str) -> Vec<&str> {
9970 split_words(text).collect()
9971 }
9972
9973 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9974 assert_eq!(split("hello_world"), &["hello_", "world"]);
9975 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9976 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9977 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9978 assert_eq!(split("helloworld"), &["helloworld"]);
9979
9980 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9981}
9982
9983#[gpui::test]
9984async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9985 init_test(cx, |_| {});
9986
9987 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9988 let mut assert = |before, after| {
9989 let _state_context = cx.set_state(before);
9990 cx.update_editor(|editor, cx| {
9991 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9992 });
9993 cx.assert_editor_state(after);
9994 };
9995
9996 // Outside bracket jumps to outside of matching bracket
9997 assert("console.logˇ(var);", "console.log(var)ˇ;");
9998 assert("console.log(var)ˇ;", "console.logˇ(var);");
9999
10000 // Inside bracket jumps to inside of matching bracket
10001 assert("console.log(ˇvar);", "console.log(varˇ);");
10002 assert("console.log(varˇ);", "console.log(ˇvar);");
10003
10004 // When outside a bracket and inside, favor jumping to the inside bracket
10005 assert(
10006 "console.log('foo', [1, 2, 3]ˇ);",
10007 "console.log(ˇ'foo', [1, 2, 3]);",
10008 );
10009 assert(
10010 "console.log(ˇ'foo', [1, 2, 3]);",
10011 "console.log('foo', [1, 2, 3]ˇ);",
10012 );
10013
10014 // Bias forward if two options are equally likely
10015 assert(
10016 "let result = curried_fun()ˇ();",
10017 "let result = curried_fun()()ˇ;",
10018 );
10019
10020 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10021 assert(
10022 indoc! {"
10023 function test() {
10024 console.log('test')ˇ
10025 }"},
10026 indoc! {"
10027 function test() {
10028 console.logˇ('test')
10029 }"},
10030 );
10031}
10032
10033#[gpui::test]
10034async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10035 init_test(cx, |_| {});
10036
10037 let fs = FakeFs::new(cx.executor());
10038 fs.insert_tree(
10039 "/a",
10040 json!({
10041 "main.rs": "fn main() { let a = 5; }",
10042 "other.rs": "// Test file",
10043 }),
10044 )
10045 .await;
10046 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10047
10048 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10049 language_registry.add(Arc::new(Language::new(
10050 LanguageConfig {
10051 name: "Rust".into(),
10052 matcher: LanguageMatcher {
10053 path_suffixes: vec!["rs".to_string()],
10054 ..Default::default()
10055 },
10056 brackets: BracketPairConfig {
10057 pairs: vec![BracketPair {
10058 start: "{".to_string(),
10059 end: "}".to_string(),
10060 close: true,
10061 surround: true,
10062 newline: true,
10063 }],
10064 disabled_scopes_by_bracket_ix: Vec::new(),
10065 },
10066 ..Default::default()
10067 },
10068 Some(tree_sitter_rust::LANGUAGE.into()),
10069 )));
10070 let mut fake_servers = language_registry.register_fake_lsp(
10071 "Rust",
10072 FakeLspAdapter {
10073 capabilities: lsp::ServerCapabilities {
10074 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10075 first_trigger_character: "{".to_string(),
10076 more_trigger_character: None,
10077 }),
10078 ..Default::default()
10079 },
10080 ..Default::default()
10081 },
10082 );
10083
10084 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10085
10086 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10087
10088 let worktree_id = workspace
10089 .update(cx, |workspace, cx| {
10090 workspace.project().update(cx, |project, cx| {
10091 project.worktrees(cx).next().unwrap().read(cx).id()
10092 })
10093 })
10094 .unwrap();
10095
10096 let buffer = project
10097 .update(cx, |project, cx| {
10098 project.open_local_buffer("/a/main.rs", cx)
10099 })
10100 .await
10101 .unwrap();
10102 cx.executor().run_until_parked();
10103 cx.executor().start_waiting();
10104 let fake_server = fake_servers.next().await.unwrap();
10105 let editor_handle = workspace
10106 .update(cx, |workspace, cx| {
10107 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10108 })
10109 .unwrap()
10110 .await
10111 .unwrap()
10112 .downcast::<Editor>()
10113 .unwrap();
10114
10115 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10116 assert_eq!(
10117 params.text_document_position.text_document.uri,
10118 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10119 );
10120 assert_eq!(
10121 params.text_document_position.position,
10122 lsp::Position::new(0, 21),
10123 );
10124
10125 Ok(Some(vec![lsp::TextEdit {
10126 new_text: "]".to_string(),
10127 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10128 }]))
10129 });
10130
10131 editor_handle.update(cx, |editor, cx| {
10132 editor.focus(cx);
10133 editor.change_selections(None, cx, |s| {
10134 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10135 });
10136 editor.handle_input("{", cx);
10137 });
10138
10139 cx.executor().run_until_parked();
10140
10141 buffer.update(cx, |buffer, _| {
10142 assert_eq!(
10143 buffer.text(),
10144 "fn main() { let a = {5}; }",
10145 "No extra braces from on type formatting should appear in the buffer"
10146 )
10147 });
10148}
10149
10150#[gpui::test]
10151async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10152 init_test(cx, |_| {});
10153
10154 let fs = FakeFs::new(cx.executor());
10155 fs.insert_tree(
10156 "/a",
10157 json!({
10158 "main.rs": "fn main() { let a = 5; }",
10159 "other.rs": "// Test file",
10160 }),
10161 )
10162 .await;
10163
10164 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10165
10166 let server_restarts = Arc::new(AtomicUsize::new(0));
10167 let closure_restarts = Arc::clone(&server_restarts);
10168 let language_server_name = "test language server";
10169 let language_name: LanguageName = "Rust".into();
10170
10171 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10172 language_registry.add(Arc::new(Language::new(
10173 LanguageConfig {
10174 name: language_name.clone(),
10175 matcher: LanguageMatcher {
10176 path_suffixes: vec!["rs".to_string()],
10177 ..Default::default()
10178 },
10179 ..Default::default()
10180 },
10181 Some(tree_sitter_rust::LANGUAGE.into()),
10182 )));
10183 let mut fake_servers = language_registry.register_fake_lsp(
10184 "Rust",
10185 FakeLspAdapter {
10186 name: language_server_name,
10187 initialization_options: Some(json!({
10188 "testOptionValue": true
10189 })),
10190 initializer: Some(Box::new(move |fake_server| {
10191 let task_restarts = Arc::clone(&closure_restarts);
10192 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10193 task_restarts.fetch_add(1, atomic::Ordering::Release);
10194 futures::future::ready(Ok(()))
10195 });
10196 })),
10197 ..Default::default()
10198 },
10199 );
10200
10201 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10202 let _buffer = project
10203 .update(cx, |project, cx| {
10204 project.open_local_buffer("/a/main.rs", cx)
10205 })
10206 .await
10207 .unwrap();
10208 let _fake_server = fake_servers.next().await.unwrap();
10209 update_test_language_settings(cx, |language_settings| {
10210 language_settings.languages.insert(
10211 language_name.clone(),
10212 LanguageSettingsContent {
10213 tab_size: NonZeroU32::new(8),
10214 ..Default::default()
10215 },
10216 );
10217 });
10218 cx.executor().run_until_parked();
10219 assert_eq!(
10220 server_restarts.load(atomic::Ordering::Acquire),
10221 0,
10222 "Should not restart LSP server on an unrelated change"
10223 );
10224
10225 update_test_project_settings(cx, |project_settings| {
10226 project_settings.lsp.insert(
10227 "Some other server name".into(),
10228 LspSettings {
10229 binary: None,
10230 settings: None,
10231 initialization_options: Some(json!({
10232 "some other init value": false
10233 })),
10234 },
10235 );
10236 });
10237 cx.executor().run_until_parked();
10238 assert_eq!(
10239 server_restarts.load(atomic::Ordering::Acquire),
10240 0,
10241 "Should not restart LSP server on an unrelated LSP settings change"
10242 );
10243
10244 update_test_project_settings(cx, |project_settings| {
10245 project_settings.lsp.insert(
10246 language_server_name.into(),
10247 LspSettings {
10248 binary: None,
10249 settings: None,
10250 initialization_options: Some(json!({
10251 "anotherInitValue": false
10252 })),
10253 },
10254 );
10255 });
10256 cx.executor().run_until_parked();
10257 assert_eq!(
10258 server_restarts.load(atomic::Ordering::Acquire),
10259 1,
10260 "Should restart LSP server on a related LSP settings change"
10261 );
10262
10263 update_test_project_settings(cx, |project_settings| {
10264 project_settings.lsp.insert(
10265 language_server_name.into(),
10266 LspSettings {
10267 binary: None,
10268 settings: None,
10269 initialization_options: Some(json!({
10270 "anotherInitValue": false
10271 })),
10272 },
10273 );
10274 });
10275 cx.executor().run_until_parked();
10276 assert_eq!(
10277 server_restarts.load(atomic::Ordering::Acquire),
10278 1,
10279 "Should not restart LSP server on a related LSP settings change that is the same"
10280 );
10281
10282 update_test_project_settings(cx, |project_settings| {
10283 project_settings.lsp.insert(
10284 language_server_name.into(),
10285 LspSettings {
10286 binary: None,
10287 settings: None,
10288 initialization_options: None,
10289 },
10290 );
10291 });
10292 cx.executor().run_until_parked();
10293 assert_eq!(
10294 server_restarts.load(atomic::Ordering::Acquire),
10295 2,
10296 "Should restart LSP server on another related LSP settings change"
10297 );
10298}
10299
10300#[gpui::test]
10301async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10302 init_test(cx, |_| {});
10303
10304 let mut cx = EditorLspTestContext::new_rust(
10305 lsp::ServerCapabilities {
10306 completion_provider: Some(lsp::CompletionOptions {
10307 trigger_characters: Some(vec![".".to_string()]),
10308 resolve_provider: Some(true),
10309 ..Default::default()
10310 }),
10311 ..Default::default()
10312 },
10313 cx,
10314 )
10315 .await;
10316
10317 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10318 cx.simulate_keystroke(".");
10319 let completion_item = lsp::CompletionItem {
10320 label: "some".into(),
10321 kind: Some(lsp::CompletionItemKind::SNIPPET),
10322 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10323 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10324 kind: lsp::MarkupKind::Markdown,
10325 value: "```rust\nSome(2)\n```".to_string(),
10326 })),
10327 deprecated: Some(false),
10328 sort_text: Some("fffffff2".to_string()),
10329 filter_text: Some("some".to_string()),
10330 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10331 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10332 range: lsp::Range {
10333 start: lsp::Position {
10334 line: 0,
10335 character: 22,
10336 },
10337 end: lsp::Position {
10338 line: 0,
10339 character: 22,
10340 },
10341 },
10342 new_text: "Some(2)".to_string(),
10343 })),
10344 additional_text_edits: Some(vec![lsp::TextEdit {
10345 range: lsp::Range {
10346 start: lsp::Position {
10347 line: 0,
10348 character: 20,
10349 },
10350 end: lsp::Position {
10351 line: 0,
10352 character: 22,
10353 },
10354 },
10355 new_text: "".to_string(),
10356 }]),
10357 ..Default::default()
10358 };
10359
10360 let closure_completion_item = completion_item.clone();
10361 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10362 let task_completion_item = closure_completion_item.clone();
10363 async move {
10364 Ok(Some(lsp::CompletionResponse::Array(vec![
10365 task_completion_item,
10366 ])))
10367 }
10368 });
10369
10370 request.next().await;
10371
10372 cx.condition(|editor, _| editor.context_menu_visible())
10373 .await;
10374 let apply_additional_edits = cx.update_editor(|editor, cx| {
10375 editor
10376 .confirm_completion(&ConfirmCompletion::default(), cx)
10377 .unwrap()
10378 });
10379 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10380
10381 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10382 let task_completion_item = completion_item.clone();
10383 async move { Ok(task_completion_item) }
10384 })
10385 .next()
10386 .await
10387 .unwrap();
10388 apply_additional_edits.await.unwrap();
10389 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10390}
10391
10392#[gpui::test]
10393async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10394 init_test(cx, |_| {});
10395
10396 let mut cx = EditorLspTestContext::new(
10397 Language::new(
10398 LanguageConfig {
10399 matcher: LanguageMatcher {
10400 path_suffixes: vec!["jsx".into()],
10401 ..Default::default()
10402 },
10403 overrides: [(
10404 "element".into(),
10405 LanguageConfigOverride {
10406 word_characters: Override::Set(['-'].into_iter().collect()),
10407 ..Default::default()
10408 },
10409 )]
10410 .into_iter()
10411 .collect(),
10412 ..Default::default()
10413 },
10414 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10415 )
10416 .with_override_query("(jsx_self_closing_element) @element")
10417 .unwrap(),
10418 lsp::ServerCapabilities {
10419 completion_provider: Some(lsp::CompletionOptions {
10420 trigger_characters: Some(vec![":".to_string()]),
10421 ..Default::default()
10422 }),
10423 ..Default::default()
10424 },
10425 cx,
10426 )
10427 .await;
10428
10429 cx.lsp
10430 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10431 Ok(Some(lsp::CompletionResponse::Array(vec![
10432 lsp::CompletionItem {
10433 label: "bg-blue".into(),
10434 ..Default::default()
10435 },
10436 lsp::CompletionItem {
10437 label: "bg-red".into(),
10438 ..Default::default()
10439 },
10440 lsp::CompletionItem {
10441 label: "bg-yellow".into(),
10442 ..Default::default()
10443 },
10444 ])))
10445 });
10446
10447 cx.set_state(r#"<p class="bgˇ" />"#);
10448
10449 // Trigger completion when typing a dash, because the dash is an extra
10450 // word character in the 'element' scope, which contains the cursor.
10451 cx.simulate_keystroke("-");
10452 cx.executor().run_until_parked();
10453 cx.update_editor(|editor, _| {
10454 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10455 assert_eq!(
10456 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10457 &["bg-red", "bg-blue", "bg-yellow"]
10458 );
10459 } else {
10460 panic!("expected completion menu to be open");
10461 }
10462 });
10463
10464 cx.simulate_keystroke("l");
10465 cx.executor().run_until_parked();
10466 cx.update_editor(|editor, _| {
10467 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10468 assert_eq!(
10469 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10470 &["bg-blue", "bg-yellow"]
10471 );
10472 } else {
10473 panic!("expected completion menu to be open");
10474 }
10475 });
10476
10477 // When filtering completions, consider the character after the '-' to
10478 // be the start of a subword.
10479 cx.set_state(r#"<p class="yelˇ" />"#);
10480 cx.simulate_keystroke("l");
10481 cx.executor().run_until_parked();
10482 cx.update_editor(|editor, _| {
10483 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10484 assert_eq!(
10485 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10486 &["bg-yellow"]
10487 );
10488 } else {
10489 panic!("expected completion menu to be open");
10490 }
10491 });
10492}
10493
10494#[gpui::test]
10495async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10496 init_test(cx, |settings| {
10497 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10498 FormatterList(vec![Formatter::Prettier].into()),
10499 ))
10500 });
10501
10502 let fs = FakeFs::new(cx.executor());
10503 fs.insert_file("/file.ts", Default::default()).await;
10504
10505 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10506 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10507
10508 language_registry.add(Arc::new(Language::new(
10509 LanguageConfig {
10510 name: "TypeScript".into(),
10511 matcher: LanguageMatcher {
10512 path_suffixes: vec!["ts".to_string()],
10513 ..Default::default()
10514 },
10515 ..Default::default()
10516 },
10517 Some(tree_sitter_rust::LANGUAGE.into()),
10518 )));
10519 update_test_language_settings(cx, |settings| {
10520 settings.defaults.prettier = Some(PrettierSettings {
10521 allowed: true,
10522 ..PrettierSettings::default()
10523 });
10524 });
10525
10526 let test_plugin = "test_plugin";
10527 let _ = language_registry.register_fake_lsp(
10528 "TypeScript",
10529 FakeLspAdapter {
10530 prettier_plugins: vec![test_plugin],
10531 ..Default::default()
10532 },
10533 );
10534
10535 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10536 let buffer = project
10537 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10538 .await
10539 .unwrap();
10540
10541 let buffer_text = "one\ntwo\nthree\n";
10542 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10543 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10544 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10545
10546 editor
10547 .update(cx, |editor, cx| {
10548 editor.perform_format(
10549 project.clone(),
10550 FormatTrigger::Manual,
10551 FormatTarget::Buffer,
10552 cx,
10553 )
10554 })
10555 .unwrap()
10556 .await;
10557 assert_eq!(
10558 editor.update(cx, |editor, cx| editor.text(cx)),
10559 buffer_text.to_string() + prettier_format_suffix,
10560 "Test prettier formatting was not applied to the original buffer text",
10561 );
10562
10563 update_test_language_settings(cx, |settings| {
10564 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10565 });
10566 let format = editor.update(cx, |editor, cx| {
10567 editor.perform_format(
10568 project.clone(),
10569 FormatTrigger::Manual,
10570 FormatTarget::Buffer,
10571 cx,
10572 )
10573 });
10574 format.await.unwrap();
10575 assert_eq!(
10576 editor.update(cx, |editor, cx| editor.text(cx)),
10577 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10578 "Autoformatting (via test prettier) was not applied to the original buffer text",
10579 );
10580}
10581
10582#[gpui::test]
10583async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10584 init_test(cx, |_| {});
10585 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10586 let base_text = indoc! {r#"struct Row;
10587struct Row1;
10588struct Row2;
10589
10590struct Row4;
10591struct Row5;
10592struct Row6;
10593
10594struct Row8;
10595struct Row9;
10596struct Row10;"#};
10597
10598 // When addition hunks are not adjacent to carets, no hunk revert is performed
10599 assert_hunk_revert(
10600 indoc! {r#"struct Row;
10601 struct Row1;
10602 struct Row1.1;
10603 struct Row1.2;
10604 struct Row2;ˇ
10605
10606 struct Row4;
10607 struct Row5;
10608 struct Row6;
10609
10610 struct Row8;
10611 ˇstruct Row9;
10612 struct Row9.1;
10613 struct Row9.2;
10614 struct Row9.3;
10615 struct Row10;"#},
10616 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10617 indoc! {r#"struct Row;
10618 struct Row1;
10619 struct Row1.1;
10620 struct Row1.2;
10621 struct Row2;ˇ
10622
10623 struct Row4;
10624 struct Row5;
10625 struct Row6;
10626
10627 struct Row8;
10628 ˇstruct Row9;
10629 struct Row9.1;
10630 struct Row9.2;
10631 struct Row9.3;
10632 struct Row10;"#},
10633 base_text,
10634 &mut cx,
10635 );
10636 // Same for selections
10637 assert_hunk_revert(
10638 indoc! {r#"struct Row;
10639 struct Row1;
10640 struct Row2;
10641 struct Row2.1;
10642 struct Row2.2;
10643 «ˇ
10644 struct Row4;
10645 struct» Row5;
10646 «struct Row6;
10647 ˇ»
10648 struct Row9.1;
10649 struct Row9.2;
10650 struct Row9.3;
10651 struct Row8;
10652 struct Row9;
10653 struct Row10;"#},
10654 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10655 indoc! {r#"struct Row;
10656 struct Row1;
10657 struct Row2;
10658 struct Row2.1;
10659 struct Row2.2;
10660 «ˇ
10661 struct Row4;
10662 struct» Row5;
10663 «struct Row6;
10664 ˇ»
10665 struct Row9.1;
10666 struct Row9.2;
10667 struct Row9.3;
10668 struct Row8;
10669 struct Row9;
10670 struct Row10;"#},
10671 base_text,
10672 &mut cx,
10673 );
10674
10675 // When carets and selections intersect the addition hunks, those are reverted.
10676 // Adjacent carets got merged.
10677 assert_hunk_revert(
10678 indoc! {r#"struct Row;
10679 ˇ// something on the top
10680 struct Row1;
10681 struct Row2;
10682 struct Roˇw3.1;
10683 struct Row2.2;
10684 struct Row2.3;ˇ
10685
10686 struct Row4;
10687 struct ˇRow5.1;
10688 struct Row5.2;
10689 struct «Rowˇ»5.3;
10690 struct Row5;
10691 struct Row6;
10692 ˇ
10693 struct Row9.1;
10694 struct «Rowˇ»9.2;
10695 struct «ˇRow»9.3;
10696 struct Row8;
10697 struct Row9;
10698 «ˇ// something on bottom»
10699 struct Row10;"#},
10700 vec![
10701 DiffHunkStatus::Added,
10702 DiffHunkStatus::Added,
10703 DiffHunkStatus::Added,
10704 DiffHunkStatus::Added,
10705 DiffHunkStatus::Added,
10706 ],
10707 indoc! {r#"struct Row;
10708 ˇstruct Row1;
10709 struct Row2;
10710 ˇ
10711 struct Row4;
10712 ˇstruct Row5;
10713 struct Row6;
10714 ˇ
10715 ˇstruct Row8;
10716 struct Row9;
10717 ˇstruct Row10;"#},
10718 base_text,
10719 &mut cx,
10720 );
10721}
10722
10723#[gpui::test]
10724async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10725 init_test(cx, |_| {});
10726 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10727 let base_text = indoc! {r#"struct Row;
10728struct Row1;
10729struct Row2;
10730
10731struct Row4;
10732struct Row5;
10733struct Row6;
10734
10735struct Row8;
10736struct Row9;
10737struct Row10;"#};
10738
10739 // Modification hunks behave the same as the addition ones.
10740 assert_hunk_revert(
10741 indoc! {r#"struct Row;
10742 struct Row1;
10743 struct Row33;
10744 ˇ
10745 struct Row4;
10746 struct Row5;
10747 struct Row6;
10748 ˇ
10749 struct Row99;
10750 struct Row9;
10751 struct Row10;"#},
10752 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10753 indoc! {r#"struct Row;
10754 struct Row1;
10755 struct Row33;
10756 ˇ
10757 struct Row4;
10758 struct Row5;
10759 struct Row6;
10760 ˇ
10761 struct Row99;
10762 struct Row9;
10763 struct Row10;"#},
10764 base_text,
10765 &mut cx,
10766 );
10767 assert_hunk_revert(
10768 indoc! {r#"struct Row;
10769 struct Row1;
10770 struct Row33;
10771 «ˇ
10772 struct Row4;
10773 struct» Row5;
10774 «struct Row6;
10775 ˇ»
10776 struct Row99;
10777 struct Row9;
10778 struct Row10;"#},
10779 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10780 indoc! {r#"struct Row;
10781 struct Row1;
10782 struct Row33;
10783 «ˇ
10784 struct Row4;
10785 struct» Row5;
10786 «struct Row6;
10787 ˇ»
10788 struct Row99;
10789 struct Row9;
10790 struct Row10;"#},
10791 base_text,
10792 &mut cx,
10793 );
10794
10795 assert_hunk_revert(
10796 indoc! {r#"ˇstruct Row1.1;
10797 struct Row1;
10798 «ˇstr»uct Row22;
10799
10800 struct ˇRow44;
10801 struct Row5;
10802 struct «Rˇ»ow66;ˇ
10803
10804 «struˇ»ct Row88;
10805 struct Row9;
10806 struct Row1011;ˇ"#},
10807 vec![
10808 DiffHunkStatus::Modified,
10809 DiffHunkStatus::Modified,
10810 DiffHunkStatus::Modified,
10811 DiffHunkStatus::Modified,
10812 DiffHunkStatus::Modified,
10813 DiffHunkStatus::Modified,
10814 ],
10815 indoc! {r#"struct Row;
10816 ˇstruct Row1;
10817 struct Row2;
10818 ˇ
10819 struct Row4;
10820 ˇstruct Row5;
10821 struct Row6;
10822 ˇ
10823 struct Row8;
10824 ˇstruct Row9;
10825 struct Row10;ˇ"#},
10826 base_text,
10827 &mut cx,
10828 );
10829}
10830
10831#[gpui::test]
10832async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10833 init_test(cx, |_| {});
10834 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10835 let base_text = indoc! {r#"struct Row;
10836struct Row1;
10837struct Row2;
10838
10839struct Row4;
10840struct Row5;
10841struct Row6;
10842
10843struct Row8;
10844struct Row9;
10845struct Row10;"#};
10846
10847 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10848 assert_hunk_revert(
10849 indoc! {r#"struct Row;
10850 struct Row2;
10851
10852 ˇstruct Row4;
10853 struct Row5;
10854 struct Row6;
10855 ˇ
10856 struct Row8;
10857 struct Row10;"#},
10858 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10859 indoc! {r#"struct Row;
10860 struct Row2;
10861
10862 ˇstruct Row4;
10863 struct Row5;
10864 struct Row6;
10865 ˇ
10866 struct Row8;
10867 struct Row10;"#},
10868 base_text,
10869 &mut cx,
10870 );
10871 assert_hunk_revert(
10872 indoc! {r#"struct Row;
10873 struct Row2;
10874
10875 «ˇstruct Row4;
10876 struct» Row5;
10877 «struct Row6;
10878 ˇ»
10879 struct Row8;
10880 struct Row10;"#},
10881 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10882 indoc! {r#"struct Row;
10883 struct Row2;
10884
10885 «ˇstruct Row4;
10886 struct» Row5;
10887 «struct Row6;
10888 ˇ»
10889 struct Row8;
10890 struct Row10;"#},
10891 base_text,
10892 &mut cx,
10893 );
10894
10895 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10896 assert_hunk_revert(
10897 indoc! {r#"struct Row;
10898 ˇstruct Row2;
10899
10900 struct Row4;
10901 struct Row5;
10902 struct Row6;
10903
10904 struct Row8;ˇ
10905 struct Row10;"#},
10906 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10907 indoc! {r#"struct Row;
10908 struct Row1;
10909 ˇstruct Row2;
10910
10911 struct Row4;
10912 struct Row5;
10913 struct Row6;
10914
10915 struct Row8;ˇ
10916 struct Row9;
10917 struct Row10;"#},
10918 base_text,
10919 &mut cx,
10920 );
10921 assert_hunk_revert(
10922 indoc! {r#"struct Row;
10923 struct Row2«ˇ;
10924 struct Row4;
10925 struct» Row5;
10926 «struct Row6;
10927
10928 struct Row8;ˇ»
10929 struct Row10;"#},
10930 vec![
10931 DiffHunkStatus::Removed,
10932 DiffHunkStatus::Removed,
10933 DiffHunkStatus::Removed,
10934 ],
10935 indoc! {r#"struct Row;
10936 struct Row1;
10937 struct Row2«ˇ;
10938
10939 struct Row4;
10940 struct» Row5;
10941 «struct Row6;
10942
10943 struct Row8;ˇ»
10944 struct Row9;
10945 struct Row10;"#},
10946 base_text,
10947 &mut cx,
10948 );
10949}
10950
10951#[gpui::test]
10952async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10953 init_test(cx, |_| {});
10954
10955 let cols = 4;
10956 let rows = 10;
10957 let sample_text_1 = sample_text(rows, cols, 'a');
10958 assert_eq!(
10959 sample_text_1,
10960 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10961 );
10962 let sample_text_2 = sample_text(rows, cols, 'l');
10963 assert_eq!(
10964 sample_text_2,
10965 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10966 );
10967 let sample_text_3 = sample_text(rows, cols, 'v');
10968 assert_eq!(
10969 sample_text_3,
10970 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10971 );
10972
10973 fn diff_every_buffer_row(
10974 buffer: &Model<Buffer>,
10975 sample_text: String,
10976 cols: usize,
10977 cx: &mut gpui::TestAppContext,
10978 ) {
10979 // revert first character in each row, creating one large diff hunk per buffer
10980 let is_first_char = |offset: usize| offset % cols == 0;
10981 buffer.update(cx, |buffer, cx| {
10982 buffer.set_text(
10983 sample_text
10984 .chars()
10985 .enumerate()
10986 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10987 .collect::<String>(),
10988 cx,
10989 );
10990 buffer.set_diff_base(Some(sample_text), cx);
10991 });
10992 cx.executor().run_until_parked();
10993 }
10994
10995 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10996 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10997
10998 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10999 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11000
11001 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11002 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11003
11004 let multibuffer = cx.new_model(|cx| {
11005 let mut multibuffer = MultiBuffer::new(ReadWrite);
11006 multibuffer.push_excerpts(
11007 buffer_1.clone(),
11008 [
11009 ExcerptRange {
11010 context: Point::new(0, 0)..Point::new(3, 0),
11011 primary: None,
11012 },
11013 ExcerptRange {
11014 context: Point::new(5, 0)..Point::new(7, 0),
11015 primary: None,
11016 },
11017 ExcerptRange {
11018 context: Point::new(9, 0)..Point::new(10, 4),
11019 primary: None,
11020 },
11021 ],
11022 cx,
11023 );
11024 multibuffer.push_excerpts(
11025 buffer_2.clone(),
11026 [
11027 ExcerptRange {
11028 context: Point::new(0, 0)..Point::new(3, 0),
11029 primary: None,
11030 },
11031 ExcerptRange {
11032 context: Point::new(5, 0)..Point::new(7, 0),
11033 primary: None,
11034 },
11035 ExcerptRange {
11036 context: Point::new(9, 0)..Point::new(10, 4),
11037 primary: None,
11038 },
11039 ],
11040 cx,
11041 );
11042 multibuffer.push_excerpts(
11043 buffer_3.clone(),
11044 [
11045 ExcerptRange {
11046 context: Point::new(0, 0)..Point::new(3, 0),
11047 primary: None,
11048 },
11049 ExcerptRange {
11050 context: Point::new(5, 0)..Point::new(7, 0),
11051 primary: None,
11052 },
11053 ExcerptRange {
11054 context: Point::new(9, 0)..Point::new(10, 4),
11055 primary: None,
11056 },
11057 ],
11058 cx,
11059 );
11060 multibuffer
11061 });
11062
11063 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11064 editor.update(cx, |editor, cx| {
11065 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");
11066 editor.select_all(&SelectAll, cx);
11067 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11068 });
11069 cx.executor().run_until_parked();
11070 // When all ranges are selected, all buffer hunks are reverted.
11071 editor.update(cx, |editor, cx| {
11072 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");
11073 });
11074 buffer_1.update(cx, |buffer, _| {
11075 assert_eq!(buffer.text(), sample_text_1);
11076 });
11077 buffer_2.update(cx, |buffer, _| {
11078 assert_eq!(buffer.text(), sample_text_2);
11079 });
11080 buffer_3.update(cx, |buffer, _| {
11081 assert_eq!(buffer.text(), sample_text_3);
11082 });
11083
11084 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11085 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11086 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11087 editor.update(cx, |editor, cx| {
11088 editor.change_selections(None, cx, |s| {
11089 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11090 });
11091 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11092 });
11093 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11094 // but not affect buffer_2 and its related excerpts.
11095 editor.update(cx, |editor, cx| {
11096 assert_eq!(
11097 editor.text(cx),
11098 "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"
11099 );
11100 });
11101 buffer_1.update(cx, |buffer, _| {
11102 assert_eq!(buffer.text(), sample_text_1);
11103 });
11104 buffer_2.update(cx, |buffer, _| {
11105 assert_eq!(
11106 buffer.text(),
11107 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11108 );
11109 });
11110 buffer_3.update(cx, |buffer, _| {
11111 assert_eq!(
11112 buffer.text(),
11113 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11114 );
11115 });
11116}
11117
11118#[gpui::test]
11119async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11120 init_test(cx, |_| {});
11121
11122 let cols = 4;
11123 let rows = 10;
11124 let sample_text_1 = sample_text(rows, cols, 'a');
11125 assert_eq!(
11126 sample_text_1,
11127 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11128 );
11129 let sample_text_2 = sample_text(rows, cols, 'l');
11130 assert_eq!(
11131 sample_text_2,
11132 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11133 );
11134 let sample_text_3 = sample_text(rows, cols, 'v');
11135 assert_eq!(
11136 sample_text_3,
11137 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11138 );
11139
11140 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11141 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11142 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11143
11144 let multi_buffer = cx.new_model(|cx| {
11145 let mut multibuffer = MultiBuffer::new(ReadWrite);
11146 multibuffer.push_excerpts(
11147 buffer_1.clone(),
11148 [
11149 ExcerptRange {
11150 context: Point::new(0, 0)..Point::new(3, 0),
11151 primary: None,
11152 },
11153 ExcerptRange {
11154 context: Point::new(5, 0)..Point::new(7, 0),
11155 primary: None,
11156 },
11157 ExcerptRange {
11158 context: Point::new(9, 0)..Point::new(10, 4),
11159 primary: None,
11160 },
11161 ],
11162 cx,
11163 );
11164 multibuffer.push_excerpts(
11165 buffer_2.clone(),
11166 [
11167 ExcerptRange {
11168 context: Point::new(0, 0)..Point::new(3, 0),
11169 primary: None,
11170 },
11171 ExcerptRange {
11172 context: Point::new(5, 0)..Point::new(7, 0),
11173 primary: None,
11174 },
11175 ExcerptRange {
11176 context: Point::new(9, 0)..Point::new(10, 4),
11177 primary: None,
11178 },
11179 ],
11180 cx,
11181 );
11182 multibuffer.push_excerpts(
11183 buffer_3.clone(),
11184 [
11185 ExcerptRange {
11186 context: Point::new(0, 0)..Point::new(3, 0),
11187 primary: None,
11188 },
11189 ExcerptRange {
11190 context: Point::new(5, 0)..Point::new(7, 0),
11191 primary: None,
11192 },
11193 ExcerptRange {
11194 context: Point::new(9, 0)..Point::new(10, 4),
11195 primary: None,
11196 },
11197 ],
11198 cx,
11199 );
11200 multibuffer
11201 });
11202
11203 let fs = FakeFs::new(cx.executor());
11204 fs.insert_tree(
11205 "/a",
11206 json!({
11207 "main.rs": sample_text_1,
11208 "other.rs": sample_text_2,
11209 "lib.rs": sample_text_3,
11210 }),
11211 )
11212 .await;
11213 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11214 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11215 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11216 let multi_buffer_editor = cx.new_view(|cx| {
11217 Editor::new(
11218 EditorMode::Full,
11219 multi_buffer,
11220 Some(project.clone()),
11221 true,
11222 cx,
11223 )
11224 });
11225 let multibuffer_item_id = workspace
11226 .update(cx, |workspace, cx| {
11227 assert!(
11228 workspace.active_item(cx).is_none(),
11229 "active item should be None before the first item is added"
11230 );
11231 workspace.add_item_to_active_pane(
11232 Box::new(multi_buffer_editor.clone()),
11233 None,
11234 true,
11235 cx,
11236 );
11237 let active_item = workspace
11238 .active_item(cx)
11239 .expect("should have an active item after adding the multi buffer");
11240 assert!(
11241 !active_item.is_singleton(cx),
11242 "A multi buffer was expected to active after adding"
11243 );
11244 active_item.item_id()
11245 })
11246 .unwrap();
11247 cx.executor().run_until_parked();
11248
11249 multi_buffer_editor.update(cx, |editor, cx| {
11250 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11251 editor.open_excerpts(&OpenExcerpts, cx);
11252 });
11253 cx.executor().run_until_parked();
11254 let first_item_id = workspace
11255 .update(cx, |workspace, cx| {
11256 let active_item = workspace
11257 .active_item(cx)
11258 .expect("should have an active item after navigating into the 1st buffer");
11259 let first_item_id = active_item.item_id();
11260 assert_ne!(
11261 first_item_id, multibuffer_item_id,
11262 "Should navigate into the 1st buffer and activate it"
11263 );
11264 assert!(
11265 active_item.is_singleton(cx),
11266 "New active item should be a singleton buffer"
11267 );
11268 assert_eq!(
11269 active_item
11270 .act_as::<Editor>(cx)
11271 .expect("should have navigated into an editor for the 1st buffer")
11272 .read(cx)
11273 .text(cx),
11274 sample_text_1
11275 );
11276
11277 workspace
11278 .go_back(workspace.active_pane().downgrade(), cx)
11279 .detach_and_log_err(cx);
11280
11281 first_item_id
11282 })
11283 .unwrap();
11284 cx.executor().run_until_parked();
11285 workspace
11286 .update(cx, |workspace, cx| {
11287 let active_item = workspace
11288 .active_item(cx)
11289 .expect("should have an active item after navigating back");
11290 assert_eq!(
11291 active_item.item_id(),
11292 multibuffer_item_id,
11293 "Should navigate back to the multi buffer"
11294 );
11295 assert!(!active_item.is_singleton(cx));
11296 })
11297 .unwrap();
11298
11299 multi_buffer_editor.update(cx, |editor, cx| {
11300 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11301 s.select_ranges(Some(39..40))
11302 });
11303 editor.open_excerpts(&OpenExcerpts, cx);
11304 });
11305 cx.executor().run_until_parked();
11306 let second_item_id = workspace
11307 .update(cx, |workspace, cx| {
11308 let active_item = workspace
11309 .active_item(cx)
11310 .expect("should have an active item after navigating into the 2nd buffer");
11311 let second_item_id = active_item.item_id();
11312 assert_ne!(
11313 second_item_id, multibuffer_item_id,
11314 "Should navigate away from the multibuffer"
11315 );
11316 assert_ne!(
11317 second_item_id, first_item_id,
11318 "Should navigate into the 2nd buffer and activate it"
11319 );
11320 assert!(
11321 active_item.is_singleton(cx),
11322 "New active item should be a singleton buffer"
11323 );
11324 assert_eq!(
11325 active_item
11326 .act_as::<Editor>(cx)
11327 .expect("should have navigated into an editor")
11328 .read(cx)
11329 .text(cx),
11330 sample_text_2
11331 );
11332
11333 workspace
11334 .go_back(workspace.active_pane().downgrade(), cx)
11335 .detach_and_log_err(cx);
11336
11337 second_item_id
11338 })
11339 .unwrap();
11340 cx.executor().run_until_parked();
11341 workspace
11342 .update(cx, |workspace, cx| {
11343 let active_item = workspace
11344 .active_item(cx)
11345 .expect("should have an active item after navigating back from the 2nd buffer");
11346 assert_eq!(
11347 active_item.item_id(),
11348 multibuffer_item_id,
11349 "Should navigate back from the 2nd buffer to the multi buffer"
11350 );
11351 assert!(!active_item.is_singleton(cx));
11352 })
11353 .unwrap();
11354
11355 multi_buffer_editor.update(cx, |editor, cx| {
11356 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11357 s.select_ranges(Some(60..70))
11358 });
11359 editor.open_excerpts(&OpenExcerpts, cx);
11360 });
11361 cx.executor().run_until_parked();
11362 workspace
11363 .update(cx, |workspace, cx| {
11364 let active_item = workspace
11365 .active_item(cx)
11366 .expect("should have an active item after navigating into the 3rd buffer");
11367 let third_item_id = active_item.item_id();
11368 assert_ne!(
11369 third_item_id, multibuffer_item_id,
11370 "Should navigate into the 3rd buffer and activate it"
11371 );
11372 assert_ne!(third_item_id, first_item_id);
11373 assert_ne!(third_item_id, second_item_id);
11374 assert!(
11375 active_item.is_singleton(cx),
11376 "New active item should be a singleton buffer"
11377 );
11378 assert_eq!(
11379 active_item
11380 .act_as::<Editor>(cx)
11381 .expect("should have navigated into an editor")
11382 .read(cx)
11383 .text(cx),
11384 sample_text_3
11385 );
11386
11387 workspace
11388 .go_back(workspace.active_pane().downgrade(), cx)
11389 .detach_and_log_err(cx);
11390 })
11391 .unwrap();
11392 cx.executor().run_until_parked();
11393 workspace
11394 .update(cx, |workspace, cx| {
11395 let active_item = workspace
11396 .active_item(cx)
11397 .expect("should have an active item after navigating back from the 3rd buffer");
11398 assert_eq!(
11399 active_item.item_id(),
11400 multibuffer_item_id,
11401 "Should navigate back from the 3rd buffer to the multi buffer"
11402 );
11403 assert!(!active_item.is_singleton(cx));
11404 })
11405 .unwrap();
11406}
11407
11408#[gpui::test]
11409async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11410 init_test(cx, |_| {});
11411
11412 let mut cx = EditorTestContext::new(cx).await;
11413
11414 let diff_base = r#"
11415 use some::mod;
11416
11417 const A: u32 = 42;
11418
11419 fn main() {
11420 println!("hello");
11421
11422 println!("world");
11423 }
11424 "#
11425 .unindent();
11426
11427 cx.set_state(
11428 &r#"
11429 use some::modified;
11430
11431 ˇ
11432 fn main() {
11433 println!("hello there");
11434
11435 println!("around the");
11436 println!("world");
11437 }
11438 "#
11439 .unindent(),
11440 );
11441
11442 cx.set_diff_base(Some(&diff_base));
11443 executor.run_until_parked();
11444
11445 cx.update_editor(|editor, cx| {
11446 editor.go_to_next_hunk(&GoToHunk, cx);
11447 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11448 });
11449 executor.run_until_parked();
11450 cx.assert_diff_hunks(
11451 r#"
11452 use some::modified;
11453
11454
11455 fn main() {
11456 - println!("hello");
11457 + println!("hello there");
11458
11459 println!("around the");
11460 println!("world");
11461 }
11462 "#
11463 .unindent(),
11464 );
11465
11466 cx.update_editor(|editor, cx| {
11467 for _ in 0..3 {
11468 editor.go_to_next_hunk(&GoToHunk, cx);
11469 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11470 }
11471 });
11472 executor.run_until_parked();
11473 cx.assert_editor_state(
11474 &r#"
11475 use some::modified;
11476
11477 ˇ
11478 fn main() {
11479 println!("hello there");
11480
11481 println!("around the");
11482 println!("world");
11483 }
11484 "#
11485 .unindent(),
11486 );
11487
11488 cx.assert_diff_hunks(
11489 r#"
11490 - use some::mod;
11491 + use some::modified;
11492
11493 - const A: u32 = 42;
11494
11495 fn main() {
11496 - println!("hello");
11497 + println!("hello there");
11498
11499 + println!("around the");
11500 println!("world");
11501 }
11502 "#
11503 .unindent(),
11504 );
11505
11506 cx.update_editor(|editor, cx| {
11507 editor.cancel(&Cancel, cx);
11508 });
11509
11510 cx.assert_diff_hunks(
11511 r#"
11512 use some::modified;
11513
11514
11515 fn main() {
11516 println!("hello there");
11517
11518 println!("around the");
11519 println!("world");
11520 }
11521 "#
11522 .unindent(),
11523 );
11524}
11525
11526#[gpui::test]
11527async fn test_diff_base_change_with_expanded_diff_hunks(
11528 executor: BackgroundExecutor,
11529 cx: &mut gpui::TestAppContext,
11530) {
11531 init_test(cx, |_| {});
11532
11533 let mut cx = EditorTestContext::new(cx).await;
11534
11535 let diff_base = r#"
11536 use some::mod1;
11537 use some::mod2;
11538
11539 const A: u32 = 42;
11540 const B: u32 = 42;
11541 const C: u32 = 42;
11542
11543 fn main() {
11544 println!("hello");
11545
11546 println!("world");
11547 }
11548 "#
11549 .unindent();
11550
11551 cx.set_state(
11552 &r#"
11553 use some::mod2;
11554
11555 const A: u32 = 42;
11556 const C: u32 = 42;
11557
11558 fn main(ˇ) {
11559 //println!("hello");
11560
11561 println!("world");
11562 //
11563 //
11564 }
11565 "#
11566 .unindent(),
11567 );
11568
11569 cx.set_diff_base(Some(&diff_base));
11570 executor.run_until_parked();
11571
11572 cx.update_editor(|editor, cx| {
11573 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11574 });
11575 executor.run_until_parked();
11576 cx.assert_diff_hunks(
11577 r#"
11578 - use some::mod1;
11579 use some::mod2;
11580
11581 const A: u32 = 42;
11582 - const B: u32 = 42;
11583 const C: u32 = 42;
11584
11585 fn main() {
11586 - println!("hello");
11587 + //println!("hello");
11588
11589 println!("world");
11590 + //
11591 + //
11592 }
11593 "#
11594 .unindent(),
11595 );
11596
11597 cx.set_diff_base(Some("new diff base!"));
11598 executor.run_until_parked();
11599 cx.assert_diff_hunks(
11600 r#"
11601 use some::mod2;
11602
11603 const A: u32 = 42;
11604 const C: u32 = 42;
11605
11606 fn main() {
11607 //println!("hello");
11608
11609 println!("world");
11610 //
11611 //
11612 }
11613 "#
11614 .unindent(),
11615 );
11616
11617 cx.update_editor(|editor, cx| {
11618 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11619 });
11620 executor.run_until_parked();
11621 cx.assert_diff_hunks(
11622 r#"
11623 - new diff base!
11624 + use some::mod2;
11625 +
11626 + const A: u32 = 42;
11627 + const C: u32 = 42;
11628 +
11629 + fn main() {
11630 + //println!("hello");
11631 +
11632 + println!("world");
11633 + //
11634 + //
11635 + }
11636 "#
11637 .unindent(),
11638 );
11639}
11640
11641#[gpui::test]
11642async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11643 init_test(cx, |_| {});
11644
11645 let mut cx = EditorTestContext::new(cx).await;
11646
11647 let diff_base = r#"
11648 use some::mod1;
11649 use some::mod2;
11650
11651 const A: u32 = 42;
11652 const B: u32 = 42;
11653 const C: u32 = 42;
11654
11655 fn main() {
11656 println!("hello");
11657
11658 println!("world");
11659 }
11660
11661 fn another() {
11662 println!("another");
11663 }
11664
11665 fn another2() {
11666 println!("another2");
11667 }
11668 "#
11669 .unindent();
11670
11671 cx.set_state(
11672 &r#"
11673 «use some::mod2;
11674
11675 const A: u32 = 42;
11676 const C: u32 = 42;
11677
11678 fn main() {
11679 //println!("hello");
11680
11681 println!("world");
11682 //
11683 //ˇ»
11684 }
11685
11686 fn another() {
11687 println!("another");
11688 println!("another");
11689 }
11690
11691 println!("another2");
11692 }
11693 "#
11694 .unindent(),
11695 );
11696
11697 cx.set_diff_base(Some(&diff_base));
11698 executor.run_until_parked();
11699
11700 cx.update_editor(|editor, cx| {
11701 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11702 });
11703 executor.run_until_parked();
11704
11705 cx.assert_diff_hunks(
11706 r#"
11707 - use some::mod1;
11708 use some::mod2;
11709
11710 const A: u32 = 42;
11711 - const B: u32 = 42;
11712 const C: u32 = 42;
11713
11714 fn main() {
11715 - println!("hello");
11716 + //println!("hello");
11717
11718 println!("world");
11719 + //
11720 + //
11721 }
11722
11723 fn another() {
11724 println!("another");
11725 + println!("another");
11726 }
11727
11728 - fn another2() {
11729 println!("another2");
11730 }
11731 "#
11732 .unindent(),
11733 );
11734
11735 // Fold across some of the diff hunks. They should no longer appear expanded.
11736 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11737 cx.executor().run_until_parked();
11738
11739 // Hunks are not shown if their position is within a fold
11740 cx.assert_diff_hunks(
11741 r#"
11742 use some::mod2;
11743
11744 const A: u32 = 42;
11745 const C: u32 = 42;
11746
11747 fn main() {
11748 //println!("hello");
11749
11750 println!("world");
11751 //
11752 //
11753 }
11754
11755 fn another() {
11756 println!("another");
11757 + println!("another");
11758 }
11759
11760 - fn another2() {
11761 println!("another2");
11762 }
11763 "#
11764 .unindent(),
11765 );
11766
11767 cx.update_editor(|editor, cx| {
11768 editor.select_all(&SelectAll, cx);
11769 editor.unfold_lines(&UnfoldLines, cx);
11770 });
11771 cx.executor().run_until_parked();
11772
11773 // The deletions reappear when unfolding.
11774 cx.assert_diff_hunks(
11775 r#"
11776 - use some::mod1;
11777 use some::mod2;
11778
11779 const A: u32 = 42;
11780 - const B: u32 = 42;
11781 const C: u32 = 42;
11782
11783 fn main() {
11784 - println!("hello");
11785 + //println!("hello");
11786
11787 println!("world");
11788 + //
11789 + //
11790 }
11791
11792 fn another() {
11793 println!("another");
11794 + println!("another");
11795 }
11796
11797 - fn another2() {
11798 println!("another2");
11799 }
11800 "#
11801 .unindent(),
11802 );
11803}
11804
11805#[gpui::test]
11806async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11807 init_test(cx, |_| {});
11808
11809 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11810 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11811 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11812 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11813 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11814 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11815
11816 let buffer_1 = cx.new_model(|cx| {
11817 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11818 buffer.set_diff_base(Some(file_1_old.into()), cx);
11819 buffer
11820 });
11821 let buffer_2 = cx.new_model(|cx| {
11822 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11823 buffer.set_diff_base(Some(file_2_old.into()), cx);
11824 buffer
11825 });
11826 let buffer_3 = cx.new_model(|cx| {
11827 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11828 buffer.set_diff_base(Some(file_3_old.into()), cx);
11829 buffer
11830 });
11831
11832 let multi_buffer = cx.new_model(|cx| {
11833 let mut multibuffer = MultiBuffer::new(ReadWrite);
11834 multibuffer.push_excerpts(
11835 buffer_1.clone(),
11836 [
11837 ExcerptRange {
11838 context: Point::new(0, 0)..Point::new(3, 0),
11839 primary: None,
11840 },
11841 ExcerptRange {
11842 context: Point::new(5, 0)..Point::new(7, 0),
11843 primary: None,
11844 },
11845 ExcerptRange {
11846 context: Point::new(9, 0)..Point::new(10, 3),
11847 primary: None,
11848 },
11849 ],
11850 cx,
11851 );
11852 multibuffer.push_excerpts(
11853 buffer_2.clone(),
11854 [
11855 ExcerptRange {
11856 context: Point::new(0, 0)..Point::new(3, 0),
11857 primary: None,
11858 },
11859 ExcerptRange {
11860 context: Point::new(5, 0)..Point::new(7, 0),
11861 primary: None,
11862 },
11863 ExcerptRange {
11864 context: Point::new(9, 0)..Point::new(10, 3),
11865 primary: None,
11866 },
11867 ],
11868 cx,
11869 );
11870 multibuffer.push_excerpts(
11871 buffer_3.clone(),
11872 [
11873 ExcerptRange {
11874 context: Point::new(0, 0)..Point::new(3, 0),
11875 primary: None,
11876 },
11877 ExcerptRange {
11878 context: Point::new(5, 0)..Point::new(7, 0),
11879 primary: None,
11880 },
11881 ExcerptRange {
11882 context: Point::new(9, 0)..Point::new(10, 3),
11883 primary: None,
11884 },
11885 ],
11886 cx,
11887 );
11888 multibuffer
11889 });
11890
11891 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11892 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11893 cx.run_until_parked();
11894
11895 cx.assert_editor_state(
11896 &"
11897 ˇaaa
11898 ccc
11899 ddd
11900
11901 ggg
11902 hhh
11903
11904
11905 lll
11906 mmm
11907 NNN
11908
11909 qqq
11910 rrr
11911
11912 uuu
11913 111
11914 222
11915 333
11916
11917 666
11918 777
11919
11920 000
11921 !!!"
11922 .unindent(),
11923 );
11924
11925 cx.update_editor(|editor, cx| {
11926 editor.select_all(&SelectAll, cx);
11927 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11928 });
11929 cx.executor().run_until_parked();
11930
11931 cx.assert_diff_hunks(
11932 "
11933 aaa
11934 - bbb
11935 ccc
11936 ddd
11937
11938 ggg
11939 hhh
11940
11941
11942 lll
11943 mmm
11944 - nnn
11945 + NNN
11946
11947 qqq
11948 rrr
11949
11950 uuu
11951 111
11952 222
11953 333
11954
11955 + 666
11956 777
11957
11958 000
11959 !!!"
11960 .unindent(),
11961 );
11962}
11963
11964#[gpui::test]
11965async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
11966 init_test(cx, |_| {});
11967
11968 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
11969 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
11970
11971 let buffer = cx.new_model(|cx| {
11972 let mut buffer = Buffer::local(text.to_string(), cx);
11973 buffer.set_diff_base(Some(base.into()), cx);
11974 buffer
11975 });
11976
11977 let multi_buffer = cx.new_model(|cx| {
11978 let mut multibuffer = MultiBuffer::new(ReadWrite);
11979 multibuffer.push_excerpts(
11980 buffer.clone(),
11981 [
11982 ExcerptRange {
11983 context: Point::new(0, 0)..Point::new(2, 0),
11984 primary: None,
11985 },
11986 ExcerptRange {
11987 context: Point::new(5, 0)..Point::new(7, 0),
11988 primary: None,
11989 },
11990 ],
11991 cx,
11992 );
11993 multibuffer
11994 });
11995
11996 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11997 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11998 cx.run_until_parked();
11999
12000 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12001 cx.executor().run_until_parked();
12002
12003 cx.assert_diff_hunks(
12004 "
12005 aaa
12006 - bbb
12007 + BBB
12008
12009 - ddd
12010 - eee
12011 + EEE
12012 fff
12013 "
12014 .unindent(),
12015 );
12016}
12017
12018#[gpui::test]
12019async fn test_edits_around_expanded_insertion_hunks(
12020 executor: BackgroundExecutor,
12021 cx: &mut gpui::TestAppContext,
12022) {
12023 init_test(cx, |_| {});
12024
12025 let mut cx = EditorTestContext::new(cx).await;
12026
12027 let diff_base = r#"
12028 use some::mod1;
12029 use some::mod2;
12030
12031 const A: u32 = 42;
12032
12033 fn main() {
12034 println!("hello");
12035
12036 println!("world");
12037 }
12038 "#
12039 .unindent();
12040 executor.run_until_parked();
12041 cx.set_state(
12042 &r#"
12043 use some::mod1;
12044 use some::mod2;
12045
12046 const A: u32 = 42;
12047 const B: u32 = 42;
12048 const C: u32 = 42;
12049 ˇ
12050
12051 fn main() {
12052 println!("hello");
12053
12054 println!("world");
12055 }
12056 "#
12057 .unindent(),
12058 );
12059
12060 cx.set_diff_base(Some(&diff_base));
12061 executor.run_until_parked();
12062
12063 cx.update_editor(|editor, cx| {
12064 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12065 });
12066 executor.run_until_parked();
12067
12068 cx.assert_diff_hunks(
12069 r#"
12070 use some::mod1;
12071 use some::mod2;
12072
12073 const A: u32 = 42;
12074 + const B: u32 = 42;
12075 + const C: u32 = 42;
12076 +
12077
12078 fn main() {
12079 println!("hello");
12080
12081 println!("world");
12082 }
12083 "#
12084 .unindent(),
12085 );
12086
12087 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12088 executor.run_until_parked();
12089
12090 cx.assert_diff_hunks(
12091 r#"
12092 use some::mod1;
12093 use some::mod2;
12094
12095 const A: u32 = 42;
12096 + const B: u32 = 42;
12097 + const C: u32 = 42;
12098 + const D: u32 = 42;
12099 +
12100
12101 fn main() {
12102 println!("hello");
12103
12104 println!("world");
12105 }
12106 "#
12107 .unindent(),
12108 );
12109
12110 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12111 executor.run_until_parked();
12112
12113 cx.assert_diff_hunks(
12114 r#"
12115 use some::mod1;
12116 use some::mod2;
12117
12118 const A: u32 = 42;
12119 + const B: u32 = 42;
12120 + const C: u32 = 42;
12121 + const D: u32 = 42;
12122 + const E: u32 = 42;
12123 +
12124
12125 fn main() {
12126 println!("hello");
12127
12128 println!("world");
12129 }
12130 "#
12131 .unindent(),
12132 );
12133
12134 cx.update_editor(|editor, cx| {
12135 editor.delete_line(&DeleteLine, cx);
12136 });
12137 executor.run_until_parked();
12138
12139 cx.assert_diff_hunks(
12140 r#"
12141 use some::mod1;
12142 use some::mod2;
12143
12144 const A: u32 = 42;
12145 + const B: u32 = 42;
12146 + const C: u32 = 42;
12147 + const D: u32 = 42;
12148 + const E: u32 = 42;
12149
12150 fn main() {
12151 println!("hello");
12152
12153 println!("world");
12154 }
12155 "#
12156 .unindent(),
12157 );
12158
12159 cx.update_editor(|editor, cx| {
12160 editor.move_up(&MoveUp, cx);
12161 editor.delete_line(&DeleteLine, cx);
12162 editor.move_up(&MoveUp, cx);
12163 editor.delete_line(&DeleteLine, cx);
12164 editor.move_up(&MoveUp, cx);
12165 editor.delete_line(&DeleteLine, cx);
12166 });
12167 executor.run_until_parked();
12168 cx.assert_editor_state(
12169 &r#"
12170 use some::mod1;
12171 use some::mod2;
12172
12173 const A: u32 = 42;
12174 const B: u32 = 42;
12175 ˇ
12176 fn main() {
12177 println!("hello");
12178
12179 println!("world");
12180 }
12181 "#
12182 .unindent(),
12183 );
12184
12185 cx.assert_diff_hunks(
12186 r#"
12187 use some::mod1;
12188 use some::mod2;
12189
12190 const A: u32 = 42;
12191 + const B: u32 = 42;
12192
12193 fn main() {
12194 println!("hello");
12195
12196 println!("world");
12197 }
12198 "#
12199 .unindent(),
12200 );
12201
12202 cx.update_editor(|editor, cx| {
12203 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12204 editor.delete_line(&DeleteLine, cx);
12205 });
12206 executor.run_until_parked();
12207 cx.assert_diff_hunks(
12208 r#"
12209 use some::mod1;
12210 - use some::mod2;
12211 -
12212 - const A: u32 = 42;
12213
12214 fn main() {
12215 println!("hello");
12216
12217 println!("world");
12218 }
12219 "#
12220 .unindent(),
12221 );
12222}
12223
12224#[gpui::test]
12225async fn test_edits_around_expanded_deletion_hunks(
12226 executor: BackgroundExecutor,
12227 cx: &mut gpui::TestAppContext,
12228) {
12229 init_test(cx, |_| {});
12230
12231 let mut cx = EditorTestContext::new(cx).await;
12232
12233 let diff_base = r#"
12234 use some::mod1;
12235 use some::mod2;
12236
12237 const A: u32 = 42;
12238 const B: u32 = 42;
12239 const C: u32 = 42;
12240
12241
12242 fn main() {
12243 println!("hello");
12244
12245 println!("world");
12246 }
12247 "#
12248 .unindent();
12249 executor.run_until_parked();
12250 cx.set_state(
12251 &r#"
12252 use some::mod1;
12253 use some::mod2;
12254
12255 ˇconst B: u32 = 42;
12256 const C: u32 = 42;
12257
12258
12259 fn main() {
12260 println!("hello");
12261
12262 println!("world");
12263 }
12264 "#
12265 .unindent(),
12266 );
12267
12268 cx.set_diff_base(Some(&diff_base));
12269 executor.run_until_parked();
12270
12271 cx.update_editor(|editor, cx| {
12272 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12273 });
12274 executor.run_until_parked();
12275
12276 cx.assert_diff_hunks(
12277 r#"
12278 use some::mod1;
12279 use some::mod2;
12280
12281 - const A: u32 = 42;
12282 const B: u32 = 42;
12283 const C: u32 = 42;
12284
12285
12286 fn main() {
12287 println!("hello");
12288
12289 println!("world");
12290 }
12291 "#
12292 .unindent(),
12293 );
12294
12295 cx.update_editor(|editor, cx| {
12296 editor.delete_line(&DeleteLine, cx);
12297 });
12298 executor.run_until_parked();
12299 cx.assert_editor_state(
12300 &r#"
12301 use some::mod1;
12302 use some::mod2;
12303
12304 ˇconst C: u32 = 42;
12305
12306
12307 fn main() {
12308 println!("hello");
12309
12310 println!("world");
12311 }
12312 "#
12313 .unindent(),
12314 );
12315 cx.assert_diff_hunks(
12316 r#"
12317 use some::mod1;
12318 use some::mod2;
12319
12320 - const A: u32 = 42;
12321 - const B: u32 = 42;
12322 const C: u32 = 42;
12323
12324
12325 fn main() {
12326 println!("hello");
12327
12328 println!("world");
12329 }
12330 "#
12331 .unindent(),
12332 );
12333
12334 cx.update_editor(|editor, cx| {
12335 editor.delete_line(&DeleteLine, cx);
12336 });
12337 executor.run_until_parked();
12338 cx.assert_editor_state(
12339 &r#"
12340 use some::mod1;
12341 use some::mod2;
12342
12343 ˇ
12344
12345 fn main() {
12346 println!("hello");
12347
12348 println!("world");
12349 }
12350 "#
12351 .unindent(),
12352 );
12353 cx.assert_diff_hunks(
12354 r#"
12355 use some::mod1;
12356 use some::mod2;
12357
12358 - const A: u32 = 42;
12359 - const B: u32 = 42;
12360 - const C: u32 = 42;
12361
12362
12363 fn main() {
12364 println!("hello");
12365
12366 println!("world");
12367 }
12368 "#
12369 .unindent(),
12370 );
12371
12372 cx.update_editor(|editor, cx| {
12373 editor.handle_input("replacement", cx);
12374 });
12375 executor.run_until_parked();
12376 cx.assert_editor_state(
12377 &r#"
12378 use some::mod1;
12379 use some::mod2;
12380
12381 replacementˇ
12382
12383 fn main() {
12384 println!("hello");
12385
12386 println!("world");
12387 }
12388 "#
12389 .unindent(),
12390 );
12391 cx.assert_diff_hunks(
12392 r#"
12393 use some::mod1;
12394 use some::mod2;
12395
12396 - const A: u32 = 42;
12397 - const B: u32 = 42;
12398 - const C: u32 = 42;
12399 -
12400 + replacement
12401
12402 fn main() {
12403 println!("hello");
12404
12405 println!("world");
12406 }
12407 "#
12408 .unindent(),
12409 );
12410}
12411
12412#[gpui::test]
12413async fn test_edit_after_expanded_modification_hunk(
12414 executor: BackgroundExecutor,
12415 cx: &mut gpui::TestAppContext,
12416) {
12417 init_test(cx, |_| {});
12418
12419 let mut cx = EditorTestContext::new(cx).await;
12420
12421 let diff_base = r#"
12422 use some::mod1;
12423 use some::mod2;
12424
12425 const A: u32 = 42;
12426 const B: u32 = 42;
12427 const C: u32 = 42;
12428 const D: u32 = 42;
12429
12430
12431 fn main() {
12432 println!("hello");
12433
12434 println!("world");
12435 }"#
12436 .unindent();
12437
12438 cx.set_state(
12439 &r#"
12440 use some::mod1;
12441 use some::mod2;
12442
12443 const A: u32 = 42;
12444 const B: u32 = 42;
12445 const C: u32 = 43ˇ
12446 const D: u32 = 42;
12447
12448
12449 fn main() {
12450 println!("hello");
12451
12452 println!("world");
12453 }"#
12454 .unindent(),
12455 );
12456
12457 cx.set_diff_base(Some(&diff_base));
12458 executor.run_until_parked();
12459 cx.update_editor(|editor, cx| {
12460 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12461 });
12462 executor.run_until_parked();
12463
12464 cx.assert_diff_hunks(
12465 r#"
12466 use some::mod1;
12467 use some::mod2;
12468
12469 const A: u32 = 42;
12470 const B: u32 = 42;
12471 - const C: u32 = 42;
12472 + const C: u32 = 43
12473 const D: u32 = 42;
12474
12475
12476 fn main() {
12477 println!("hello");
12478
12479 println!("world");
12480 }"#
12481 .unindent(),
12482 );
12483
12484 cx.update_editor(|editor, cx| {
12485 editor.handle_input("\nnew_line\n", cx);
12486 });
12487 executor.run_until_parked();
12488
12489 cx.assert_diff_hunks(
12490 r#"
12491 use some::mod1;
12492 use some::mod2;
12493
12494 const A: u32 = 42;
12495 const B: u32 = 42;
12496 - const C: u32 = 42;
12497 + const C: u32 = 43
12498 + new_line
12499 +
12500 const D: u32 = 42;
12501
12502
12503 fn main() {
12504 println!("hello");
12505
12506 println!("world");
12507 }"#
12508 .unindent(),
12509 );
12510}
12511
12512async fn setup_indent_guides_editor(
12513 text: &str,
12514 cx: &mut gpui::TestAppContext,
12515) -> (BufferId, EditorTestContext) {
12516 init_test(cx, |_| {});
12517
12518 let mut cx = EditorTestContext::new(cx).await;
12519
12520 let buffer_id = cx.update_editor(|editor, cx| {
12521 editor.set_text(text, cx);
12522 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12523
12524 buffer_ids[0]
12525 });
12526
12527 (buffer_id, cx)
12528}
12529
12530fn assert_indent_guides(
12531 range: Range<u32>,
12532 expected: Vec<IndentGuide>,
12533 active_indices: Option<Vec<usize>>,
12534 cx: &mut EditorTestContext,
12535) {
12536 let indent_guides = cx.update_editor(|editor, cx| {
12537 let snapshot = editor.snapshot(cx).display_snapshot;
12538 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12539 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12540 true,
12541 &snapshot,
12542 cx,
12543 );
12544
12545 indent_guides.sort_by(|a, b| {
12546 a.depth.cmp(&b.depth).then(
12547 a.start_row
12548 .cmp(&b.start_row)
12549 .then(a.end_row.cmp(&b.end_row)),
12550 )
12551 });
12552 indent_guides
12553 });
12554
12555 if let Some(expected) = active_indices {
12556 let active_indices = cx.update_editor(|editor, cx| {
12557 let snapshot = editor.snapshot(cx).display_snapshot;
12558 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12559 });
12560
12561 assert_eq!(
12562 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12563 expected,
12564 "Active indent guide indices do not match"
12565 );
12566 }
12567
12568 let expected: Vec<_> = expected
12569 .into_iter()
12570 .map(|guide| MultiBufferIndentGuide {
12571 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12572 buffer: guide,
12573 })
12574 .collect();
12575
12576 assert_eq!(indent_guides, expected, "Indent guides do not match");
12577}
12578
12579fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12580 IndentGuide {
12581 buffer_id,
12582 start_row,
12583 end_row,
12584 depth,
12585 tab_size: 4,
12586 settings: IndentGuideSettings {
12587 enabled: true,
12588 line_width: 1,
12589 active_line_width: 1,
12590 ..Default::default()
12591 },
12592 }
12593}
12594
12595#[gpui::test]
12596async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12597 let (buffer_id, mut cx) = setup_indent_guides_editor(
12598 &"
12599 fn main() {
12600 let a = 1;
12601 }"
12602 .unindent(),
12603 cx,
12604 )
12605 .await;
12606
12607 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12608}
12609
12610#[gpui::test]
12611async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12612 let (buffer_id, mut cx) = setup_indent_guides_editor(
12613 &"
12614 fn main() {
12615 let a = 1;
12616 let b = 2;
12617 }"
12618 .unindent(),
12619 cx,
12620 )
12621 .await;
12622
12623 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12624}
12625
12626#[gpui::test]
12627async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12628 let (buffer_id, mut cx) = setup_indent_guides_editor(
12629 &"
12630 fn main() {
12631 let a = 1;
12632 if a == 3 {
12633 let b = 2;
12634 } else {
12635 let c = 3;
12636 }
12637 }"
12638 .unindent(),
12639 cx,
12640 )
12641 .await;
12642
12643 assert_indent_guides(
12644 0..8,
12645 vec![
12646 indent_guide(buffer_id, 1, 6, 0),
12647 indent_guide(buffer_id, 3, 3, 1),
12648 indent_guide(buffer_id, 5, 5, 1),
12649 ],
12650 None,
12651 &mut cx,
12652 );
12653}
12654
12655#[gpui::test]
12656async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12657 let (buffer_id, mut cx) = setup_indent_guides_editor(
12658 &"
12659 fn main() {
12660 let a = 1;
12661 let b = 2;
12662 let c = 3;
12663 }"
12664 .unindent(),
12665 cx,
12666 )
12667 .await;
12668
12669 assert_indent_guides(
12670 0..5,
12671 vec![
12672 indent_guide(buffer_id, 1, 3, 0),
12673 indent_guide(buffer_id, 2, 2, 1),
12674 ],
12675 None,
12676 &mut cx,
12677 );
12678}
12679
12680#[gpui::test]
12681async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12682 let (buffer_id, mut cx) = setup_indent_guides_editor(
12683 &"
12684 fn main() {
12685 let a = 1;
12686
12687 let c = 3;
12688 }"
12689 .unindent(),
12690 cx,
12691 )
12692 .await;
12693
12694 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12695}
12696
12697#[gpui::test]
12698async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12699 let (buffer_id, mut cx) = setup_indent_guides_editor(
12700 &"
12701 fn main() {
12702 let a = 1;
12703
12704 let c = 3;
12705
12706 if a == 3 {
12707 let b = 2;
12708 } else {
12709 let c = 3;
12710 }
12711 }"
12712 .unindent(),
12713 cx,
12714 )
12715 .await;
12716
12717 assert_indent_guides(
12718 0..11,
12719 vec![
12720 indent_guide(buffer_id, 1, 9, 0),
12721 indent_guide(buffer_id, 6, 6, 1),
12722 indent_guide(buffer_id, 8, 8, 1),
12723 ],
12724 None,
12725 &mut cx,
12726 );
12727}
12728
12729#[gpui::test]
12730async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12731 let (buffer_id, mut cx) = setup_indent_guides_editor(
12732 &"
12733 fn main() {
12734 let a = 1;
12735
12736 let c = 3;
12737
12738 if a == 3 {
12739 let b = 2;
12740 } else {
12741 let c = 3;
12742 }
12743 }"
12744 .unindent(),
12745 cx,
12746 )
12747 .await;
12748
12749 assert_indent_guides(
12750 1..11,
12751 vec![
12752 indent_guide(buffer_id, 1, 9, 0),
12753 indent_guide(buffer_id, 6, 6, 1),
12754 indent_guide(buffer_id, 8, 8, 1),
12755 ],
12756 None,
12757 &mut cx,
12758 );
12759}
12760
12761#[gpui::test]
12762async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12763 let (buffer_id, mut cx) = setup_indent_guides_editor(
12764 &"
12765 fn main() {
12766 let a = 1;
12767
12768 let c = 3;
12769
12770 if a == 3 {
12771 let b = 2;
12772 } else {
12773 let c = 3;
12774 }
12775 }"
12776 .unindent(),
12777 cx,
12778 )
12779 .await;
12780
12781 assert_indent_guides(
12782 1..10,
12783 vec![
12784 indent_guide(buffer_id, 1, 9, 0),
12785 indent_guide(buffer_id, 6, 6, 1),
12786 indent_guide(buffer_id, 8, 8, 1),
12787 ],
12788 None,
12789 &mut cx,
12790 );
12791}
12792
12793#[gpui::test]
12794async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12795 let (buffer_id, mut cx) = setup_indent_guides_editor(
12796 &"
12797 block1
12798 block2
12799 block3
12800 block4
12801 block2
12802 block1
12803 block1"
12804 .unindent(),
12805 cx,
12806 )
12807 .await;
12808
12809 assert_indent_guides(
12810 1..10,
12811 vec![
12812 indent_guide(buffer_id, 1, 4, 0),
12813 indent_guide(buffer_id, 2, 3, 1),
12814 indent_guide(buffer_id, 3, 3, 2),
12815 ],
12816 None,
12817 &mut cx,
12818 );
12819}
12820
12821#[gpui::test]
12822async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12823 let (buffer_id, mut cx) = setup_indent_guides_editor(
12824 &"
12825 block1
12826 block2
12827 block3
12828
12829 block1
12830 block1"
12831 .unindent(),
12832 cx,
12833 )
12834 .await;
12835
12836 assert_indent_guides(
12837 0..6,
12838 vec![
12839 indent_guide(buffer_id, 1, 2, 0),
12840 indent_guide(buffer_id, 2, 2, 1),
12841 ],
12842 None,
12843 &mut cx,
12844 );
12845}
12846
12847#[gpui::test]
12848async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12849 let (buffer_id, mut cx) = setup_indent_guides_editor(
12850 &"
12851 block1
12852
12853
12854
12855 block2
12856 "
12857 .unindent(),
12858 cx,
12859 )
12860 .await;
12861
12862 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12863}
12864
12865#[gpui::test]
12866async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12867 let (buffer_id, mut cx) = setup_indent_guides_editor(
12868 &"
12869 def a:
12870 \tb = 3
12871 \tif True:
12872 \t\tc = 4
12873 \t\td = 5
12874 \tprint(b)
12875 "
12876 .unindent(),
12877 cx,
12878 )
12879 .await;
12880
12881 assert_indent_guides(
12882 0..6,
12883 vec![
12884 indent_guide(buffer_id, 1, 6, 0),
12885 indent_guide(buffer_id, 3, 4, 1),
12886 ],
12887 None,
12888 &mut cx,
12889 );
12890}
12891
12892#[gpui::test]
12893async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12894 let (buffer_id, mut cx) = setup_indent_guides_editor(
12895 &"
12896 fn main() {
12897 let a = 1;
12898 }"
12899 .unindent(),
12900 cx,
12901 )
12902 .await;
12903
12904 cx.update_editor(|editor, cx| {
12905 editor.change_selections(None, cx, |s| {
12906 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12907 });
12908 });
12909
12910 assert_indent_guides(
12911 0..3,
12912 vec![indent_guide(buffer_id, 1, 1, 0)],
12913 Some(vec![0]),
12914 &mut cx,
12915 );
12916}
12917
12918#[gpui::test]
12919async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12920 let (buffer_id, mut cx) = setup_indent_guides_editor(
12921 &"
12922 fn main() {
12923 if 1 == 2 {
12924 let a = 1;
12925 }
12926 }"
12927 .unindent(),
12928 cx,
12929 )
12930 .await;
12931
12932 cx.update_editor(|editor, cx| {
12933 editor.change_selections(None, cx, |s| {
12934 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12935 });
12936 });
12937
12938 assert_indent_guides(
12939 0..4,
12940 vec![
12941 indent_guide(buffer_id, 1, 3, 0),
12942 indent_guide(buffer_id, 2, 2, 1),
12943 ],
12944 Some(vec![1]),
12945 &mut cx,
12946 );
12947
12948 cx.update_editor(|editor, cx| {
12949 editor.change_selections(None, cx, |s| {
12950 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12951 });
12952 });
12953
12954 assert_indent_guides(
12955 0..4,
12956 vec![
12957 indent_guide(buffer_id, 1, 3, 0),
12958 indent_guide(buffer_id, 2, 2, 1),
12959 ],
12960 Some(vec![1]),
12961 &mut cx,
12962 );
12963
12964 cx.update_editor(|editor, cx| {
12965 editor.change_selections(None, cx, |s| {
12966 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12967 });
12968 });
12969
12970 assert_indent_guides(
12971 0..4,
12972 vec![
12973 indent_guide(buffer_id, 1, 3, 0),
12974 indent_guide(buffer_id, 2, 2, 1),
12975 ],
12976 Some(vec![0]),
12977 &mut cx,
12978 );
12979}
12980
12981#[gpui::test]
12982async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12983 let (buffer_id, mut cx) = setup_indent_guides_editor(
12984 &"
12985 fn main() {
12986 let a = 1;
12987
12988 let b = 2;
12989 }"
12990 .unindent(),
12991 cx,
12992 )
12993 .await;
12994
12995 cx.update_editor(|editor, cx| {
12996 editor.change_selections(None, cx, |s| {
12997 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12998 });
12999 });
13000
13001 assert_indent_guides(
13002 0..5,
13003 vec![indent_guide(buffer_id, 1, 3, 0)],
13004 Some(vec![0]),
13005 &mut cx,
13006 );
13007}
13008
13009#[gpui::test]
13010async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13011 let (buffer_id, mut cx) = setup_indent_guides_editor(
13012 &"
13013 def m:
13014 a = 1
13015 pass"
13016 .unindent(),
13017 cx,
13018 )
13019 .await;
13020
13021 cx.update_editor(|editor, cx| {
13022 editor.change_selections(None, cx, |s| {
13023 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13024 });
13025 });
13026
13027 assert_indent_guides(
13028 0..3,
13029 vec![indent_guide(buffer_id, 1, 2, 0)],
13030 Some(vec![0]),
13031 &mut cx,
13032 );
13033}
13034
13035#[gpui::test]
13036fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13037 init_test(cx, |_| {});
13038
13039 let editor = cx.add_window(|cx| {
13040 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13041 build_editor(buffer, cx)
13042 });
13043
13044 let render_args = Arc::new(Mutex::new(None));
13045 let snapshot = editor
13046 .update(cx, |editor, cx| {
13047 let snapshot = editor.buffer().read(cx).snapshot(cx);
13048 let range =
13049 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13050
13051 struct RenderArgs {
13052 row: MultiBufferRow,
13053 folded: bool,
13054 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13055 }
13056
13057 let crease = Crease::new(
13058 range,
13059 FoldPlaceholder::test(),
13060 {
13061 let toggle_callback = render_args.clone();
13062 move |row, folded, callback, _cx| {
13063 *toggle_callback.lock() = Some(RenderArgs {
13064 row,
13065 folded,
13066 callback,
13067 });
13068 div()
13069 }
13070 },
13071 |_row, _folded, _cx| div(),
13072 );
13073
13074 editor.insert_creases(Some(crease), cx);
13075 let snapshot = editor.snapshot(cx);
13076 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13077 snapshot
13078 })
13079 .unwrap();
13080
13081 let render_args = render_args.lock().take().unwrap();
13082 assert_eq!(render_args.row, MultiBufferRow(1));
13083 assert!(!render_args.folded);
13084 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13085
13086 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13087 .unwrap();
13088 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13089 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13090
13091 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13092 .unwrap();
13093 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13094 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13095}
13096
13097#[gpui::test]
13098async fn test_input_text(cx: &mut gpui::TestAppContext) {
13099 init_test(cx, |_| {});
13100 let mut cx = EditorTestContext::new(cx).await;
13101
13102 cx.set_state(
13103 &r#"ˇone
13104 two
13105
13106 three
13107 fourˇ
13108 five
13109
13110 siˇx"#
13111 .unindent(),
13112 );
13113
13114 cx.dispatch_action(HandleInput(String::new()));
13115 cx.assert_editor_state(
13116 &r#"ˇone
13117 two
13118
13119 three
13120 fourˇ
13121 five
13122
13123 siˇx"#
13124 .unindent(),
13125 );
13126
13127 cx.dispatch_action(HandleInput("AAAA".to_string()));
13128 cx.assert_editor_state(
13129 &r#"AAAAˇone
13130 two
13131
13132 three
13133 fourAAAAˇ
13134 five
13135
13136 siAAAAˇx"#
13137 .unindent(),
13138 );
13139}
13140
13141#[gpui::test]
13142async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13143 init_test(cx, |_| {});
13144
13145 let mut cx = EditorTestContext::new(cx).await;
13146 cx.set_state(
13147 r#"let foo = 1;
13148let foo = 2;
13149let foo = 3;
13150let fooˇ = 4;
13151let foo = 5;
13152let foo = 6;
13153let foo = 7;
13154let foo = 8;
13155let foo = 9;
13156let foo = 10;
13157let foo = 11;
13158let foo = 12;
13159let foo = 13;
13160let foo = 14;
13161let foo = 15;"#,
13162 );
13163
13164 cx.update_editor(|e, cx| {
13165 assert_eq!(
13166 e.next_scroll_position,
13167 NextScrollCursorCenterTopBottom::Center,
13168 "Default next scroll direction is center",
13169 );
13170
13171 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13172 assert_eq!(
13173 e.next_scroll_position,
13174 NextScrollCursorCenterTopBottom::Top,
13175 "After center, next scroll direction should be top",
13176 );
13177
13178 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13179 assert_eq!(
13180 e.next_scroll_position,
13181 NextScrollCursorCenterTopBottom::Bottom,
13182 "After top, next scroll direction should be bottom",
13183 );
13184
13185 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13186 assert_eq!(
13187 e.next_scroll_position,
13188 NextScrollCursorCenterTopBottom::Center,
13189 "After bottom, scrolling should start over",
13190 );
13191
13192 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13193 assert_eq!(
13194 e.next_scroll_position,
13195 NextScrollCursorCenterTopBottom::Top,
13196 "Scrolling continues if retriggered fast enough"
13197 );
13198 });
13199
13200 cx.executor()
13201 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13202 cx.executor().run_until_parked();
13203 cx.update_editor(|e, _| {
13204 assert_eq!(
13205 e.next_scroll_position,
13206 NextScrollCursorCenterTopBottom::Center,
13207 "If scrolling is not triggered fast enough, it should reset"
13208 );
13209 });
13210}
13211
13212#[gpui::test]
13213async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13214 init_test(cx, |_| {});
13215 let mut cx = EditorLspTestContext::new_rust(
13216 lsp::ServerCapabilities {
13217 definition_provider: Some(lsp::OneOf::Left(true)),
13218 references_provider: Some(lsp::OneOf::Left(true)),
13219 ..lsp::ServerCapabilities::default()
13220 },
13221 cx,
13222 )
13223 .await;
13224
13225 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13226 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13227 move |params, _| async move {
13228 if empty_go_to_definition {
13229 Ok(None)
13230 } else {
13231 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13232 uri: params.text_document_position_params.text_document.uri,
13233 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13234 })))
13235 }
13236 },
13237 );
13238 let references =
13239 cx.lsp
13240 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13241 Ok(Some(vec![lsp::Location {
13242 uri: params.text_document_position.text_document.uri,
13243 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13244 }]))
13245 });
13246 (go_to_definition, references)
13247 };
13248
13249 cx.set_state(
13250 &r#"fn one() {
13251 let mut a = ˇtwo();
13252 }
13253
13254 fn two() {}"#
13255 .unindent(),
13256 );
13257 set_up_lsp_handlers(false, &mut cx);
13258 let navigated = cx
13259 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13260 .await
13261 .expect("Failed to navigate to definition");
13262 assert_eq!(
13263 navigated,
13264 Navigated::Yes,
13265 "Should have navigated to definition from the GetDefinition response"
13266 );
13267 cx.assert_editor_state(
13268 &r#"fn one() {
13269 let mut a = two();
13270 }
13271
13272 fn «twoˇ»() {}"#
13273 .unindent(),
13274 );
13275
13276 let editors = cx.update_workspace(|workspace, cx| {
13277 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13278 });
13279 cx.update_editor(|_, test_editor_cx| {
13280 assert_eq!(
13281 editors.len(),
13282 1,
13283 "Initially, only one, test, editor should be open in the workspace"
13284 );
13285 assert_eq!(
13286 test_editor_cx.view(),
13287 editors.last().expect("Asserted len is 1")
13288 );
13289 });
13290
13291 set_up_lsp_handlers(true, &mut cx);
13292 let navigated = cx
13293 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13294 .await
13295 .expect("Failed to navigate to lookup references");
13296 assert_eq!(
13297 navigated,
13298 Navigated::Yes,
13299 "Should have navigated to references as a fallback after empty GoToDefinition response"
13300 );
13301 // We should not change the selections in the existing file,
13302 // if opening another milti buffer with the references
13303 cx.assert_editor_state(
13304 &r#"fn one() {
13305 let mut a = two();
13306 }
13307
13308 fn «twoˇ»() {}"#
13309 .unindent(),
13310 );
13311 let editors = cx.update_workspace(|workspace, cx| {
13312 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13313 });
13314 cx.update_editor(|_, test_editor_cx| {
13315 assert_eq!(
13316 editors.len(),
13317 2,
13318 "After falling back to references search, we open a new editor with the results"
13319 );
13320 let references_fallback_text = editors
13321 .into_iter()
13322 .find(|new_editor| new_editor != test_editor_cx.view())
13323 .expect("Should have one non-test editor now")
13324 .read(test_editor_cx)
13325 .text(test_editor_cx);
13326 assert_eq!(
13327 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13328 "Should use the range from the references response and not the GoToDefinition one"
13329 );
13330 });
13331}
13332
13333#[gpui::test]
13334async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13335 init_test(cx, |_| {});
13336
13337 let language = Arc::new(Language::new(
13338 LanguageConfig::default(),
13339 Some(tree_sitter_rust::LANGUAGE.into()),
13340 ));
13341
13342 let text = r#"
13343 #[cfg(test)]
13344 mod tests() {
13345 #[test]
13346 fn runnable_1() {
13347 let a = 1;
13348 }
13349
13350 #[test]
13351 fn runnable_2() {
13352 let a = 1;
13353 let b = 2;
13354 }
13355 }
13356 "#
13357 .unindent();
13358
13359 let fs = FakeFs::new(cx.executor());
13360 fs.insert_file("/file.rs", Default::default()).await;
13361
13362 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13363 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13364 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13365 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13366 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13367
13368 let editor = cx.new_view(|cx| {
13369 Editor::new(
13370 EditorMode::Full,
13371 multi_buffer,
13372 Some(project.clone()),
13373 true,
13374 cx,
13375 )
13376 });
13377
13378 editor.update(cx, |editor, cx| {
13379 editor.tasks.insert(
13380 (buffer.read(cx).remote_id(), 3),
13381 RunnableTasks {
13382 templates: vec![],
13383 offset: MultiBufferOffset(43),
13384 column: 0,
13385 extra_variables: HashMap::default(),
13386 context_range: BufferOffset(43)..BufferOffset(85),
13387 },
13388 );
13389 editor.tasks.insert(
13390 (buffer.read(cx).remote_id(), 8),
13391 RunnableTasks {
13392 templates: vec![],
13393 offset: MultiBufferOffset(86),
13394 column: 0,
13395 extra_variables: HashMap::default(),
13396 context_range: BufferOffset(86)..BufferOffset(191),
13397 },
13398 );
13399
13400 // Test finding task when cursor is inside function body
13401 editor.change_selections(None, cx, |s| {
13402 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13403 });
13404 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13405 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13406
13407 // Test finding task when cursor is on function name
13408 editor.change_selections(None, cx, |s| {
13409 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13410 });
13411 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13412 assert_eq!(row, 8, "Should find task when cursor is on function name");
13413 });
13414}
13415
13416fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13417 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13418 point..point
13419}
13420
13421fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13422 let (text, ranges) = marked_text_ranges(marked_text, true);
13423 assert_eq!(view.text(cx), text);
13424 assert_eq!(
13425 view.selections.ranges(cx),
13426 ranges,
13427 "Assert selections are {}",
13428 marked_text
13429 );
13430}
13431
13432pub fn handle_signature_help_request(
13433 cx: &mut EditorLspTestContext,
13434 mocked_response: lsp::SignatureHelp,
13435) -> impl Future<Output = ()> {
13436 let mut request =
13437 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13438 let mocked_response = mocked_response.clone();
13439 async move { Ok(Some(mocked_response)) }
13440 });
13441
13442 async move {
13443 request.next().await;
13444 }
13445}
13446
13447/// Handle completion request passing a marked string specifying where the completion
13448/// should be triggered from using '|' character, what range should be replaced, and what completions
13449/// should be returned using '<' and '>' to delimit the range
13450pub fn handle_completion_request(
13451 cx: &mut EditorLspTestContext,
13452 marked_string: &str,
13453 completions: Vec<&'static str>,
13454 counter: Arc<AtomicUsize>,
13455) -> impl Future<Output = ()> {
13456 let complete_from_marker: TextRangeMarker = '|'.into();
13457 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13458 let (_, mut marked_ranges) = marked_text_ranges_by(
13459 marked_string,
13460 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13461 );
13462
13463 let complete_from_position =
13464 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13465 let replace_range =
13466 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13467
13468 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13469 let completions = completions.clone();
13470 counter.fetch_add(1, atomic::Ordering::Release);
13471 async move {
13472 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13473 assert_eq!(
13474 params.text_document_position.position,
13475 complete_from_position
13476 );
13477 Ok(Some(lsp::CompletionResponse::Array(
13478 completions
13479 .iter()
13480 .map(|completion_text| lsp::CompletionItem {
13481 label: completion_text.to_string(),
13482 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13483 range: replace_range,
13484 new_text: completion_text.to_string(),
13485 })),
13486 ..Default::default()
13487 })
13488 .collect(),
13489 )))
13490 }
13491 });
13492
13493 async move {
13494 request.next().await;
13495 }
13496}
13497
13498fn handle_resolve_completion_request(
13499 cx: &mut EditorLspTestContext,
13500 edits: Option<Vec<(&'static str, &'static str)>>,
13501) -> impl Future<Output = ()> {
13502 let edits = edits.map(|edits| {
13503 edits
13504 .iter()
13505 .map(|(marked_string, new_text)| {
13506 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13507 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13508 lsp::TextEdit::new(replace_range, new_text.to_string())
13509 })
13510 .collect::<Vec<_>>()
13511 });
13512
13513 let mut request =
13514 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13515 let edits = edits.clone();
13516 async move {
13517 Ok(lsp::CompletionItem {
13518 additional_text_edits: edits,
13519 ..Default::default()
13520 })
13521 }
13522 });
13523
13524 async move {
13525 request.next().await;
13526 }
13527}
13528
13529pub(crate) fn update_test_language_settings(
13530 cx: &mut TestAppContext,
13531 f: impl Fn(&mut AllLanguageSettingsContent),
13532) {
13533 cx.update(|cx| {
13534 SettingsStore::update_global(cx, |store, cx| {
13535 store.update_user_settings::<AllLanguageSettings>(cx, f);
13536 });
13537 });
13538}
13539
13540pub(crate) fn update_test_project_settings(
13541 cx: &mut TestAppContext,
13542 f: impl Fn(&mut ProjectSettings),
13543) {
13544 cx.update(|cx| {
13545 SettingsStore::update_global(cx, |store, cx| {
13546 store.update_user_settings::<ProjectSettings>(cx, f);
13547 });
13548 });
13549}
13550
13551pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13552 cx.update(|cx| {
13553 assets::Assets.load_test_fonts(cx);
13554 let store = SettingsStore::test(cx);
13555 cx.set_global(store);
13556 theme::init(theme::LoadThemes::JustBase, cx);
13557 release_channel::init(SemanticVersion::default(), cx);
13558 client::init_settings(cx);
13559 language::init(cx);
13560 Project::init_settings(cx);
13561 workspace::init_settings(cx);
13562 crate::init(cx);
13563 });
13564
13565 update_test_language_settings(cx, f);
13566}
13567
13568pub(crate) fn rust_lang() -> Arc<Language> {
13569 Arc::new(Language::new(
13570 LanguageConfig {
13571 name: "Rust".into(),
13572 matcher: LanguageMatcher {
13573 path_suffixes: vec!["rs".to_string()],
13574 ..Default::default()
13575 },
13576 ..Default::default()
13577 },
13578 Some(tree_sitter_rust::LANGUAGE.into()),
13579 ))
13580}
13581
13582#[track_caller]
13583fn assert_hunk_revert(
13584 not_reverted_text_with_selections: &str,
13585 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13586 expected_reverted_text_with_selections: &str,
13587 base_text: &str,
13588 cx: &mut EditorLspTestContext,
13589) {
13590 cx.set_state(not_reverted_text_with_selections);
13591 cx.update_editor(|editor, cx| {
13592 editor
13593 .buffer()
13594 .read(cx)
13595 .as_singleton()
13596 .unwrap()
13597 .update(cx, |buffer, cx| {
13598 buffer.set_diff_base(Some(base_text.into()), cx);
13599 });
13600 });
13601 cx.executor().run_until_parked();
13602
13603 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13604 let snapshot = editor.buffer().read(cx).snapshot(cx);
13605 let reverted_hunk_statuses = snapshot
13606 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13607 .map(|hunk| hunk_status(&hunk))
13608 .collect::<Vec<_>>();
13609
13610 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13611 reverted_hunk_statuses
13612 });
13613 cx.executor().run_until_parked();
13614 cx.assert_editor_state(expected_reverted_text_with_selections);
13615 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13616}