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 group_interval = Duration::from_millis(1);
173 let buffer = cx.new_model(|cx| {
174 let mut buf = language::Buffer::local("123456", cx);
175 buf.set_group_interval(group_interval);
176 buf
177 });
178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
179 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
180
181 _ = editor.update(cx, |editor, cx| {
182 editor.start_transaction_at(now, cx);
183 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
184
185 editor.insert("cd", cx);
186 editor.end_transaction_at(now, cx);
187 assert_eq!(editor.text(cx), "12cd56");
188 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
189
190 editor.start_transaction_at(now, cx);
191 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
192 editor.insert("e", cx);
193 editor.end_transaction_at(now, cx);
194 assert_eq!(editor.text(cx), "12cde6");
195 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
196
197 now += group_interval + Duration::from_millis(1);
198 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
199
200 // Simulate an edit in another editor
201 buffer.update(cx, |buffer, cx| {
202 buffer.start_transaction_at(now, cx);
203 buffer.edit([(0..1, "a")], None, cx);
204 buffer.edit([(1..1, "b")], None, cx);
205 buffer.end_transaction_at(now, cx);
206 });
207
208 assert_eq!(editor.text(cx), "ab2cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
210
211 // Last transaction happened past the group interval in a different editor.
212 // Undo it individually and don't restore selections.
213 editor.undo(&Undo, cx);
214 assert_eq!(editor.text(cx), "12cde6");
215 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
216
217 // First two transactions happened within the group interval in this editor.
218 // Undo them together and restore selections.
219 editor.undo(&Undo, cx);
220 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
221 assert_eq!(editor.text(cx), "123456");
222 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
223
224 // Redo the first two transactions together.
225 editor.redo(&Redo, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
228
229 // Redo the last transaction on its own.
230 editor.redo(&Redo, cx);
231 assert_eq!(editor.text(cx), "ab2cde6");
232 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
233
234 // Test empty transactions.
235 editor.start_transaction_at(now, cx);
236 editor.end_transaction_at(now, cx);
237 editor.undo(&Undo, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 });
240}
241
242#[gpui::test]
243fn test_ime_composition(cx: &mut TestAppContext) {
244 init_test(cx, |_| {});
245
246 let buffer = cx.new_model(|cx| {
247 let mut buffer = language::Buffer::local("abcde", cx);
248 // Ensure automatic grouping doesn't occur.
249 buffer.set_group_interval(Duration::ZERO);
250 buffer
251 });
252
253 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
254 cx.add_window(|cx| {
255 let mut editor = build_editor(buffer.clone(), cx);
256
257 // Start a new IME composition.
258 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
259 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
261 assert_eq!(editor.text(cx), "äbcde");
262 assert_eq!(
263 editor.marked_text_ranges(cx),
264 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
265 );
266
267 // Finalize IME composition.
268 editor.replace_text_in_range(None, "ā", cx);
269 assert_eq!(editor.text(cx), "ābcde");
270 assert_eq!(editor.marked_text_ranges(cx), None);
271
272 // IME composition edits are grouped and are undone/redone at once.
273 editor.undo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "abcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276 editor.redo(&Default::default(), cx);
277 assert_eq!(editor.text(cx), "ābcde");
278 assert_eq!(editor.marked_text_ranges(cx), None);
279
280 // Start a new IME composition.
281 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
282 assert_eq!(
283 editor.marked_text_ranges(cx),
284 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
285 );
286
287 // Undoing during an IME composition cancels it.
288 editor.undo(&Default::default(), cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
293 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
294 assert_eq!(editor.text(cx), "ābcdè");
295 assert_eq!(
296 editor.marked_text_ranges(cx),
297 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
298 );
299
300 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
301 editor.replace_text_in_range(Some(4..999), "ę", cx);
302 assert_eq!(editor.text(cx), "ābcdę");
303 assert_eq!(editor.marked_text_ranges(cx), None);
304
305 // Start a new IME composition with multiple cursors.
306 editor.change_selections(None, cx, |s| {
307 s.select_ranges([
308 OffsetUtf16(1)..OffsetUtf16(1),
309 OffsetUtf16(3)..OffsetUtf16(3),
310 OffsetUtf16(5)..OffsetUtf16(5),
311 ])
312 });
313 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
314 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
315 assert_eq!(
316 editor.marked_text_ranges(cx),
317 Some(vec![
318 OffsetUtf16(0)..OffsetUtf16(3),
319 OffsetUtf16(4)..OffsetUtf16(7),
320 OffsetUtf16(8)..OffsetUtf16(11)
321 ])
322 );
323
324 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
325 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
326 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(1)..OffsetUtf16(2),
331 OffsetUtf16(5)..OffsetUtf16(6),
332 OffsetUtf16(9)..OffsetUtf16(10)
333 ])
334 );
335
336 // Finalize IME composition with multiple cursors.
337 editor.replace_text_in_range(Some(9..10), "2", cx);
338 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
339 assert_eq!(editor.marked_text_ranges(cx), None);
340
341 editor
342 });
343}
344
345#[gpui::test]
346fn test_selection_with_mouse(cx: &mut TestAppContext) {
347 init_test(cx, |_| {});
348
349 let editor = cx.add_window(|cx| {
350 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
351 build_editor(buffer, cx)
352 });
353
354 _ = editor.update(cx, |view, cx| {
355 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
356 });
357 assert_eq!(
358 editor
359 .update(cx, |view, cx| view.selections.display_ranges(cx))
360 .unwrap(),
361 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
362 );
363
364 _ = editor.update(cx, |view, cx| {
365 view.update_selection(
366 DisplayPoint::new(DisplayRow(3), 3),
367 0,
368 gpui::Point::<f32>::default(),
369 cx,
370 );
371 });
372
373 assert_eq!(
374 editor
375 .update(cx, |view, cx| view.selections.display_ranges(cx))
376 .unwrap(),
377 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
378 );
379
380 _ = editor.update(cx, |view, cx| {
381 view.update_selection(
382 DisplayPoint::new(DisplayRow(1), 1),
383 0,
384 gpui::Point::<f32>::default(),
385 cx,
386 );
387 });
388
389 assert_eq!(
390 editor
391 .update(cx, |view, cx| view.selections.display_ranges(cx))
392 .unwrap(),
393 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
394 );
395
396 _ = editor.update(cx, |view, cx| {
397 view.end_selection(cx);
398 view.update_selection(
399 DisplayPoint::new(DisplayRow(3), 3),
400 0,
401 gpui::Point::<f32>::default(),
402 cx,
403 );
404 });
405
406 assert_eq!(
407 editor
408 .update(cx, |view, cx| view.selections.display_ranges(cx))
409 .unwrap(),
410 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
411 );
412
413 _ = editor.update(cx, |view, cx| {
414 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
415 view.update_selection(
416 DisplayPoint::new(DisplayRow(0), 0),
417 0,
418 gpui::Point::<f32>::default(),
419 cx,
420 );
421 });
422
423 assert_eq!(
424 editor
425 .update(cx, |view, cx| view.selections.display_ranges(cx))
426 .unwrap(),
427 [
428 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
429 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
430 ]
431 );
432
433 _ = editor.update(cx, |view, cx| {
434 view.end_selection(cx);
435 });
436
437 assert_eq!(
438 editor
439 .update(cx, |view, cx| view.selections.display_ranges(cx))
440 .unwrap(),
441 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
442 );
443}
444
445#[gpui::test]
446fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
447 init_test(cx, |_| {});
448
449 let editor = cx.add_window(|cx| {
450 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
451 build_editor(buffer, cx)
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.end_selection(cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.end_selection(cx);
468 });
469
470 assert_eq!(
471 editor
472 .update(cx, |view, cx| view.selections.display_ranges(cx))
473 .unwrap(),
474 [
475 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
476 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
477 ]
478 );
479
480 _ = editor.update(cx, |view, cx| {
481 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
482 });
483
484 _ = editor.update(cx, |view, cx| {
485 view.end_selection(cx);
486 });
487
488 assert_eq!(
489 editor
490 .update(cx, |view, cx| view.selections.display_ranges(cx))
491 .unwrap(),
492 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
493 );
494}
495
496#[gpui::test]
497fn test_canceling_pending_selection(cx: &mut TestAppContext) {
498 init_test(cx, |_| {});
499
500 let view = cx.add_window(|cx| {
501 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
502 build_editor(buffer, cx)
503 });
504
505 _ = view.update(cx, |view, cx| {
506 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
507 assert_eq!(
508 view.selections.display_ranges(cx),
509 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
510 );
511 });
512
513 _ = view.update(cx, |view, cx| {
514 view.update_selection(
515 DisplayPoint::new(DisplayRow(3), 3),
516 0,
517 gpui::Point::<f32>::default(),
518 cx,
519 );
520 assert_eq!(
521 view.selections.display_ranges(cx),
522 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
523 );
524 });
525
526 _ = view.update(cx, |view, cx| {
527 view.cancel(&Cancel, cx);
528 view.update_selection(
529 DisplayPoint::new(DisplayRow(1), 1),
530 0,
531 gpui::Point::<f32>::default(),
532 cx,
533 );
534 assert_eq!(
535 view.selections.display_ranges(cx),
536 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
537 );
538 });
539}
540
541#[gpui::test]
542fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
543 init_test(cx, |_| {});
544
545 let view = cx.add_window(|cx| {
546 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
547 build_editor(buffer, cx)
548 });
549
550 _ = view.update(cx, |view, cx| {
551 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
552 assert_eq!(
553 view.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
555 );
556
557 view.move_down(&Default::default(), cx);
558 assert_eq!(
559 view.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
561 );
562
563 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
564 assert_eq!(
565 view.selections.display_ranges(cx),
566 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
567 );
568
569 view.move_up(&Default::default(), cx);
570 assert_eq!(
571 view.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
573 );
574 });
575}
576
577#[gpui::test]
578fn test_clone(cx: &mut TestAppContext) {
579 init_test(cx, |_| {});
580
581 let (text, selection_ranges) = marked_text_ranges(
582 indoc! {"
583 one
584 two
585 threeˇ
586 four
587 fiveˇ
588 "},
589 true,
590 );
591
592 let editor = cx.add_window(|cx| {
593 let buffer = MultiBuffer::build_simple(&text, cx);
594 build_editor(buffer, cx)
595 });
596
597 _ = editor.update(cx, |editor, cx| {
598 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
599 editor.fold_ranges(
600 [
601 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
602 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
603 ],
604 true,
605 cx,
606 );
607 });
608
609 let cloned_editor = editor
610 .update(cx, |editor, cx| {
611 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
612 })
613 .unwrap()
614 .unwrap();
615
616 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
617 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618
619 assert_eq!(
620 cloned_editor
621 .update(cx, |e, cx| e.display_text(cx))
622 .unwrap(),
623 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
624 );
625 assert_eq!(
626 cloned_snapshot
627 .folds_in_range(0..text.len())
628 .collect::<Vec<_>>(),
629 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
630 );
631 assert_set_eq!(
632 cloned_editor
633 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
634 .unwrap(),
635 editor
636 .update(cx, |editor, cx| editor.selections.ranges(cx))
637 .unwrap()
638 );
639 assert_set_eq!(
640 cloned_editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap(),
643 editor
644 .update(cx, |e, cx| e.selections.display_ranges(cx))
645 .unwrap()
646 );
647}
648
649#[gpui::test]
650async fn test_navigation_history(cx: &mut TestAppContext) {
651 init_test(cx, |_| {});
652
653 use workspace::item::Item;
654
655 let fs = FakeFs::new(cx.executor());
656 let project = Project::test(fs, [], cx).await;
657 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
658 let pane = workspace
659 .update(cx, |workspace, _| workspace.active_pane().clone())
660 .unwrap();
661
662 _ = workspace.update(cx, |_v, cx| {
663 cx.new_view(|cx| {
664 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
665 let mut editor = build_editor(buffer.clone(), cx);
666 let handle = cx.view();
667 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
668
669 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
670 editor.nav_history.as_mut().unwrap().pop_backward(cx)
671 }
672
673 // Move the cursor a small distance.
674 // Nothing is added to the navigation history.
675 editor.change_selections(None, cx, |s| {
676 s.select_display_ranges([
677 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
678 ])
679 });
680 editor.change_selections(None, cx, |s| {
681 s.select_display_ranges([
682 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
683 ])
684 });
685 assert!(pop_history(&mut editor, cx).is_none());
686
687 // Move the cursor a large distance.
688 // The history can jump back to the previous position.
689 editor.change_selections(None, cx, |s| {
690 s.select_display_ranges([
691 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
692 ])
693 });
694 let nav_entry = pop_history(&mut editor, cx).unwrap();
695 editor.navigate(nav_entry.data.unwrap(), cx);
696 assert_eq!(nav_entry.item.id(), cx.entity_id());
697 assert_eq!(
698 editor.selections.display_ranges(cx),
699 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
700 );
701 assert!(pop_history(&mut editor, cx).is_none());
702
703 // Move the cursor a small distance via the mouse.
704 // Nothing is added to the navigation history.
705 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
706 editor.end_selection(cx);
707 assert_eq!(
708 editor.selections.display_ranges(cx),
709 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
710 );
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance via the mouse.
714 // The history can jump back to the previous position.
715 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
716 editor.end_selection(cx);
717 assert_eq!(
718 editor.selections.display_ranges(cx),
719 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
720 );
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Set scroll position to check later
731 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
732 let original_scroll_position = editor.scroll_manager.anchor();
733
734 // Jump to the end of the document and adjust scroll
735 editor.move_to_end(&MoveToEnd, cx);
736 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
737 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 let nav_entry = pop_history(&mut editor, cx).unwrap();
740 editor.navigate(nav_entry.data.unwrap(), cx);
741 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
744 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
745 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
746 let invalid_point = Point::new(9999, 0);
747 editor.navigate(
748 Box::new(NavigationData {
749 cursor_anchor: invalid_anchor,
750 cursor_position: invalid_point,
751 scroll_anchor: ScrollAnchor {
752 anchor: invalid_anchor,
753 offset: Default::default(),
754 },
755 scroll_top_row: invalid_point.row,
756 }),
757 cx,
758 );
759 assert_eq!(
760 editor.selections.display_ranges(cx),
761 &[editor.max_point(cx)..editor.max_point(cx)]
762 );
763 assert_eq!(
764 editor.scroll_position(cx),
765 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
766 );
767
768 editor
769 })
770 });
771}
772
773#[gpui::test]
774fn test_cancel(cx: &mut TestAppContext) {
775 init_test(cx, |_| {});
776
777 let view = cx.add_window(|cx| {
778 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
779 build_editor(buffer, cx)
780 });
781
782 _ = view.update(cx, |view, cx| {
783 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
784 view.update_selection(
785 DisplayPoint::new(DisplayRow(1), 1),
786 0,
787 gpui::Point::<f32>::default(),
788 cx,
789 );
790 view.end_selection(cx);
791
792 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
793 view.update_selection(
794 DisplayPoint::new(DisplayRow(0), 3),
795 0,
796 gpui::Point::<f32>::default(),
797 cx,
798 );
799 view.end_selection(cx);
800 assert_eq!(
801 view.selections.display_ranges(cx),
802 [
803 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
804 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
805 ]
806 );
807 });
808
809 _ = view.update(cx, |view, cx| {
810 view.cancel(&Cancel, cx);
811 assert_eq!(
812 view.selections.display_ranges(cx),
813 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
814 );
815 });
816
817 _ = view.update(cx, |view, cx| {
818 view.cancel(&Cancel, cx);
819 assert_eq!(
820 view.selections.display_ranges(cx),
821 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
822 );
823 });
824}
825
826#[gpui::test]
827fn test_fold_action(cx: &mut TestAppContext) {
828 init_test(cx, |_| {});
829
830 let view = cx.add_window(|cx| {
831 let buffer = MultiBuffer::build_simple(
832 &"
833 impl Foo {
834 // Hello!
835
836 fn a() {
837 1
838 }
839
840 fn b() {
841 2
842 }
843
844 fn c() {
845 3
846 }
847 }
848 "
849 .unindent(),
850 cx,
851 );
852 build_editor(buffer.clone(), cx)
853 });
854
855 _ = view.update(cx, |view, cx| {
856 view.change_selections(None, cx, |s| {
857 s.select_display_ranges([
858 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
859 ]);
860 });
861 view.fold(&Fold, cx);
862 assert_eq!(
863 view.display_text(cx),
864 "
865 impl Foo {
866 // Hello!
867
868 fn a() {
869 1
870 }
871
872 fn b() {⋯
873 }
874
875 fn c() {⋯
876 }
877 }
878 "
879 .unindent(),
880 );
881
882 view.fold(&Fold, cx);
883 assert_eq!(
884 view.display_text(cx),
885 "
886 impl Foo {⋯
887 }
888 "
889 .unindent(),
890 );
891
892 view.unfold_lines(&UnfoldLines, cx);
893 assert_eq!(
894 view.display_text(cx),
895 "
896 impl Foo {
897 // Hello!
898
899 fn a() {
900 1
901 }
902
903 fn b() {⋯
904 }
905
906 fn c() {⋯
907 }
908 }
909 "
910 .unindent(),
911 );
912
913 view.unfold_lines(&UnfoldLines, cx);
914 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
915 });
916}
917
918#[gpui::test]
919fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
920 init_test(cx, |_| {});
921
922 let view = cx.add_window(|cx| {
923 let buffer = MultiBuffer::build_simple(
924 &"
925 class Foo:
926 # Hello!
927
928 def a():
929 print(1)
930
931 def b():
932 print(2)
933
934 def c():
935 print(3)
936 "
937 .unindent(),
938 cx,
939 );
940 build_editor(buffer.clone(), cx)
941 });
942
943 _ = view.update(cx, |view, cx| {
944 view.change_selections(None, cx, |s| {
945 s.select_display_ranges([
946 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
947 ]);
948 });
949 view.fold(&Fold, cx);
950 assert_eq!(
951 view.display_text(cx),
952 "
953 class Foo:
954 # Hello!
955
956 def a():
957 print(1)
958
959 def b():⋯
960
961 def c():⋯
962 "
963 .unindent(),
964 );
965
966 view.fold(&Fold, cx);
967 assert_eq!(
968 view.display_text(cx),
969 "
970 class Foo:⋯
971 "
972 .unindent(),
973 );
974
975 view.unfold_lines(&UnfoldLines, cx);
976 assert_eq!(
977 view.display_text(cx),
978 "
979 class Foo:
980 # Hello!
981
982 def a():
983 print(1)
984
985 def b():⋯
986
987 def c():⋯
988 "
989 .unindent(),
990 );
991
992 view.unfold_lines(&UnfoldLines, cx);
993 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
994 });
995}
996
997#[gpui::test]
998fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
999 init_test(cx, |_| {});
1000
1001 let view = cx.add_window(|cx| {
1002 let buffer = MultiBuffer::build_simple(
1003 &"
1004 class Foo:
1005 # Hello!
1006
1007 def a():
1008 print(1)
1009
1010 def b():
1011 print(2)
1012
1013
1014 def c():
1015 print(3)
1016
1017
1018 "
1019 .unindent(),
1020 cx,
1021 );
1022 build_editor(buffer.clone(), cx)
1023 });
1024
1025 _ = view.update(cx, |view, cx| {
1026 view.change_selections(None, cx, |s| {
1027 s.select_display_ranges([
1028 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1029 ]);
1030 });
1031 view.fold(&Fold, cx);
1032 assert_eq!(
1033 view.display_text(cx),
1034 "
1035 class Foo:
1036 # Hello!
1037
1038 def a():
1039 print(1)
1040
1041 def b():⋯
1042
1043
1044 def c():⋯
1045
1046
1047 "
1048 .unindent(),
1049 );
1050
1051 view.fold(&Fold, cx);
1052 assert_eq!(
1053 view.display_text(cx),
1054 "
1055 class Foo:⋯
1056
1057
1058 "
1059 .unindent(),
1060 );
1061
1062 view.unfold_lines(&UnfoldLines, cx);
1063 assert_eq!(
1064 view.display_text(cx),
1065 "
1066 class Foo:
1067 # Hello!
1068
1069 def a():
1070 print(1)
1071
1072 def b():⋯
1073
1074
1075 def c():⋯
1076
1077
1078 "
1079 .unindent(),
1080 );
1081
1082 view.unfold_lines(&UnfoldLines, cx);
1083 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1084 });
1085}
1086
1087#[gpui::test]
1088fn test_fold_at_level(cx: &mut TestAppContext) {
1089 init_test(cx, |_| {});
1090
1091 let view = cx.add_window(|cx| {
1092 let buffer = MultiBuffer::build_simple(
1093 &"
1094 class Foo:
1095 # Hello!
1096
1097 def a():
1098 print(1)
1099
1100 def b():
1101 print(2)
1102
1103
1104 class Bar:
1105 # World!
1106
1107 def a():
1108 print(1)
1109
1110 def b():
1111 print(2)
1112
1113
1114 "
1115 .unindent(),
1116 cx,
1117 );
1118 build_editor(buffer.clone(), cx)
1119 });
1120
1121 _ = view.update(cx, |view, cx| {
1122 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1123 assert_eq!(
1124 view.display_text(cx),
1125 "
1126 class Foo:
1127 # Hello!
1128
1129 def a():⋯
1130
1131 def b():⋯
1132
1133
1134 class Bar:
1135 # World!
1136
1137 def a():⋯
1138
1139 def b():⋯
1140
1141
1142 "
1143 .unindent(),
1144 );
1145
1146 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1147 assert_eq!(
1148 view.display_text(cx),
1149 "
1150 class Foo:⋯
1151
1152
1153 class Bar:⋯
1154
1155
1156 "
1157 .unindent(),
1158 );
1159
1160 view.unfold_all(&UnfoldAll, cx);
1161 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1162 assert_eq!(
1163 view.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():
1169 print(1)
1170
1171 def b():
1172 print(2)
1173
1174
1175 class Bar:
1176 # World!
1177
1178 def a():
1179 print(1)
1180
1181 def b():
1182 print(2)
1183
1184
1185 "
1186 .unindent(),
1187 );
1188
1189 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1190 });
1191}
1192
1193#[gpui::test]
1194fn test_move_cursor(cx: &mut TestAppContext) {
1195 init_test(cx, |_| {});
1196
1197 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1198 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1199
1200 buffer.update(cx, |buffer, cx| {
1201 buffer.edit(
1202 vec![
1203 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1204 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1205 ],
1206 None,
1207 cx,
1208 );
1209 });
1210 _ = view.update(cx, |view, cx| {
1211 assert_eq!(
1212 view.selections.display_ranges(cx),
1213 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1214 );
1215
1216 view.move_down(&MoveDown, cx);
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1220 );
1221
1222 view.move_right(&MoveRight, cx);
1223 assert_eq!(
1224 view.selections.display_ranges(cx),
1225 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1226 );
1227
1228 view.move_left(&MoveLeft, cx);
1229 assert_eq!(
1230 view.selections.display_ranges(cx),
1231 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1232 );
1233
1234 view.move_up(&MoveUp, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1238 );
1239
1240 view.move_to_end(&MoveToEnd, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1244 );
1245
1246 view.move_to_beginning(&MoveToBeginning, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1250 );
1251
1252 view.change_selections(None, cx, |s| {
1253 s.select_display_ranges([
1254 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1255 ]);
1256 });
1257 view.select_to_beginning(&SelectToBeginning, cx);
1258 assert_eq!(
1259 view.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1261 );
1262
1263 view.select_to_end(&SelectToEnd, cx);
1264 assert_eq!(
1265 view.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1267 );
1268 });
1269}
1270
1271// TODO: Re-enable this test
1272#[cfg(target_os = "macos")]
1273#[gpui::test]
1274fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1275 init_test(cx, |_| {});
1276
1277 let view = cx.add_window(|cx| {
1278 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1279 build_editor(buffer.clone(), cx)
1280 });
1281
1282 assert_eq!('ⓐ'.len_utf8(), 3);
1283 assert_eq!('α'.len_utf8(), 2);
1284
1285 _ = view.update(cx, |view, cx| {
1286 view.fold_ranges(
1287 vec![
1288 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1289 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1290 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1291 ],
1292 true,
1293 cx,
1294 );
1295 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1296
1297 view.move_right(&MoveRight, cx);
1298 assert_eq!(
1299 view.selections.display_ranges(cx),
1300 &[empty_range(0, "ⓐ".len())]
1301 );
1302 view.move_right(&MoveRight, cx);
1303 assert_eq!(
1304 view.selections.display_ranges(cx),
1305 &[empty_range(0, "ⓐⓑ".len())]
1306 );
1307 view.move_right(&MoveRight, cx);
1308 assert_eq!(
1309 view.selections.display_ranges(cx),
1310 &[empty_range(0, "ⓐⓑ⋯".len())]
1311 );
1312
1313 view.move_down(&MoveDown, cx);
1314 assert_eq!(
1315 view.selections.display_ranges(cx),
1316 &[empty_range(1, "ab⋯e".len())]
1317 );
1318 view.move_left(&MoveLeft, cx);
1319 assert_eq!(
1320 view.selections.display_ranges(cx),
1321 &[empty_range(1, "ab⋯".len())]
1322 );
1323 view.move_left(&MoveLeft, cx);
1324 assert_eq!(
1325 view.selections.display_ranges(cx),
1326 &[empty_range(1, "ab".len())]
1327 );
1328 view.move_left(&MoveLeft, cx);
1329 assert_eq!(
1330 view.selections.display_ranges(cx),
1331 &[empty_range(1, "a".len())]
1332 );
1333
1334 view.move_down(&MoveDown, cx);
1335 assert_eq!(
1336 view.selections.display_ranges(cx),
1337 &[empty_range(2, "α".len())]
1338 );
1339 view.move_right(&MoveRight, cx);
1340 assert_eq!(
1341 view.selections.display_ranges(cx),
1342 &[empty_range(2, "αβ".len())]
1343 );
1344 view.move_right(&MoveRight, cx);
1345 assert_eq!(
1346 view.selections.display_ranges(cx),
1347 &[empty_range(2, "αβ⋯".len())]
1348 );
1349 view.move_right(&MoveRight, cx);
1350 assert_eq!(
1351 view.selections.display_ranges(cx),
1352 &[empty_range(2, "αβ⋯ε".len())]
1353 );
1354
1355 view.move_up(&MoveUp, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 view.move_down(&MoveDown, cx);
1361 assert_eq!(
1362 view.selections.display_ranges(cx),
1363 &[empty_range(2, "αβ⋯ε".len())]
1364 );
1365 view.move_up(&MoveUp, cx);
1366 assert_eq!(
1367 view.selections.display_ranges(cx),
1368 &[empty_range(1, "ab⋯e".len())]
1369 );
1370
1371 view.move_up(&MoveUp, cx);
1372 assert_eq!(
1373 view.selections.display_ranges(cx),
1374 &[empty_range(0, "ⓐⓑ".len())]
1375 );
1376 view.move_left(&MoveLeft, cx);
1377 assert_eq!(
1378 view.selections.display_ranges(cx),
1379 &[empty_range(0, "ⓐ".len())]
1380 );
1381 view.move_left(&MoveLeft, cx);
1382 assert_eq!(
1383 view.selections.display_ranges(cx),
1384 &[empty_range(0, "".len())]
1385 );
1386 });
1387}
1388
1389#[gpui::test]
1390fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1391 init_test(cx, |_| {});
1392
1393 let view = cx.add_window(|cx| {
1394 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1395 build_editor(buffer.clone(), cx)
1396 });
1397 _ = view.update(cx, |view, cx| {
1398 view.change_selections(None, cx, |s| {
1399 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1400 });
1401 view.move_down(&MoveDown, cx);
1402 assert_eq!(
1403 view.selections.display_ranges(cx),
1404 &[empty_range(1, "abcd".len())]
1405 );
1406
1407 view.move_down(&MoveDown, cx);
1408 assert_eq!(
1409 view.selections.display_ranges(cx),
1410 &[empty_range(2, "αβγ".len())]
1411 );
1412
1413 view.move_down(&MoveDown, cx);
1414 assert_eq!(
1415 view.selections.display_ranges(cx),
1416 &[empty_range(3, "abcd".len())]
1417 );
1418
1419 view.move_down(&MoveDown, cx);
1420 assert_eq!(
1421 view.selections.display_ranges(cx),
1422 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1423 );
1424
1425 view.move_up(&MoveUp, cx);
1426 assert_eq!(
1427 view.selections.display_ranges(cx),
1428 &[empty_range(3, "abcd".len())]
1429 );
1430
1431 view.move_up(&MoveUp, cx);
1432 assert_eq!(
1433 view.selections.display_ranges(cx),
1434 &[empty_range(2, "αβγ".len())]
1435 );
1436 });
1437}
1438
1439#[gpui::test]
1440fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1441 init_test(cx, |_| {});
1442 let move_to_beg = MoveToBeginningOfLine {
1443 stop_at_soft_wraps: true,
1444 };
1445
1446 let move_to_end = MoveToEndOfLine {
1447 stop_at_soft_wraps: true,
1448 };
1449
1450 let view = cx.add_window(|cx| {
1451 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1452 build_editor(buffer, cx)
1453 });
1454 _ = view.update(cx, |view, cx| {
1455 view.change_selections(None, cx, |s| {
1456 s.select_display_ranges([
1457 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1458 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1459 ]);
1460 });
1461 });
1462
1463 _ = view.update(cx, |view, cx| {
1464 view.move_to_beginning_of_line(&move_to_beg, cx);
1465 assert_eq!(
1466 view.selections.display_ranges(cx),
1467 &[
1468 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1469 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1470 ]
1471 );
1472 });
1473
1474 _ = view.update(cx, |view, cx| {
1475 view.move_to_beginning_of_line(&move_to_beg, cx);
1476 assert_eq!(
1477 view.selections.display_ranges(cx),
1478 &[
1479 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1480 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1481 ]
1482 );
1483 });
1484
1485 _ = view.update(cx, |view, cx| {
1486 view.move_to_beginning_of_line(&move_to_beg, cx);
1487 assert_eq!(
1488 view.selections.display_ranges(cx),
1489 &[
1490 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1491 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1492 ]
1493 );
1494 });
1495
1496 _ = view.update(cx, |view, cx| {
1497 view.move_to_end_of_line(&move_to_end, cx);
1498 assert_eq!(
1499 view.selections.display_ranges(cx),
1500 &[
1501 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1502 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1503 ]
1504 );
1505 });
1506
1507 // Moving to the end of line again is a no-op.
1508 _ = view.update(cx, |view, cx| {
1509 view.move_to_end_of_line(&move_to_end, cx);
1510 assert_eq!(
1511 view.selections.display_ranges(cx),
1512 &[
1513 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1514 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1515 ]
1516 );
1517 });
1518
1519 _ = view.update(cx, |view, cx| {
1520 view.move_left(&MoveLeft, cx);
1521 view.select_to_beginning_of_line(
1522 &SelectToBeginningOfLine {
1523 stop_at_soft_wraps: true,
1524 },
1525 cx,
1526 );
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1531 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1532 ]
1533 );
1534 });
1535
1536 _ = view.update(cx, |view, cx| {
1537 view.select_to_beginning_of_line(
1538 &SelectToBeginningOfLine {
1539 stop_at_soft_wraps: true,
1540 },
1541 cx,
1542 );
1543 assert_eq!(
1544 view.selections.display_ranges(cx),
1545 &[
1546 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1547 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1548 ]
1549 );
1550 });
1551
1552 _ = view.update(cx, |view, cx| {
1553 view.select_to_beginning_of_line(
1554 &SelectToBeginningOfLine {
1555 stop_at_soft_wraps: true,
1556 },
1557 cx,
1558 );
1559 assert_eq!(
1560 view.selections.display_ranges(cx),
1561 &[
1562 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1563 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1564 ]
1565 );
1566 });
1567
1568 _ = view.update(cx, |view, cx| {
1569 view.select_to_end_of_line(
1570 &SelectToEndOfLine {
1571 stop_at_soft_wraps: true,
1572 },
1573 cx,
1574 );
1575 assert_eq!(
1576 view.selections.display_ranges(cx),
1577 &[
1578 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1579 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1580 ]
1581 );
1582 });
1583
1584 _ = view.update(cx, |view, cx| {
1585 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1586 assert_eq!(view.display_text(cx), "ab\n de");
1587 assert_eq!(
1588 view.selections.display_ranges(cx),
1589 &[
1590 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1591 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1592 ]
1593 );
1594 });
1595
1596 _ = view.update(cx, |view, cx| {
1597 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1598 assert_eq!(view.display_text(cx), "\n");
1599 assert_eq!(
1600 view.selections.display_ranges(cx),
1601 &[
1602 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1603 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1604 ]
1605 );
1606 });
1607}
1608
1609#[gpui::test]
1610fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1611 init_test(cx, |_| {});
1612 let move_to_beg = MoveToBeginningOfLine {
1613 stop_at_soft_wraps: false,
1614 };
1615
1616 let move_to_end = MoveToEndOfLine {
1617 stop_at_soft_wraps: false,
1618 };
1619
1620 let view = cx.add_window(|cx| {
1621 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1622 build_editor(buffer, cx)
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.set_wrap_width(Some(140.0.into()), cx);
1627
1628 // We expect the following lines after wrapping
1629 // ```
1630 // thequickbrownfox
1631 // jumpedoverthelazydo
1632 // gs
1633 // ```
1634 // The final `gs` was soft-wrapped onto a new line.
1635 assert_eq!(
1636 "thequickbrownfox\njumpedoverthelaz\nydogs",
1637 view.display_text(cx),
1638 );
1639
1640 // First, let's assert behavior on the first line, that was not soft-wrapped.
1641 // Start the cursor at the `k` on the first line
1642 view.change_selections(None, cx, |s| {
1643 s.select_display_ranges([
1644 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1645 ]);
1646 });
1647
1648 // Moving to the beginning of the line should put us at the beginning of the line.
1649 view.move_to_beginning_of_line(&move_to_beg, cx);
1650 assert_eq!(
1651 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1652 view.selections.display_ranges(cx)
1653 );
1654
1655 // Moving to the end of the line should put us at the end of the line.
1656 view.move_to_end_of_line(&move_to_end, cx);
1657 assert_eq!(
1658 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1659 view.selections.display_ranges(cx)
1660 );
1661
1662 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1663 // Start the cursor at the last line (`y` that was wrapped to a new line)
1664 view.change_selections(None, cx, |s| {
1665 s.select_display_ranges([
1666 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1667 ]);
1668 });
1669
1670 // Moving to the beginning of the line should put us at the start of the second line of
1671 // display text, i.e., the `j`.
1672 view.move_to_beginning_of_line(&move_to_beg, cx);
1673 assert_eq!(
1674 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1675 view.selections.display_ranges(cx)
1676 );
1677
1678 // Moving to the beginning of the line again should be a no-op.
1679 view.move_to_beginning_of_line(&move_to_beg, cx);
1680 assert_eq!(
1681 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1682 view.selections.display_ranges(cx)
1683 );
1684
1685 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1686 // next display line.
1687 view.move_to_end_of_line(&move_to_end, cx);
1688 assert_eq!(
1689 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1690 view.selections.display_ranges(cx)
1691 );
1692
1693 // Moving to the end of the line again should be a no-op.
1694 view.move_to_end_of_line(&move_to_end, cx);
1695 assert_eq!(
1696 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1697 view.selections.display_ranges(cx)
1698 );
1699 });
1700}
1701
1702#[gpui::test]
1703fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1704 init_test(cx, |_| {});
1705
1706 let view = cx.add_window(|cx| {
1707 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1708 build_editor(buffer, cx)
1709 });
1710 _ = view.update(cx, |view, cx| {
1711 view.change_selections(None, cx, |s| {
1712 s.select_display_ranges([
1713 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1714 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1715 ])
1716 });
1717
1718 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1719 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1720
1721 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1722 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1723
1724 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1725 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1726
1727 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1728 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1729
1730 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1731 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1732
1733 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1734 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1735
1736 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1737 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1738
1739 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1740 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1741
1742 view.move_right(&MoveRight, cx);
1743 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1744 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1745
1746 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1747 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1748
1749 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1750 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1751 });
1752}
1753
1754#[gpui::test]
1755fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1756 init_test(cx, |_| {});
1757
1758 let view = cx.add_window(|cx| {
1759 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1760 build_editor(buffer, cx)
1761 });
1762
1763 _ = view.update(cx, |view, cx| {
1764 view.set_wrap_width(Some(140.0.into()), cx);
1765 assert_eq!(
1766 view.display_text(cx),
1767 "use one::{\n two::three::\n four::five\n};"
1768 );
1769
1770 view.change_selections(None, cx, |s| {
1771 s.select_display_ranges([
1772 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1773 ]);
1774 });
1775
1776 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1777 assert_eq!(
1778 view.selections.display_ranges(cx),
1779 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1780 );
1781
1782 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1783 assert_eq!(
1784 view.selections.display_ranges(cx),
1785 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1786 );
1787
1788 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1789 assert_eq!(
1790 view.selections.display_ranges(cx),
1791 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1792 );
1793
1794 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1795 assert_eq!(
1796 view.selections.display_ranges(cx),
1797 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1798 );
1799
1800 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1801 assert_eq!(
1802 view.selections.display_ranges(cx),
1803 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1804 );
1805
1806 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1807 assert_eq!(
1808 view.selections.display_ranges(cx),
1809 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1810 );
1811 });
1812}
1813
1814#[gpui::test]
1815async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1816 init_test(cx, |_| {});
1817 let mut cx = EditorTestContext::new(cx).await;
1818
1819 let line_height = cx.editor(|editor, cx| {
1820 editor
1821 .style()
1822 .unwrap()
1823 .text
1824 .line_height_in_pixels(cx.rem_size())
1825 });
1826 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1827
1828 cx.set_state(
1829 &r#"ˇone
1830 two
1831
1832 three
1833 fourˇ
1834 five
1835
1836 six"#
1837 .unindent(),
1838 );
1839
1840 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1841 cx.assert_editor_state(
1842 &r#"one
1843 two
1844 ˇ
1845 three
1846 four
1847 five
1848 ˇ
1849 six"#
1850 .unindent(),
1851 );
1852
1853 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1854 cx.assert_editor_state(
1855 &r#"one
1856 two
1857
1858 three
1859 four
1860 five
1861 ˇ
1862 sixˇ"#
1863 .unindent(),
1864 );
1865
1866 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1867 cx.assert_editor_state(
1868 &r#"one
1869 two
1870
1871 three
1872 four
1873 five
1874
1875 sixˇ"#
1876 .unindent(),
1877 );
1878
1879 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1880 cx.assert_editor_state(
1881 &r#"one
1882 two
1883
1884 three
1885 four
1886 five
1887 ˇ
1888 six"#
1889 .unindent(),
1890 );
1891
1892 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1893 cx.assert_editor_state(
1894 &r#"one
1895 two
1896 ˇ
1897 three
1898 four
1899 five
1900
1901 six"#
1902 .unindent(),
1903 );
1904
1905 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1906 cx.assert_editor_state(
1907 &r#"ˇone
1908 two
1909
1910 three
1911 four
1912 five
1913
1914 six"#
1915 .unindent(),
1916 );
1917}
1918
1919#[gpui::test]
1920async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1921 init_test(cx, |_| {});
1922 let mut cx = EditorTestContext::new(cx).await;
1923 let line_height = cx.editor(|editor, cx| {
1924 editor
1925 .style()
1926 .unwrap()
1927 .text
1928 .line_height_in_pixels(cx.rem_size())
1929 });
1930 let window = cx.window;
1931 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1932
1933 cx.set_state(
1934 r#"ˇone
1935 two
1936 three
1937 four
1938 five
1939 six
1940 seven
1941 eight
1942 nine
1943 ten
1944 "#,
1945 );
1946
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 0.)
1951 );
1952 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1953 assert_eq!(
1954 editor.snapshot(cx).scroll_position(),
1955 gpui::Point::new(0., 3.)
1956 );
1957 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1958 assert_eq!(
1959 editor.snapshot(cx).scroll_position(),
1960 gpui::Point::new(0., 6.)
1961 );
1962 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1963 assert_eq!(
1964 editor.snapshot(cx).scroll_position(),
1965 gpui::Point::new(0., 3.)
1966 );
1967
1968 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1969 assert_eq!(
1970 editor.snapshot(cx).scroll_position(),
1971 gpui::Point::new(0., 1.)
1972 );
1973 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1974 assert_eq!(
1975 editor.snapshot(cx).scroll_position(),
1976 gpui::Point::new(0., 3.)
1977 );
1978 });
1979}
1980
1981#[gpui::test]
1982async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1983 init_test(cx, |_| {});
1984 let mut cx = EditorTestContext::new(cx).await;
1985
1986 let line_height = cx.update_editor(|editor, cx| {
1987 editor.set_vertical_scroll_margin(2, cx);
1988 editor
1989 .style()
1990 .unwrap()
1991 .text
1992 .line_height_in_pixels(cx.rem_size())
1993 });
1994 let window = cx.window;
1995 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1996
1997 cx.set_state(
1998 r#"ˇone
1999 two
2000 three
2001 four
2002 five
2003 six
2004 seven
2005 eight
2006 nine
2007 ten
2008 "#,
2009 );
2010 cx.update_editor(|editor, cx| {
2011 assert_eq!(
2012 editor.snapshot(cx).scroll_position(),
2013 gpui::Point::new(0., 0.0)
2014 );
2015 });
2016
2017 // Add a cursor below the visible area. Since both cursors cannot fit
2018 // on screen, the editor autoscrolls to reveal the newest cursor, and
2019 // allows the vertical scroll margin below that cursor.
2020 cx.update_editor(|editor, cx| {
2021 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2022 selections.select_ranges([
2023 Point::new(0, 0)..Point::new(0, 0),
2024 Point::new(6, 0)..Point::new(6, 0),
2025 ]);
2026 })
2027 });
2028 cx.update_editor(|editor, cx| {
2029 assert_eq!(
2030 editor.snapshot(cx).scroll_position(),
2031 gpui::Point::new(0., 3.0)
2032 );
2033 });
2034
2035 // Move down. The editor cursor scrolls down to track the newest cursor.
2036 cx.update_editor(|editor, cx| {
2037 editor.move_down(&Default::default(), cx);
2038 });
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 4.0)
2043 );
2044 });
2045
2046 // Add a cursor above the visible area. Since both cursors fit on screen,
2047 // the editor scrolls to show both.
2048 cx.update_editor(|editor, cx| {
2049 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2050 selections.select_ranges([
2051 Point::new(1, 0)..Point::new(1, 0),
2052 Point::new(6, 0)..Point::new(6, 0),
2053 ]);
2054 })
2055 });
2056 cx.update_editor(|editor, cx| {
2057 assert_eq!(
2058 editor.snapshot(cx).scroll_position(),
2059 gpui::Point::new(0., 1.0)
2060 );
2061 });
2062}
2063
2064#[gpui::test]
2065async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2066 init_test(cx, |_| {});
2067 let mut cx = EditorTestContext::new(cx).await;
2068
2069 let line_height = cx.editor(|editor, cx| {
2070 editor
2071 .style()
2072 .unwrap()
2073 .text
2074 .line_height_in_pixels(cx.rem_size())
2075 });
2076 let window = cx.window;
2077 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2078 cx.set_state(
2079 &r#"
2080 ˇone
2081 two
2082 threeˇ
2083 four
2084 five
2085 six
2086 seven
2087 eight
2088 nine
2089 ten
2090 "#
2091 .unindent(),
2092 );
2093
2094 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2095 cx.assert_editor_state(
2096 &r#"
2097 one
2098 two
2099 three
2100 ˇfour
2101 five
2102 sixˇ
2103 seven
2104 eight
2105 nine
2106 ten
2107 "#
2108 .unindent(),
2109 );
2110
2111 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2112 cx.assert_editor_state(
2113 &r#"
2114 one
2115 two
2116 three
2117 four
2118 five
2119 six
2120 ˇseven
2121 eight
2122 nineˇ
2123 ten
2124 "#
2125 .unindent(),
2126 );
2127
2128 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2129 cx.assert_editor_state(
2130 &r#"
2131 one
2132 two
2133 three
2134 ˇfour
2135 five
2136 sixˇ
2137 seven
2138 eight
2139 nine
2140 ten
2141 "#
2142 .unindent(),
2143 );
2144
2145 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2146 cx.assert_editor_state(
2147 &r#"
2148 ˇone
2149 two
2150 threeˇ
2151 four
2152 five
2153 six
2154 seven
2155 eight
2156 nine
2157 ten
2158 "#
2159 .unindent(),
2160 );
2161
2162 // Test select collapsing
2163 cx.update_editor(|editor, cx| {
2164 editor.move_page_down(&MovePageDown::default(), cx);
2165 editor.move_page_down(&MovePageDown::default(), cx);
2166 editor.move_page_down(&MovePageDown::default(), cx);
2167 });
2168 cx.assert_editor_state(
2169 &r#"
2170 one
2171 two
2172 three
2173 four
2174 five
2175 six
2176 seven
2177 eight
2178 nine
2179 ˇten
2180 ˇ"#
2181 .unindent(),
2182 );
2183}
2184
2185#[gpui::test]
2186async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2187 init_test(cx, |_| {});
2188 let mut cx = EditorTestContext::new(cx).await;
2189 cx.set_state("one «two threeˇ» four");
2190 cx.update_editor(|editor, cx| {
2191 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2192 assert_eq!(editor.text(cx), " four");
2193 });
2194}
2195
2196#[gpui::test]
2197fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2198 init_test(cx, |_| {});
2199
2200 let view = cx.add_window(|cx| {
2201 let buffer = MultiBuffer::build_simple("one two three four", cx);
2202 build_editor(buffer.clone(), cx)
2203 });
2204
2205 _ = view.update(cx, |view, cx| {
2206 view.change_selections(None, cx, |s| {
2207 s.select_display_ranges([
2208 // an empty selection - the preceding word fragment is deleted
2209 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2210 // characters selected - they are deleted
2211 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2212 ])
2213 });
2214 view.delete_to_previous_word_start(
2215 &DeleteToPreviousWordStart {
2216 ignore_newlines: false,
2217 },
2218 cx,
2219 );
2220 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2221 });
2222
2223 _ = view.update(cx, |view, cx| {
2224 view.change_selections(None, cx, |s| {
2225 s.select_display_ranges([
2226 // an empty selection - the following word fragment is deleted
2227 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2228 // characters selected - they are deleted
2229 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2230 ])
2231 });
2232 view.delete_to_next_word_end(
2233 &DeleteToNextWordEnd {
2234 ignore_newlines: false,
2235 },
2236 cx,
2237 );
2238 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2239 });
2240}
2241
2242#[gpui::test]
2243fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2244 init_test(cx, |_| {});
2245
2246 let view = cx.add_window(|cx| {
2247 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2248 build_editor(buffer.clone(), cx)
2249 });
2250 let del_to_prev_word_start = DeleteToPreviousWordStart {
2251 ignore_newlines: false,
2252 };
2253 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2254 ignore_newlines: true,
2255 };
2256
2257 _ = view.update(cx, |view, cx| {
2258 view.change_selections(None, cx, |s| {
2259 s.select_display_ranges([
2260 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2261 ])
2262 });
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\nthree\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\nthree");
2267 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2268 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2269 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2271 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2272 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2273 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2274 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2275 });
2276}
2277
2278#[gpui::test]
2279fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2280 init_test(cx, |_| {});
2281
2282 let view = cx.add_window(|cx| {
2283 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2284 build_editor(buffer.clone(), cx)
2285 });
2286 let del_to_next_word_end = DeleteToNextWordEnd {
2287 ignore_newlines: false,
2288 };
2289 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2290 ignore_newlines: true,
2291 };
2292
2293 _ = view.update(cx, |view, cx| {
2294 view.change_selections(None, cx, |s| {
2295 s.select_display_ranges([
2296 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2297 ])
2298 });
2299 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2300 assert_eq!(
2301 view.buffer.read(cx).read(cx).text(),
2302 "one\n two\nthree\n four"
2303 );
2304 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2305 assert_eq!(
2306 view.buffer.read(cx).read(cx).text(),
2307 "\n two\nthree\n four"
2308 );
2309 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2310 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2311 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2312 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2313 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2314 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2315 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2316 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2317 });
2318}
2319
2320#[gpui::test]
2321fn test_newline(cx: &mut TestAppContext) {
2322 init_test(cx, |_| {});
2323
2324 let view = cx.add_window(|cx| {
2325 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2326 build_editor(buffer.clone(), cx)
2327 });
2328
2329 _ = view.update(cx, |view, cx| {
2330 view.change_selections(None, cx, |s| {
2331 s.select_display_ranges([
2332 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2333 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2334 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2335 ])
2336 });
2337
2338 view.newline(&Newline, cx);
2339 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2340 });
2341}
2342
2343#[gpui::test]
2344fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2345 init_test(cx, |_| {});
2346
2347 let editor = cx.add_window(|cx| {
2348 let buffer = MultiBuffer::build_simple(
2349 "
2350 a
2351 b(
2352 X
2353 )
2354 c(
2355 X
2356 )
2357 "
2358 .unindent()
2359 .as_str(),
2360 cx,
2361 );
2362 let mut editor = build_editor(buffer.clone(), cx);
2363 editor.change_selections(None, cx, |s| {
2364 s.select_ranges([
2365 Point::new(2, 4)..Point::new(2, 5),
2366 Point::new(5, 4)..Point::new(5, 5),
2367 ])
2368 });
2369 editor
2370 });
2371
2372 _ = editor.update(cx, |editor, cx| {
2373 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2374 editor.buffer.update(cx, |buffer, cx| {
2375 buffer.edit(
2376 [
2377 (Point::new(1, 2)..Point::new(3, 0), ""),
2378 (Point::new(4, 2)..Point::new(6, 0), ""),
2379 ],
2380 None,
2381 cx,
2382 );
2383 assert_eq!(
2384 buffer.read(cx).text(),
2385 "
2386 a
2387 b()
2388 c()
2389 "
2390 .unindent()
2391 );
2392 });
2393 assert_eq!(
2394 editor.selections.ranges(cx),
2395 &[
2396 Point::new(1, 2)..Point::new(1, 2),
2397 Point::new(2, 2)..Point::new(2, 2),
2398 ],
2399 );
2400
2401 editor.newline(&Newline, cx);
2402 assert_eq!(
2403 editor.text(cx),
2404 "
2405 a
2406 b(
2407 )
2408 c(
2409 )
2410 "
2411 .unindent()
2412 );
2413
2414 // The selections are moved after the inserted newlines
2415 assert_eq!(
2416 editor.selections.ranges(cx),
2417 &[
2418 Point::new(2, 0)..Point::new(2, 0),
2419 Point::new(4, 0)..Point::new(4, 0),
2420 ],
2421 );
2422 });
2423}
2424
2425#[gpui::test]
2426async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2427 init_test(cx, |settings| {
2428 settings.defaults.tab_size = NonZeroU32::new(4)
2429 });
2430
2431 let language = Arc::new(
2432 Language::new(
2433 LanguageConfig::default(),
2434 Some(tree_sitter_rust::LANGUAGE.into()),
2435 )
2436 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2437 .unwrap(),
2438 );
2439
2440 let mut cx = EditorTestContext::new(cx).await;
2441 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2442 cx.set_state(indoc! {"
2443 const a: ˇA = (
2444 (ˇ
2445 «const_functionˇ»(ˇ),
2446 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2447 )ˇ
2448 ˇ);ˇ
2449 "});
2450
2451 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2452 cx.assert_editor_state(indoc! {"
2453 ˇ
2454 const a: A = (
2455 ˇ
2456 (
2457 ˇ
2458 ˇ
2459 const_function(),
2460 ˇ
2461 ˇ
2462 ˇ
2463 ˇ
2464 something_else,
2465 ˇ
2466 )
2467 ˇ
2468 ˇ
2469 );
2470 "});
2471}
2472
2473#[gpui::test]
2474async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2475 init_test(cx, |settings| {
2476 settings.defaults.tab_size = NonZeroU32::new(4)
2477 });
2478
2479 let language = Arc::new(
2480 Language::new(
2481 LanguageConfig::default(),
2482 Some(tree_sitter_rust::LANGUAGE.into()),
2483 )
2484 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2485 .unwrap(),
2486 );
2487
2488 let mut cx = EditorTestContext::new(cx).await;
2489 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2490 cx.set_state(indoc! {"
2491 const a: ˇA = (
2492 (ˇ
2493 «const_functionˇ»(ˇ),
2494 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2495 )ˇ
2496 ˇ);ˇ
2497 "});
2498
2499 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2500 cx.assert_editor_state(indoc! {"
2501 const a: A = (
2502 ˇ
2503 (
2504 ˇ
2505 const_function(),
2506 ˇ
2507 ˇ
2508 something_else,
2509 ˇ
2510 ˇ
2511 ˇ
2512 ˇ
2513 )
2514 ˇ
2515 );
2516 ˇ
2517 ˇ
2518 "});
2519}
2520
2521#[gpui::test]
2522async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2523 init_test(cx, |settings| {
2524 settings.defaults.tab_size = NonZeroU32::new(4)
2525 });
2526
2527 let language = Arc::new(Language::new(
2528 LanguageConfig {
2529 line_comments: vec!["//".into()],
2530 ..LanguageConfig::default()
2531 },
2532 None,
2533 ));
2534 {
2535 let mut cx = EditorTestContext::new(cx).await;
2536 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2537 cx.set_state(indoc! {"
2538 // Fooˇ
2539 "});
2540
2541 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2542 cx.assert_editor_state(indoc! {"
2543 // Foo
2544 //ˇ
2545 "});
2546 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2547 cx.set_state(indoc! {"
2548 ˇ// Foo
2549 "});
2550 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2551 cx.assert_editor_state(indoc! {"
2552
2553 ˇ// Foo
2554 "});
2555 }
2556 // Ensure that comment continuations can be disabled.
2557 update_test_language_settings(cx, |settings| {
2558 settings.defaults.extend_comment_on_newline = Some(false);
2559 });
2560 let mut cx = EditorTestContext::new(cx).await;
2561 cx.set_state(indoc! {"
2562 // Fooˇ
2563 "});
2564 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2565 cx.assert_editor_state(indoc! {"
2566 // Foo
2567 ˇ
2568 "});
2569}
2570
2571#[gpui::test]
2572fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2573 init_test(cx, |_| {});
2574
2575 let editor = cx.add_window(|cx| {
2576 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2577 let mut editor = build_editor(buffer.clone(), cx);
2578 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2579 editor
2580 });
2581
2582 _ = editor.update(cx, |editor, cx| {
2583 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2584 editor.buffer.update(cx, |buffer, cx| {
2585 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2586 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2587 });
2588 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2589
2590 editor.insert("Z", cx);
2591 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2592
2593 // The selections are moved after the inserted characters
2594 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2595 });
2596}
2597
2598#[gpui::test]
2599async fn test_tab(cx: &mut gpui::TestAppContext) {
2600 init_test(cx, |settings| {
2601 settings.defaults.tab_size = NonZeroU32::new(3)
2602 });
2603
2604 let mut cx = EditorTestContext::new(cx).await;
2605 cx.set_state(indoc! {"
2606 ˇabˇc
2607 ˇ🏀ˇ🏀ˇefg
2608 dˇ
2609 "});
2610 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2611 cx.assert_editor_state(indoc! {"
2612 ˇab ˇc
2613 ˇ🏀 ˇ🏀 ˇefg
2614 d ˇ
2615 "});
2616
2617 cx.set_state(indoc! {"
2618 a
2619 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2620 "});
2621 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2622 cx.assert_editor_state(indoc! {"
2623 a
2624 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2625 "});
2626}
2627
2628#[gpui::test]
2629async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2630 init_test(cx, |_| {});
2631
2632 let mut cx = EditorTestContext::new(cx).await;
2633 let language = Arc::new(
2634 Language::new(
2635 LanguageConfig::default(),
2636 Some(tree_sitter_rust::LANGUAGE.into()),
2637 )
2638 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2639 .unwrap(),
2640 );
2641 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2642
2643 // cursors that are already at the suggested indent level insert
2644 // a soft tab. cursors that are to the left of the suggested indent
2645 // auto-indent their line.
2646 cx.set_state(indoc! {"
2647 ˇ
2648 const a: B = (
2649 c(
2650 d(
2651 ˇ
2652 )
2653 ˇ
2654 ˇ )
2655 );
2656 "});
2657 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2658 cx.assert_editor_state(indoc! {"
2659 ˇ
2660 const a: B = (
2661 c(
2662 d(
2663 ˇ
2664 )
2665 ˇ
2666 ˇ)
2667 );
2668 "});
2669
2670 // handle auto-indent when there are multiple cursors on the same line
2671 cx.set_state(indoc! {"
2672 const a: B = (
2673 c(
2674 ˇ ˇ
2675 ˇ )
2676 );
2677 "});
2678 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2679 cx.assert_editor_state(indoc! {"
2680 const a: B = (
2681 c(
2682 ˇ
2683 ˇ)
2684 );
2685 "});
2686}
2687
2688#[gpui::test]
2689async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2690 init_test(cx, |settings| {
2691 settings.defaults.tab_size = NonZeroU32::new(4)
2692 });
2693
2694 let language = Arc::new(
2695 Language::new(
2696 LanguageConfig::default(),
2697 Some(tree_sitter_rust::LANGUAGE.into()),
2698 )
2699 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2700 .unwrap(),
2701 );
2702
2703 let mut cx = EditorTestContext::new(cx).await;
2704 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2705 cx.set_state(indoc! {"
2706 fn a() {
2707 if b {
2708 \t ˇc
2709 }
2710 }
2711 "});
2712
2713 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2714 cx.assert_editor_state(indoc! {"
2715 fn a() {
2716 if b {
2717 ˇc
2718 }
2719 }
2720 "});
2721}
2722
2723#[gpui::test]
2724async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2725 init_test(cx, |settings| {
2726 settings.defaults.tab_size = NonZeroU32::new(4);
2727 });
2728
2729 let mut cx = EditorTestContext::new(cx).await;
2730
2731 cx.set_state(indoc! {"
2732 «oneˇ» «twoˇ»
2733 three
2734 four
2735 "});
2736 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2737 cx.assert_editor_state(indoc! {"
2738 «oneˇ» «twoˇ»
2739 three
2740 four
2741 "});
2742
2743 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2744 cx.assert_editor_state(indoc! {"
2745 «oneˇ» «twoˇ»
2746 three
2747 four
2748 "});
2749
2750 // select across line ending
2751 cx.set_state(indoc! {"
2752 one two
2753 t«hree
2754 ˇ» four
2755 "});
2756 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2757 cx.assert_editor_state(indoc! {"
2758 one two
2759 t«hree
2760 ˇ» four
2761 "});
2762
2763 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2764 cx.assert_editor_state(indoc! {"
2765 one two
2766 t«hree
2767 ˇ» four
2768 "});
2769
2770 // Ensure that indenting/outdenting works when the cursor is at column 0.
2771 cx.set_state(indoc! {"
2772 one two
2773 ˇthree
2774 four
2775 "});
2776 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2777 cx.assert_editor_state(indoc! {"
2778 one two
2779 ˇthree
2780 four
2781 "});
2782
2783 cx.set_state(indoc! {"
2784 one two
2785 ˇ three
2786 four
2787 "});
2788 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2789 cx.assert_editor_state(indoc! {"
2790 one two
2791 ˇthree
2792 four
2793 "});
2794}
2795
2796#[gpui::test]
2797async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2798 init_test(cx, |settings| {
2799 settings.defaults.hard_tabs = Some(true);
2800 });
2801
2802 let mut cx = EditorTestContext::new(cx).await;
2803
2804 // select two ranges on one line
2805 cx.set_state(indoc! {"
2806 «oneˇ» «twoˇ»
2807 three
2808 four
2809 "});
2810 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2811 cx.assert_editor_state(indoc! {"
2812 \t«oneˇ» «twoˇ»
2813 three
2814 four
2815 "});
2816 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2817 cx.assert_editor_state(indoc! {"
2818 \t\t«oneˇ» «twoˇ»
2819 three
2820 four
2821 "});
2822 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2823 cx.assert_editor_state(indoc! {"
2824 \t«oneˇ» «twoˇ»
2825 three
2826 four
2827 "});
2828 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2829 cx.assert_editor_state(indoc! {"
2830 «oneˇ» «twoˇ»
2831 three
2832 four
2833 "});
2834
2835 // select across a line ending
2836 cx.set_state(indoc! {"
2837 one two
2838 t«hree
2839 ˇ»four
2840 "});
2841 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2842 cx.assert_editor_state(indoc! {"
2843 one two
2844 \tt«hree
2845 ˇ»four
2846 "});
2847 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2848 cx.assert_editor_state(indoc! {"
2849 one two
2850 \t\tt«hree
2851 ˇ»four
2852 "});
2853 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2854 cx.assert_editor_state(indoc! {"
2855 one two
2856 \tt«hree
2857 ˇ»four
2858 "});
2859 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2860 cx.assert_editor_state(indoc! {"
2861 one two
2862 t«hree
2863 ˇ»four
2864 "});
2865
2866 // Ensure that indenting/outdenting works when the cursor is at column 0.
2867 cx.set_state(indoc! {"
2868 one two
2869 ˇthree
2870 four
2871 "});
2872 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2873 cx.assert_editor_state(indoc! {"
2874 one two
2875 ˇthree
2876 four
2877 "});
2878 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2879 cx.assert_editor_state(indoc! {"
2880 one two
2881 \tˇthree
2882 four
2883 "});
2884 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2885 cx.assert_editor_state(indoc! {"
2886 one two
2887 ˇthree
2888 four
2889 "});
2890}
2891
2892#[gpui::test]
2893fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2894 init_test(cx, |settings| {
2895 settings.languages.extend([
2896 (
2897 "TOML".into(),
2898 LanguageSettingsContent {
2899 tab_size: NonZeroU32::new(2),
2900 ..Default::default()
2901 },
2902 ),
2903 (
2904 "Rust".into(),
2905 LanguageSettingsContent {
2906 tab_size: NonZeroU32::new(4),
2907 ..Default::default()
2908 },
2909 ),
2910 ]);
2911 });
2912
2913 let toml_language = Arc::new(Language::new(
2914 LanguageConfig {
2915 name: "TOML".into(),
2916 ..Default::default()
2917 },
2918 None,
2919 ));
2920 let rust_language = Arc::new(Language::new(
2921 LanguageConfig {
2922 name: "Rust".into(),
2923 ..Default::default()
2924 },
2925 None,
2926 ));
2927
2928 let toml_buffer =
2929 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2930 let rust_buffer = cx.new_model(|cx| {
2931 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2932 });
2933 let multibuffer = cx.new_model(|cx| {
2934 let mut multibuffer = MultiBuffer::new(ReadWrite);
2935 multibuffer.push_excerpts(
2936 toml_buffer.clone(),
2937 [ExcerptRange {
2938 context: Point::new(0, 0)..Point::new(2, 0),
2939 primary: None,
2940 }],
2941 cx,
2942 );
2943 multibuffer.push_excerpts(
2944 rust_buffer.clone(),
2945 [ExcerptRange {
2946 context: Point::new(0, 0)..Point::new(1, 0),
2947 primary: None,
2948 }],
2949 cx,
2950 );
2951 multibuffer
2952 });
2953
2954 cx.add_window(|cx| {
2955 let mut editor = build_editor(multibuffer, cx);
2956
2957 assert_eq!(
2958 editor.text(cx),
2959 indoc! {"
2960 a = 1
2961 b = 2
2962
2963 const c: usize = 3;
2964 "}
2965 );
2966
2967 select_ranges(
2968 &mut editor,
2969 indoc! {"
2970 «aˇ» = 1
2971 b = 2
2972
2973 «const c:ˇ» usize = 3;
2974 "},
2975 cx,
2976 );
2977
2978 editor.tab(&Tab, cx);
2979 assert_text_with_selections(
2980 &mut editor,
2981 indoc! {"
2982 «aˇ» = 1
2983 b = 2
2984
2985 «const c:ˇ» usize = 3;
2986 "},
2987 cx,
2988 );
2989 editor.tab_prev(&TabPrev, cx);
2990 assert_text_with_selections(
2991 &mut editor,
2992 indoc! {"
2993 «aˇ» = 1
2994 b = 2
2995
2996 «const c:ˇ» usize = 3;
2997 "},
2998 cx,
2999 );
3000
3001 editor
3002 });
3003}
3004
3005#[gpui::test]
3006async fn test_backspace(cx: &mut gpui::TestAppContext) {
3007 init_test(cx, |_| {});
3008
3009 let mut cx = EditorTestContext::new(cx).await;
3010
3011 // Basic backspace
3012 cx.set_state(indoc! {"
3013 onˇe two three
3014 fou«rˇ» five six
3015 seven «ˇeight nine
3016 »ten
3017 "});
3018 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3019 cx.assert_editor_state(indoc! {"
3020 oˇe two three
3021 fouˇ five six
3022 seven ˇten
3023 "});
3024
3025 // Test backspace inside and around indents
3026 cx.set_state(indoc! {"
3027 zero
3028 ˇone
3029 ˇtwo
3030 ˇ ˇ ˇ three
3031 ˇ ˇ four
3032 "});
3033 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3034 cx.assert_editor_state(indoc! {"
3035 zero
3036 ˇone
3037 ˇtwo
3038 ˇ threeˇ four
3039 "});
3040
3041 // Test backspace with line_mode set to true
3042 cx.update_editor(|e, _| e.selections.line_mode = true);
3043 cx.set_state(indoc! {"
3044 The ˇquick ˇbrown
3045 fox jumps over
3046 the lazy dog
3047 ˇThe qu«ick bˇ»rown"});
3048 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3049 cx.assert_editor_state(indoc! {"
3050 ˇfox jumps over
3051 the lazy dogˇ"});
3052}
3053
3054#[gpui::test]
3055async fn test_delete(cx: &mut gpui::TestAppContext) {
3056 init_test(cx, |_| {});
3057
3058 let mut cx = EditorTestContext::new(cx).await;
3059 cx.set_state(indoc! {"
3060 onˇe two three
3061 fou«rˇ» five six
3062 seven «ˇeight nine
3063 »ten
3064 "});
3065 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3066 cx.assert_editor_state(indoc! {"
3067 onˇ two three
3068 fouˇ five six
3069 seven ˇten
3070 "});
3071
3072 // Test backspace with line_mode set to true
3073 cx.update_editor(|e, _| e.selections.line_mode = true);
3074 cx.set_state(indoc! {"
3075 The ˇquick ˇbrown
3076 fox «ˇjum»ps over
3077 the lazy dog
3078 ˇThe qu«ick bˇ»rown"});
3079 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3080 cx.assert_editor_state("ˇthe lazy dogˇ");
3081}
3082
3083#[gpui::test]
3084fn test_delete_line(cx: &mut TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let view = cx.add_window(|cx| {
3088 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3089 build_editor(buffer, cx)
3090 });
3091 _ = view.update(cx, |view, cx| {
3092 view.change_selections(None, cx, |s| {
3093 s.select_display_ranges([
3094 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3095 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3096 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3097 ])
3098 });
3099 view.delete_line(&DeleteLine, cx);
3100 assert_eq!(view.display_text(cx), "ghi");
3101 assert_eq!(
3102 view.selections.display_ranges(cx),
3103 vec![
3104 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3105 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3106 ]
3107 );
3108 });
3109
3110 let view = cx.add_window(|cx| {
3111 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3112 build_editor(buffer, cx)
3113 });
3114 _ = view.update(cx, |view, cx| {
3115 view.change_selections(None, cx, |s| {
3116 s.select_display_ranges([
3117 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3118 ])
3119 });
3120 view.delete_line(&DeleteLine, cx);
3121 assert_eq!(view.display_text(cx), "ghi\n");
3122 assert_eq!(
3123 view.selections.display_ranges(cx),
3124 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3125 );
3126 });
3127}
3128
3129#[gpui::test]
3130fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3131 init_test(cx, |_| {});
3132
3133 cx.add_window(|cx| {
3134 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3135 let mut editor = build_editor(buffer.clone(), cx);
3136 let buffer = buffer.read(cx).as_singleton().unwrap();
3137
3138 assert_eq!(
3139 editor.selections.ranges::<Point>(cx),
3140 &[Point::new(0, 0)..Point::new(0, 0)]
3141 );
3142
3143 // When on single line, replace newline at end by space
3144 editor.join_lines(&JoinLines, cx);
3145 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3146 assert_eq!(
3147 editor.selections.ranges::<Point>(cx),
3148 &[Point::new(0, 3)..Point::new(0, 3)]
3149 );
3150
3151 // When multiple lines are selected, remove newlines that are spanned by the selection
3152 editor.change_selections(None, cx, |s| {
3153 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3154 });
3155 editor.join_lines(&JoinLines, cx);
3156 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3157 assert_eq!(
3158 editor.selections.ranges::<Point>(cx),
3159 &[Point::new(0, 11)..Point::new(0, 11)]
3160 );
3161
3162 // Undo should be transactional
3163 editor.undo(&Undo, cx);
3164 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3165 assert_eq!(
3166 editor.selections.ranges::<Point>(cx),
3167 &[Point::new(0, 5)..Point::new(2, 2)]
3168 );
3169
3170 // When joining an empty line don't insert a space
3171 editor.change_selections(None, cx, |s| {
3172 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3173 });
3174 editor.join_lines(&JoinLines, cx);
3175 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3176 assert_eq!(
3177 editor.selections.ranges::<Point>(cx),
3178 [Point::new(2, 3)..Point::new(2, 3)]
3179 );
3180
3181 // We can remove trailing newlines
3182 editor.join_lines(&JoinLines, cx);
3183 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3184 assert_eq!(
3185 editor.selections.ranges::<Point>(cx),
3186 [Point::new(2, 3)..Point::new(2, 3)]
3187 );
3188
3189 // We don't blow up on the last line
3190 editor.join_lines(&JoinLines, cx);
3191 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3192 assert_eq!(
3193 editor.selections.ranges::<Point>(cx),
3194 [Point::new(2, 3)..Point::new(2, 3)]
3195 );
3196
3197 // reset to test indentation
3198 editor.buffer.update(cx, |buffer, cx| {
3199 buffer.edit(
3200 [
3201 (Point::new(1, 0)..Point::new(1, 2), " "),
3202 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3203 ],
3204 None,
3205 cx,
3206 )
3207 });
3208
3209 // We remove any leading spaces
3210 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3211 editor.change_selections(None, cx, |s| {
3212 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3213 });
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3216
3217 // We don't insert a space for a line containing only spaces
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3220
3221 // We ignore any leading tabs
3222 editor.join_lines(&JoinLines, cx);
3223 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3224
3225 editor
3226 });
3227}
3228
3229#[gpui::test]
3230fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3231 init_test(cx, |_| {});
3232
3233 cx.add_window(|cx| {
3234 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3235 let mut editor = build_editor(buffer.clone(), cx);
3236 let buffer = buffer.read(cx).as_singleton().unwrap();
3237
3238 editor.change_selections(None, cx, |s| {
3239 s.select_ranges([
3240 Point::new(0, 2)..Point::new(1, 1),
3241 Point::new(1, 2)..Point::new(1, 2),
3242 Point::new(3, 1)..Point::new(3, 2),
3243 ])
3244 });
3245
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3248
3249 assert_eq!(
3250 editor.selections.ranges::<Point>(cx),
3251 [
3252 Point::new(0, 7)..Point::new(0, 7),
3253 Point::new(1, 3)..Point::new(1, 3)
3254 ]
3255 );
3256 editor
3257 });
3258}
3259
3260#[gpui::test]
3261async fn test_join_lines_with_git_diff_base(
3262 executor: BackgroundExecutor,
3263 cx: &mut gpui::TestAppContext,
3264) {
3265 init_test(cx, |_| {});
3266
3267 let mut cx = EditorTestContext::new(cx).await;
3268
3269 let diff_base = r#"
3270 Line 0
3271 Line 1
3272 Line 2
3273 Line 3
3274 "#
3275 .unindent();
3276
3277 cx.set_state(
3278 &r#"
3279 ˇLine 0
3280 Line 1
3281 Line 2
3282 Line 3
3283 "#
3284 .unindent(),
3285 );
3286
3287 cx.set_diff_base(Some(&diff_base));
3288 executor.run_until_parked();
3289
3290 // Join lines
3291 cx.update_editor(|editor, cx| {
3292 editor.join_lines(&JoinLines, cx);
3293 });
3294 executor.run_until_parked();
3295
3296 cx.assert_editor_state(
3297 &r#"
3298 Line 0ˇ Line 1
3299 Line 2
3300 Line 3
3301 "#
3302 .unindent(),
3303 );
3304 // Join again
3305 cx.update_editor(|editor, cx| {
3306 editor.join_lines(&JoinLines, cx);
3307 });
3308 executor.run_until_parked();
3309
3310 cx.assert_editor_state(
3311 &r#"
3312 Line 0 Line 1ˇ Line 2
3313 Line 3
3314 "#
3315 .unindent(),
3316 );
3317}
3318
3319#[gpui::test]
3320async fn test_custom_newlines_cause_no_false_positive_diffs(
3321 executor: BackgroundExecutor,
3322 cx: &mut gpui::TestAppContext,
3323) {
3324 init_test(cx, |_| {});
3325 let mut cx = EditorTestContext::new(cx).await;
3326 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3327 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3328 executor.run_until_parked();
3329
3330 cx.update_editor(|editor, cx| {
3331 assert_eq!(
3332 editor
3333 .buffer()
3334 .read(cx)
3335 .snapshot(cx)
3336 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3337 .collect::<Vec<_>>(),
3338 Vec::new(),
3339 "Should not have any diffs for files with custom newlines"
3340 );
3341 });
3342}
3343
3344#[gpui::test]
3345async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3346 init_test(cx, |_| {});
3347
3348 let mut cx = EditorTestContext::new(cx).await;
3349
3350 // Test sort_lines_case_insensitive()
3351 cx.set_state(indoc! {"
3352 «z
3353 y
3354 x
3355 Z
3356 Y
3357 Xˇ»
3358 "});
3359 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3360 cx.assert_editor_state(indoc! {"
3361 «x
3362 X
3363 y
3364 Y
3365 z
3366 Zˇ»
3367 "});
3368
3369 // Test reverse_lines()
3370 cx.set_state(indoc! {"
3371 «5
3372 4
3373 3
3374 2
3375 1ˇ»
3376 "});
3377 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3378 cx.assert_editor_state(indoc! {"
3379 «1
3380 2
3381 3
3382 4
3383 5ˇ»
3384 "});
3385
3386 // Skip testing shuffle_line()
3387
3388 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3389 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3390
3391 // Don't manipulate when cursor is on single line, but expand the selection
3392 cx.set_state(indoc! {"
3393 ddˇdd
3394 ccc
3395 bb
3396 a
3397 "});
3398 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3399 cx.assert_editor_state(indoc! {"
3400 «ddddˇ»
3401 ccc
3402 bb
3403 a
3404 "});
3405
3406 // Basic manipulate case
3407 // Start selection moves to column 0
3408 // End of selection shrinks to fit shorter line
3409 cx.set_state(indoc! {"
3410 dd«d
3411 ccc
3412 bb
3413 aaaaaˇ»
3414 "});
3415 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3416 cx.assert_editor_state(indoc! {"
3417 «aaaaa
3418 bb
3419 ccc
3420 dddˇ»
3421 "});
3422
3423 // Manipulate case with newlines
3424 cx.set_state(indoc! {"
3425 dd«d
3426 ccc
3427
3428 bb
3429 aaaaa
3430
3431 ˇ»
3432 "});
3433 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3434 cx.assert_editor_state(indoc! {"
3435 «
3436
3437 aaaaa
3438 bb
3439 ccc
3440 dddˇ»
3441
3442 "});
3443
3444 // Adding new line
3445 cx.set_state(indoc! {"
3446 aa«a
3447 bbˇ»b
3448 "});
3449 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3450 cx.assert_editor_state(indoc! {"
3451 «aaa
3452 bbb
3453 added_lineˇ»
3454 "});
3455
3456 // Removing line
3457 cx.set_state(indoc! {"
3458 aa«a
3459 bbbˇ»
3460 "});
3461 cx.update_editor(|e, cx| {
3462 e.manipulate_lines(cx, |lines| {
3463 lines.pop();
3464 })
3465 });
3466 cx.assert_editor_state(indoc! {"
3467 «aaaˇ»
3468 "});
3469
3470 // Removing all lines
3471 cx.set_state(indoc! {"
3472 aa«a
3473 bbbˇ»
3474 "});
3475 cx.update_editor(|e, cx| {
3476 e.manipulate_lines(cx, |lines| {
3477 lines.drain(..);
3478 })
3479 });
3480 cx.assert_editor_state(indoc! {"
3481 ˇ
3482 "});
3483}
3484
3485#[gpui::test]
3486async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3487 init_test(cx, |_| {});
3488
3489 let mut cx = EditorTestContext::new(cx).await;
3490
3491 // Consider continuous selection as single selection
3492 cx.set_state(indoc! {"
3493 Aaa«aa
3494 cˇ»c«c
3495 bb
3496 aaaˇ»aa
3497 "});
3498 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3499 cx.assert_editor_state(indoc! {"
3500 «Aaaaa
3501 ccc
3502 bb
3503 aaaaaˇ»
3504 "});
3505
3506 cx.set_state(indoc! {"
3507 Aaa«aa
3508 cˇ»c«c
3509 bb
3510 aaaˇ»aa
3511 "});
3512 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3513 cx.assert_editor_state(indoc! {"
3514 «Aaaaa
3515 ccc
3516 bbˇ»
3517 "});
3518
3519 // Consider non continuous selection as distinct dedup operations
3520 cx.set_state(indoc! {"
3521 «aaaaa
3522 bb
3523 aaaaa
3524 aaaaaˇ»
3525
3526 aaa«aaˇ»
3527 "});
3528 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3529 cx.assert_editor_state(indoc! {"
3530 «aaaaa
3531 bbˇ»
3532
3533 «aaaaaˇ»
3534 "});
3535}
3536
3537#[gpui::test]
3538async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3539 init_test(cx, |_| {});
3540
3541 let mut cx = EditorTestContext::new(cx).await;
3542
3543 cx.set_state(indoc! {"
3544 «Aaa
3545 aAa
3546 Aaaˇ»
3547 "});
3548 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3549 cx.assert_editor_state(indoc! {"
3550 «Aaa
3551 aAaˇ»
3552 "});
3553
3554 cx.set_state(indoc! {"
3555 «Aaa
3556 aAa
3557 aaAˇ»
3558 "});
3559 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3560 cx.assert_editor_state(indoc! {"
3561 «Aaaˇ»
3562 "});
3563}
3564
3565#[gpui::test]
3566async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 // Manipulate with multiple selections on a single line
3572 cx.set_state(indoc! {"
3573 dd«dd
3574 cˇ»c«c
3575 bb
3576 aaaˇ»aa
3577 "});
3578 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3579 cx.assert_editor_state(indoc! {"
3580 «aaaaa
3581 bb
3582 ccc
3583 ddddˇ»
3584 "});
3585
3586 // Manipulate with multiple disjoin selections
3587 cx.set_state(indoc! {"
3588 5«
3589 4
3590 3
3591 2
3592 1ˇ»
3593
3594 dd«dd
3595 ccc
3596 bb
3597 aaaˇ»aa
3598 "});
3599 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3600 cx.assert_editor_state(indoc! {"
3601 «1
3602 2
3603 3
3604 4
3605 5ˇ»
3606
3607 «aaaaa
3608 bb
3609 ccc
3610 ddddˇ»
3611 "});
3612
3613 // Adding lines on each selection
3614 cx.set_state(indoc! {"
3615 2«
3616 1ˇ»
3617
3618 bb«bb
3619 aaaˇ»aa
3620 "});
3621 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3622 cx.assert_editor_state(indoc! {"
3623 «2
3624 1
3625 added lineˇ»
3626
3627 «bbbb
3628 aaaaa
3629 added lineˇ»
3630 "});
3631
3632 // Removing lines on each selection
3633 cx.set_state(indoc! {"
3634 2«
3635 1ˇ»
3636
3637 bb«bb
3638 aaaˇ»aa
3639 "});
3640 cx.update_editor(|e, cx| {
3641 e.manipulate_lines(cx, |lines| {
3642 lines.pop();
3643 })
3644 });
3645 cx.assert_editor_state(indoc! {"
3646 «2ˇ»
3647
3648 «bbbbˇ»
3649 "});
3650}
3651
3652#[gpui::test]
3653async fn test_manipulate_text(cx: &mut TestAppContext) {
3654 init_test(cx, |_| {});
3655
3656 let mut cx = EditorTestContext::new(cx).await;
3657
3658 // Test convert_to_upper_case()
3659 cx.set_state(indoc! {"
3660 «hello worldˇ»
3661 "});
3662 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3663 cx.assert_editor_state(indoc! {"
3664 «HELLO WORLDˇ»
3665 "});
3666
3667 // Test convert_to_lower_case()
3668 cx.set_state(indoc! {"
3669 «HELLO WORLDˇ»
3670 "});
3671 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3672 cx.assert_editor_state(indoc! {"
3673 «hello worldˇ»
3674 "});
3675
3676 // Test multiple line, single selection case
3677 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3678 cx.set_state(indoc! {"
3679 «The quick brown
3680 fox jumps over
3681 the lazy dogˇ»
3682 "});
3683 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3684 cx.assert_editor_state(indoc! {"
3685 «The Quick Brown
3686 Fox Jumps Over
3687 The Lazy Dogˇ»
3688 "});
3689
3690 // Test multiple line, single selection case
3691 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3692 cx.set_state(indoc! {"
3693 «The quick brown
3694 fox jumps over
3695 the lazy dogˇ»
3696 "});
3697 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3698 cx.assert_editor_state(indoc! {"
3699 «TheQuickBrown
3700 FoxJumpsOver
3701 TheLazyDogˇ»
3702 "});
3703
3704 // From here on out, test more complex cases of manipulate_text()
3705
3706 // Test no selection case - should affect words cursors are in
3707 // Cursor at beginning, middle, and end of word
3708 cx.set_state(indoc! {"
3709 ˇhello big beauˇtiful worldˇ
3710 "});
3711 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3712 cx.assert_editor_state(indoc! {"
3713 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3714 "});
3715
3716 // Test multiple selections on a single line and across multiple lines
3717 cx.set_state(indoc! {"
3718 «Theˇ» quick «brown
3719 foxˇ» jumps «overˇ»
3720 the «lazyˇ» dog
3721 "});
3722 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3723 cx.assert_editor_state(indoc! {"
3724 «THEˇ» quick «BROWN
3725 FOXˇ» jumps «OVERˇ»
3726 the «LAZYˇ» dog
3727 "});
3728
3729 // Test case where text length grows
3730 cx.set_state(indoc! {"
3731 «tschüߡ»
3732 "});
3733 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3734 cx.assert_editor_state(indoc! {"
3735 «TSCHÜSSˇ»
3736 "});
3737
3738 // Test to make sure we don't crash when text shrinks
3739 cx.set_state(indoc! {"
3740 aaa_bbbˇ
3741 "});
3742 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3743 cx.assert_editor_state(indoc! {"
3744 «aaaBbbˇ»
3745 "});
3746
3747 // Test to make sure we all aware of the fact that each word can grow and shrink
3748 // Final selections should be aware of this fact
3749 cx.set_state(indoc! {"
3750 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3751 "});
3752 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3753 cx.assert_editor_state(indoc! {"
3754 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3755 "});
3756
3757 cx.set_state(indoc! {"
3758 «hElLo, WoRld!ˇ»
3759 "});
3760 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3761 cx.assert_editor_state(indoc! {"
3762 «HeLlO, wOrLD!ˇ»
3763 "});
3764}
3765
3766#[gpui::test]
3767fn test_duplicate_line(cx: &mut TestAppContext) {
3768 init_test(cx, |_| {});
3769
3770 let view = cx.add_window(|cx| {
3771 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3772 build_editor(buffer, cx)
3773 });
3774 _ = view.update(cx, |view, cx| {
3775 view.change_selections(None, cx, |s| {
3776 s.select_display_ranges([
3777 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3778 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3779 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3780 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3781 ])
3782 });
3783 view.duplicate_line_down(&DuplicateLineDown, cx);
3784 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3785 assert_eq!(
3786 view.selections.display_ranges(cx),
3787 vec![
3788 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3789 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3790 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3791 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3792 ]
3793 );
3794 });
3795
3796 let view = cx.add_window(|cx| {
3797 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3798 build_editor(buffer, cx)
3799 });
3800 _ = view.update(cx, |view, cx| {
3801 view.change_selections(None, cx, |s| {
3802 s.select_display_ranges([
3803 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3804 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3805 ])
3806 });
3807 view.duplicate_line_down(&DuplicateLineDown, cx);
3808 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3809 assert_eq!(
3810 view.selections.display_ranges(cx),
3811 vec![
3812 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3813 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3814 ]
3815 );
3816 });
3817
3818 // With `move_upwards` the selections stay in place, except for
3819 // the lines inserted above them
3820 let view = cx.add_window(|cx| {
3821 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3822 build_editor(buffer, cx)
3823 });
3824 _ = view.update(cx, |view, cx| {
3825 view.change_selections(None, cx, |s| {
3826 s.select_display_ranges([
3827 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3828 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3829 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3830 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3831 ])
3832 });
3833 view.duplicate_line_up(&DuplicateLineUp, cx);
3834 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3835 assert_eq!(
3836 view.selections.display_ranges(cx),
3837 vec![
3838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3839 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3840 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3841 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3842 ]
3843 );
3844 });
3845
3846 let view = cx.add_window(|cx| {
3847 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3848 build_editor(buffer, cx)
3849 });
3850 _ = view.update(cx, |view, cx| {
3851 view.change_selections(None, cx, |s| {
3852 s.select_display_ranges([
3853 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3854 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3855 ])
3856 });
3857 view.duplicate_line_up(&DuplicateLineUp, cx);
3858 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3859 assert_eq!(
3860 view.selections.display_ranges(cx),
3861 vec![
3862 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3863 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3864 ]
3865 );
3866 });
3867}
3868
3869#[gpui::test]
3870fn test_move_line_up_down(cx: &mut TestAppContext) {
3871 init_test(cx, |_| {});
3872
3873 let view = cx.add_window(|cx| {
3874 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3875 build_editor(buffer, cx)
3876 });
3877 _ = view.update(cx, |view, cx| {
3878 view.fold_ranges(
3879 vec![
3880 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3881 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3882 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3883 ],
3884 true,
3885 cx,
3886 );
3887 view.change_selections(None, cx, |s| {
3888 s.select_display_ranges([
3889 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3890 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3891 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3892 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3893 ])
3894 });
3895 assert_eq!(
3896 view.display_text(cx),
3897 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3898 );
3899
3900 view.move_line_up(&MoveLineUp, cx);
3901 assert_eq!(
3902 view.display_text(cx),
3903 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3904 );
3905 assert_eq!(
3906 view.selections.display_ranges(cx),
3907 vec![
3908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3909 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3910 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3911 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3912 ]
3913 );
3914 });
3915
3916 _ = view.update(cx, |view, cx| {
3917 view.move_line_down(&MoveLineDown, cx);
3918 assert_eq!(
3919 view.display_text(cx),
3920 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3921 );
3922 assert_eq!(
3923 view.selections.display_ranges(cx),
3924 vec![
3925 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3926 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3927 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3928 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3929 ]
3930 );
3931 });
3932
3933 _ = view.update(cx, |view, cx| {
3934 view.move_line_down(&MoveLineDown, cx);
3935 assert_eq!(
3936 view.display_text(cx),
3937 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3938 );
3939 assert_eq!(
3940 view.selections.display_ranges(cx),
3941 vec![
3942 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3943 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3944 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3945 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3946 ]
3947 );
3948 });
3949
3950 _ = view.update(cx, |view, cx| {
3951 view.move_line_up(&MoveLineUp, cx);
3952 assert_eq!(
3953 view.display_text(cx),
3954 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3955 );
3956 assert_eq!(
3957 view.selections.display_ranges(cx),
3958 vec![
3959 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3960 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3961 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3962 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3963 ]
3964 );
3965 });
3966}
3967
3968#[gpui::test]
3969fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3970 init_test(cx, |_| {});
3971
3972 let editor = cx.add_window(|cx| {
3973 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3974 build_editor(buffer, cx)
3975 });
3976 _ = editor.update(cx, |editor, cx| {
3977 let snapshot = editor.buffer.read(cx).snapshot(cx);
3978 editor.insert_blocks(
3979 [BlockProperties {
3980 style: BlockStyle::Fixed,
3981 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3982 height: 1,
3983 render: Box::new(|_| div().into_any()),
3984 priority: 0,
3985 }],
3986 Some(Autoscroll::fit()),
3987 cx,
3988 );
3989 editor.change_selections(None, cx, |s| {
3990 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3991 });
3992 editor.move_line_down(&MoveLineDown, cx);
3993 });
3994}
3995
3996#[gpui::test]
3997async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
3998 init_test(cx, |_| {});
3999
4000 let mut cx = EditorTestContext::new(cx).await;
4001 cx.set_state(
4002 &"
4003 ˇzero
4004 one
4005 two
4006 three
4007 four
4008 five
4009 "
4010 .unindent(),
4011 );
4012
4013 // Create a four-line block that replaces three lines of text.
4014 cx.update_editor(|editor, cx| {
4015 let snapshot = editor.snapshot(cx);
4016 let snapshot = &snapshot.buffer_snapshot;
4017 let placement = BlockPlacement::Replace(
4018 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4019 );
4020 editor.insert_blocks(
4021 [BlockProperties {
4022 placement,
4023 height: 4,
4024 style: BlockStyle::Sticky,
4025 render: Box::new(|_| gpui::div().into_any_element()),
4026 priority: 0,
4027 }],
4028 None,
4029 cx,
4030 );
4031 });
4032
4033 // Move down so that the cursor touches the block.
4034 cx.update_editor(|editor, cx| {
4035 editor.move_down(&Default::default(), cx);
4036 });
4037 cx.assert_editor_state(
4038 &"
4039 zero
4040 «one
4041 two
4042 threeˇ»
4043 four
4044 five
4045 "
4046 .unindent(),
4047 );
4048
4049 // Move down past the block.
4050 cx.update_editor(|editor, cx| {
4051 editor.move_down(&Default::default(), cx);
4052 });
4053 cx.assert_editor_state(
4054 &"
4055 zero
4056 one
4057 two
4058 three
4059 ˇfour
4060 five
4061 "
4062 .unindent(),
4063 );
4064}
4065
4066#[gpui::test]
4067fn test_transpose(cx: &mut TestAppContext) {
4068 init_test(cx, |_| {});
4069
4070 _ = cx.add_window(|cx| {
4071 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4072 editor.set_style(EditorStyle::default(), cx);
4073 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4074 editor.transpose(&Default::default(), cx);
4075 assert_eq!(editor.text(cx), "bac");
4076 assert_eq!(editor.selections.ranges(cx), [2..2]);
4077
4078 editor.transpose(&Default::default(), cx);
4079 assert_eq!(editor.text(cx), "bca");
4080 assert_eq!(editor.selections.ranges(cx), [3..3]);
4081
4082 editor.transpose(&Default::default(), cx);
4083 assert_eq!(editor.text(cx), "bac");
4084 assert_eq!(editor.selections.ranges(cx), [3..3]);
4085
4086 editor
4087 });
4088
4089 _ = cx.add_window(|cx| {
4090 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4091 editor.set_style(EditorStyle::default(), cx);
4092 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4093 editor.transpose(&Default::default(), cx);
4094 assert_eq!(editor.text(cx), "acb\nde");
4095 assert_eq!(editor.selections.ranges(cx), [3..3]);
4096
4097 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4098 editor.transpose(&Default::default(), cx);
4099 assert_eq!(editor.text(cx), "acbd\ne");
4100 assert_eq!(editor.selections.ranges(cx), [5..5]);
4101
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "acbde\n");
4104 assert_eq!(editor.selections.ranges(cx), [6..6]);
4105
4106 editor.transpose(&Default::default(), cx);
4107 assert_eq!(editor.text(cx), "acbd\ne");
4108 assert_eq!(editor.selections.ranges(cx), [6..6]);
4109
4110 editor
4111 });
4112
4113 _ = cx.add_window(|cx| {
4114 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4115 editor.set_style(EditorStyle::default(), cx);
4116 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4117 editor.transpose(&Default::default(), cx);
4118 assert_eq!(editor.text(cx), "bacd\ne");
4119 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4120
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "bcade\n");
4123 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4124
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bcda\ne");
4127 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bcade\n");
4131 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4132
4133 editor.transpose(&Default::default(), cx);
4134 assert_eq!(editor.text(cx), "bcaed\n");
4135 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4136
4137 editor
4138 });
4139
4140 _ = cx.add_window(|cx| {
4141 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4142 editor.set_style(EditorStyle::default(), cx);
4143 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "🏀🍐✋");
4146 assert_eq!(editor.selections.ranges(cx), [8..8]);
4147
4148 editor.transpose(&Default::default(), cx);
4149 assert_eq!(editor.text(cx), "🏀✋🍐");
4150 assert_eq!(editor.selections.ranges(cx), [11..11]);
4151
4152 editor.transpose(&Default::default(), cx);
4153 assert_eq!(editor.text(cx), "🏀🍐✋");
4154 assert_eq!(editor.selections.ranges(cx), [11..11]);
4155
4156 editor
4157 });
4158}
4159
4160#[gpui::test]
4161async fn test_rewrap(cx: &mut TestAppContext) {
4162 init_test(cx, |_| {});
4163
4164 let mut cx = EditorTestContext::new(cx).await;
4165
4166 let language_with_c_comments = Arc::new(Language::new(
4167 LanguageConfig {
4168 line_comments: vec!["// ".into()],
4169 ..LanguageConfig::default()
4170 },
4171 None,
4172 ));
4173 let language_with_pound_comments = Arc::new(Language::new(
4174 LanguageConfig {
4175 line_comments: vec!["# ".into()],
4176 ..LanguageConfig::default()
4177 },
4178 None,
4179 ));
4180 let markdown_language = Arc::new(Language::new(
4181 LanguageConfig {
4182 name: "Markdown".into(),
4183 ..LanguageConfig::default()
4184 },
4185 None,
4186 ));
4187 let language_with_doc_comments = Arc::new(Language::new(
4188 LanguageConfig {
4189 line_comments: vec!["// ".into(), "/// ".into()],
4190 ..LanguageConfig::default()
4191 },
4192 Some(tree_sitter_rust::LANGUAGE.into()),
4193 ));
4194
4195 let plaintext_language = Arc::new(Language::new(
4196 LanguageConfig {
4197 name: "Plain Text".into(),
4198 ..LanguageConfig::default()
4199 },
4200 None,
4201 ));
4202
4203 assert_rewrap(
4204 indoc! {"
4205 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. 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.
4206 "},
4207 indoc! {"
4208 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4209 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4210 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4211 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4212 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4213 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4214 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4215 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4216 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4217 // porttitor id. Aliquam id accumsan eros.
4218 "},
4219 language_with_c_comments.clone(),
4220 &mut cx,
4221 );
4222
4223 // Test that rewrapping works inside of a selection
4224 assert_rewrap(
4225 indoc! {"
4226 «// 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.ˇ»
4227 "},
4228 indoc! {"
4229 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4230 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4231 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4232 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4233 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4234 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4235 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4236 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4237 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4238 // porttitor id. Aliquam id accumsan eros.ˇ»
4239 "},
4240 language_with_c_comments.clone(),
4241 &mut cx,
4242 );
4243
4244 // Test that cursors that expand to the same region are collapsed.
4245 assert_rewrap(
4246 indoc! {"
4247 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4248 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4249 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4250 // ˇ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.
4251 "},
4252 indoc! {"
4253 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4254 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4255 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4256 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4257 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4258 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4259 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4260 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4261 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4262 // porttitor id. Aliquam id accumsan eros.
4263 "},
4264 language_with_c_comments.clone(),
4265 &mut cx,
4266 );
4267
4268 // Test that non-contiguous selections are treated separately.
4269 assert_rewrap(
4270 indoc! {"
4271 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4272 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4273 //
4274 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4275 // ˇ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.
4276 "},
4277 indoc! {"
4278 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4279 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4280 // auctor, eu lacinia sapien scelerisque.
4281 //
4282 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4283 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4284 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4285 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4286 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4287 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4288 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4289 "},
4290 language_with_c_comments.clone(),
4291 &mut cx,
4292 );
4293
4294 // Test that different comment prefixes are supported.
4295 assert_rewrap(
4296 indoc! {"
4297 # ˇ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.
4298 "},
4299 indoc! {"
4300 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4301 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4302 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4303 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4304 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4305 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4306 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4307 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4308 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4309 # accumsan eros.
4310 "},
4311 language_with_pound_comments.clone(),
4312 &mut cx,
4313 );
4314
4315 // Test that rewrapping is ignored outside of comments in most languages.
4316 assert_rewrap(
4317 indoc! {"
4318 /// Adds two numbers.
4319 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4320 fn add(a: u32, b: u32) -> u32 {
4321 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ˇ
4322 }
4323 "},
4324 indoc! {"
4325 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4326 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4327 fn add(a: u32, b: u32) -> u32 {
4328 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ˇ
4329 }
4330 "},
4331 language_with_doc_comments.clone(),
4332 &mut cx,
4333 );
4334
4335 // Test that rewrapping works in Markdown and Plain Text languages.
4336 assert_rewrap(
4337 indoc! {"
4338 # Hello
4339
4340 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.
4341 "},
4342 indoc! {"
4343 # Hello
4344
4345 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4346 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4347 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4348 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4349 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4350 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4351 Integer sit amet scelerisque nisi.
4352 "},
4353 markdown_language,
4354 &mut cx,
4355 );
4356
4357 assert_rewrap(
4358 indoc! {"
4359 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.
4360 "},
4361 indoc! {"
4362 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4363 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4364 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4365 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4366 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4367 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4368 Integer sit amet scelerisque nisi.
4369 "},
4370 plaintext_language,
4371 &mut cx,
4372 );
4373
4374 // Test rewrapping unaligned comments in a selection.
4375 assert_rewrap(
4376 indoc! {"
4377 fn foo() {
4378 if true {
4379 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4380 // Praesent semper egestas tellus id dignissim.ˇ»
4381 do_something();
4382 } else {
4383 //
4384 }
4385 }
4386 "},
4387 indoc! {"
4388 fn foo() {
4389 if true {
4390 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4391 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4392 // egestas tellus id dignissim.ˇ»
4393 do_something();
4394 } else {
4395 //
4396 }
4397 }
4398 "},
4399 language_with_doc_comments.clone(),
4400 &mut cx,
4401 );
4402
4403 assert_rewrap(
4404 indoc! {"
4405 fn foo() {
4406 if true {
4407 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4408 // Praesent semper egestas tellus id dignissim.»
4409 do_something();
4410 } else {
4411 //
4412 }
4413
4414 }
4415 "},
4416 indoc! {"
4417 fn foo() {
4418 if true {
4419 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4420 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4421 // egestas tellus id dignissim.»
4422 do_something();
4423 } else {
4424 //
4425 }
4426
4427 }
4428 "},
4429 language_with_doc_comments.clone(),
4430 &mut cx,
4431 );
4432
4433 #[track_caller]
4434 fn assert_rewrap(
4435 unwrapped_text: &str,
4436 wrapped_text: &str,
4437 language: Arc<Language>,
4438 cx: &mut EditorTestContext,
4439 ) {
4440 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4441 cx.set_state(unwrapped_text);
4442 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4443 cx.assert_editor_state(wrapped_text);
4444 }
4445}
4446
4447#[gpui::test]
4448async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4449 init_test(cx, |_| {});
4450
4451 let mut cx = EditorTestContext::new(cx).await;
4452
4453 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4454 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4455 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4456
4457 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4458 cx.set_state("two ˇfour ˇsix ˇ");
4459 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4460 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4461
4462 // Paste again but with only two cursors. Since the number of cursors doesn't
4463 // match the number of slices in the clipboard, the entire clipboard text
4464 // is pasted at each cursor.
4465 cx.set_state("ˇtwo one✅ four three six five ˇ");
4466 cx.update_editor(|e, cx| {
4467 e.handle_input("( ", cx);
4468 e.paste(&Paste, cx);
4469 e.handle_input(") ", cx);
4470 });
4471 cx.assert_editor_state(
4472 &([
4473 "( one✅ ",
4474 "three ",
4475 "five ) ˇtwo one✅ four three six five ( one✅ ",
4476 "three ",
4477 "five ) ˇ",
4478 ]
4479 .join("\n")),
4480 );
4481
4482 // Cut with three selections, one of which is full-line.
4483 cx.set_state(indoc! {"
4484 1«2ˇ»3
4485 4ˇ567
4486 «8ˇ»9"});
4487 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4488 cx.assert_editor_state(indoc! {"
4489 1ˇ3
4490 ˇ9"});
4491
4492 // Paste with three selections, noticing how the copied selection that was full-line
4493 // gets inserted before the second cursor.
4494 cx.set_state(indoc! {"
4495 1ˇ3
4496 9ˇ
4497 «oˇ»ne"});
4498 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4499 cx.assert_editor_state(indoc! {"
4500 12ˇ3
4501 4567
4502 9ˇ
4503 8ˇne"});
4504
4505 // Copy with a single cursor only, which writes the whole line into the clipboard.
4506 cx.set_state(indoc! {"
4507 The quick brown
4508 fox juˇmps over
4509 the lazy dog"});
4510 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4511 assert_eq!(
4512 cx.read_from_clipboard()
4513 .and_then(|item| item.text().as_deref().map(str::to_string)),
4514 Some("fox jumps over\n".to_string())
4515 );
4516
4517 // Paste with three selections, noticing how the copied full-line selection is inserted
4518 // before the empty selections but replaces the selection that is non-empty.
4519 cx.set_state(indoc! {"
4520 Tˇhe quick brown
4521 «foˇ»x jumps over
4522 tˇhe lazy dog"});
4523 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4524 cx.assert_editor_state(indoc! {"
4525 fox jumps over
4526 Tˇhe quick brown
4527 fox jumps over
4528 ˇx jumps over
4529 fox jumps over
4530 tˇhe lazy dog"});
4531}
4532
4533#[gpui::test]
4534async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4535 init_test(cx, |_| {});
4536
4537 let mut cx = EditorTestContext::new(cx).await;
4538 let language = Arc::new(Language::new(
4539 LanguageConfig::default(),
4540 Some(tree_sitter_rust::LANGUAGE.into()),
4541 ));
4542 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4543
4544 // Cut an indented block, without the leading whitespace.
4545 cx.set_state(indoc! {"
4546 const a: B = (
4547 c(),
4548 «d(
4549 e,
4550 f
4551 )ˇ»
4552 );
4553 "});
4554 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4555 cx.assert_editor_state(indoc! {"
4556 const a: B = (
4557 c(),
4558 ˇ
4559 );
4560 "});
4561
4562 // Paste it at the same position.
4563 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4564 cx.assert_editor_state(indoc! {"
4565 const a: B = (
4566 c(),
4567 d(
4568 e,
4569 f
4570 )ˇ
4571 );
4572 "});
4573
4574 // Paste it at a line with a lower indent level.
4575 cx.set_state(indoc! {"
4576 ˇ
4577 const a: B = (
4578 c(),
4579 );
4580 "});
4581 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4582 cx.assert_editor_state(indoc! {"
4583 d(
4584 e,
4585 f
4586 )ˇ
4587 const a: B = (
4588 c(),
4589 );
4590 "});
4591
4592 // Cut an indented block, with the leading whitespace.
4593 cx.set_state(indoc! {"
4594 const a: B = (
4595 c(),
4596 « d(
4597 e,
4598 f
4599 )
4600 ˇ»);
4601 "});
4602 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4603 cx.assert_editor_state(indoc! {"
4604 const a: B = (
4605 c(),
4606 ˇ);
4607 "});
4608
4609 // Paste it at the same position.
4610 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4611 cx.assert_editor_state(indoc! {"
4612 const a: B = (
4613 c(),
4614 d(
4615 e,
4616 f
4617 )
4618 ˇ);
4619 "});
4620
4621 // Paste it at a line with a higher indent level.
4622 cx.set_state(indoc! {"
4623 const a: B = (
4624 c(),
4625 d(
4626 e,
4627 fˇ
4628 )
4629 );
4630 "});
4631 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4632 cx.assert_editor_state(indoc! {"
4633 const a: B = (
4634 c(),
4635 d(
4636 e,
4637 f d(
4638 e,
4639 f
4640 )
4641 ˇ
4642 )
4643 );
4644 "});
4645}
4646
4647#[gpui::test]
4648fn test_select_all(cx: &mut TestAppContext) {
4649 init_test(cx, |_| {});
4650
4651 let view = cx.add_window(|cx| {
4652 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4653 build_editor(buffer, cx)
4654 });
4655 _ = view.update(cx, |view, cx| {
4656 view.select_all(&SelectAll, cx);
4657 assert_eq!(
4658 view.selections.display_ranges(cx),
4659 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4660 );
4661 });
4662}
4663
4664#[gpui::test]
4665fn test_select_line(cx: &mut TestAppContext) {
4666 init_test(cx, |_| {});
4667
4668 let view = cx.add_window(|cx| {
4669 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4670 build_editor(buffer, cx)
4671 });
4672 _ = view.update(cx, |view, cx| {
4673 view.change_selections(None, cx, |s| {
4674 s.select_display_ranges([
4675 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4676 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4677 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4678 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4679 ])
4680 });
4681 view.select_line(&SelectLine, cx);
4682 assert_eq!(
4683 view.selections.display_ranges(cx),
4684 vec![
4685 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4686 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4687 ]
4688 );
4689 });
4690
4691 _ = view.update(cx, |view, cx| {
4692 view.select_line(&SelectLine, cx);
4693 assert_eq!(
4694 view.selections.display_ranges(cx),
4695 vec![
4696 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4697 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4698 ]
4699 );
4700 });
4701
4702 _ = view.update(cx, |view, cx| {
4703 view.select_line(&SelectLine, cx);
4704 assert_eq!(
4705 view.selections.display_ranges(cx),
4706 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4707 );
4708 });
4709}
4710
4711#[gpui::test]
4712fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4713 init_test(cx, |_| {});
4714
4715 let view = cx.add_window(|cx| {
4716 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4717 build_editor(buffer, cx)
4718 });
4719 _ = view.update(cx, |view, cx| {
4720 view.fold_ranges(
4721 vec![
4722 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4723 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4724 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4725 ],
4726 true,
4727 cx,
4728 );
4729 view.change_selections(None, cx, |s| {
4730 s.select_display_ranges([
4731 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4732 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4733 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4734 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4735 ])
4736 });
4737 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4738 });
4739
4740 _ = view.update(cx, |view, cx| {
4741 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4742 assert_eq!(
4743 view.display_text(cx),
4744 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4745 );
4746 assert_eq!(
4747 view.selections.display_ranges(cx),
4748 [
4749 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4750 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4751 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4752 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4753 ]
4754 );
4755 });
4756
4757 _ = view.update(cx, |view, cx| {
4758 view.change_selections(None, cx, |s| {
4759 s.select_display_ranges([
4760 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4761 ])
4762 });
4763 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4764 assert_eq!(
4765 view.display_text(cx),
4766 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4767 );
4768 assert_eq!(
4769 view.selections.display_ranges(cx),
4770 [
4771 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4772 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4773 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4774 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4775 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4776 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4777 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4778 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4779 ]
4780 );
4781 });
4782}
4783
4784#[gpui::test]
4785async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4786 init_test(cx, |_| {});
4787
4788 let mut cx = EditorTestContext::new(cx).await;
4789
4790 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4791 cx.set_state(indoc!(
4792 r#"abc
4793 defˇghi
4794
4795 jk
4796 nlmo
4797 "#
4798 ));
4799
4800 cx.update_editor(|editor, cx| {
4801 editor.add_selection_above(&Default::default(), cx);
4802 });
4803
4804 cx.assert_editor_state(indoc!(
4805 r#"abcˇ
4806 defˇghi
4807
4808 jk
4809 nlmo
4810 "#
4811 ));
4812
4813 cx.update_editor(|editor, cx| {
4814 editor.add_selection_above(&Default::default(), cx);
4815 });
4816
4817 cx.assert_editor_state(indoc!(
4818 r#"abcˇ
4819 defˇghi
4820
4821 jk
4822 nlmo
4823 "#
4824 ));
4825
4826 cx.update_editor(|view, cx| {
4827 view.add_selection_below(&Default::default(), cx);
4828 });
4829
4830 cx.assert_editor_state(indoc!(
4831 r#"abc
4832 defˇghi
4833
4834 jk
4835 nlmo
4836 "#
4837 ));
4838
4839 cx.update_editor(|view, cx| {
4840 view.undo_selection(&Default::default(), cx);
4841 });
4842
4843 cx.assert_editor_state(indoc!(
4844 r#"abcˇ
4845 defˇghi
4846
4847 jk
4848 nlmo
4849 "#
4850 ));
4851
4852 cx.update_editor(|view, cx| {
4853 view.redo_selection(&Default::default(), cx);
4854 });
4855
4856 cx.assert_editor_state(indoc!(
4857 r#"abc
4858 defˇghi
4859
4860 jk
4861 nlmo
4862 "#
4863 ));
4864
4865 cx.update_editor(|view, cx| {
4866 view.add_selection_below(&Default::default(), cx);
4867 });
4868
4869 cx.assert_editor_state(indoc!(
4870 r#"abc
4871 defˇghi
4872
4873 jk
4874 nlmˇo
4875 "#
4876 ));
4877
4878 cx.update_editor(|view, cx| {
4879 view.add_selection_below(&Default::default(), cx);
4880 });
4881
4882 cx.assert_editor_state(indoc!(
4883 r#"abc
4884 defˇghi
4885
4886 jk
4887 nlmˇo
4888 "#
4889 ));
4890
4891 // change selections
4892 cx.set_state(indoc!(
4893 r#"abc
4894 def«ˇg»hi
4895
4896 jk
4897 nlmo
4898 "#
4899 ));
4900
4901 cx.update_editor(|view, cx| {
4902 view.add_selection_below(&Default::default(), cx);
4903 });
4904
4905 cx.assert_editor_state(indoc!(
4906 r#"abc
4907 def«ˇg»hi
4908
4909 jk
4910 nlm«ˇo»
4911 "#
4912 ));
4913
4914 cx.update_editor(|view, cx| {
4915 view.add_selection_below(&Default::default(), cx);
4916 });
4917
4918 cx.assert_editor_state(indoc!(
4919 r#"abc
4920 def«ˇg»hi
4921
4922 jk
4923 nlm«ˇo»
4924 "#
4925 ));
4926
4927 cx.update_editor(|view, cx| {
4928 view.add_selection_above(&Default::default(), cx);
4929 });
4930
4931 cx.assert_editor_state(indoc!(
4932 r#"abc
4933 def«ˇg»hi
4934
4935 jk
4936 nlmo
4937 "#
4938 ));
4939
4940 cx.update_editor(|view, cx| {
4941 view.add_selection_above(&Default::default(), cx);
4942 });
4943
4944 cx.assert_editor_state(indoc!(
4945 r#"abc
4946 def«ˇg»hi
4947
4948 jk
4949 nlmo
4950 "#
4951 ));
4952
4953 // Change selections again
4954 cx.set_state(indoc!(
4955 r#"a«bc
4956 defgˇ»hi
4957
4958 jk
4959 nlmo
4960 "#
4961 ));
4962
4963 cx.update_editor(|view, cx| {
4964 view.add_selection_below(&Default::default(), cx);
4965 });
4966
4967 cx.assert_editor_state(indoc!(
4968 r#"a«bcˇ»
4969 d«efgˇ»hi
4970
4971 j«kˇ»
4972 nlmo
4973 "#
4974 ));
4975
4976 cx.update_editor(|view, cx| {
4977 view.add_selection_below(&Default::default(), cx);
4978 });
4979 cx.assert_editor_state(indoc!(
4980 r#"a«bcˇ»
4981 d«efgˇ»hi
4982
4983 j«kˇ»
4984 n«lmoˇ»
4985 "#
4986 ));
4987 cx.update_editor(|view, cx| {
4988 view.add_selection_above(&Default::default(), cx);
4989 });
4990
4991 cx.assert_editor_state(indoc!(
4992 r#"a«bcˇ»
4993 d«efgˇ»hi
4994
4995 j«kˇ»
4996 nlmo
4997 "#
4998 ));
4999
5000 // Change selections again
5001 cx.set_state(indoc!(
5002 r#"abc
5003 d«ˇefghi
5004
5005 jk
5006 nlm»o
5007 "#
5008 ));
5009
5010 cx.update_editor(|view, cx| {
5011 view.add_selection_above(&Default::default(), cx);
5012 });
5013
5014 cx.assert_editor_state(indoc!(
5015 r#"a«ˇbc»
5016 d«ˇef»ghi
5017
5018 j«ˇk»
5019 n«ˇlm»o
5020 "#
5021 ));
5022
5023 cx.update_editor(|view, cx| {
5024 view.add_selection_below(&Default::default(), cx);
5025 });
5026
5027 cx.assert_editor_state(indoc!(
5028 r#"abc
5029 d«ˇef»ghi
5030
5031 j«ˇk»
5032 n«ˇlm»o
5033 "#
5034 ));
5035}
5036
5037#[gpui::test]
5038async fn test_select_next(cx: &mut gpui::TestAppContext) {
5039 init_test(cx, |_| {});
5040
5041 let mut cx = EditorTestContext::new(cx).await;
5042 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5043
5044 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5045 .unwrap();
5046 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5047
5048 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5049 .unwrap();
5050 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5051
5052 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5053 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5054
5055 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5056 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5057
5058 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5059 .unwrap();
5060 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5061
5062 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5063 .unwrap();
5064 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5065}
5066
5067#[gpui::test]
5068async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5069 init_test(cx, |_| {});
5070
5071 let mut cx = EditorTestContext::new(cx).await;
5072 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5073
5074 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5075 .unwrap();
5076 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5077}
5078
5079#[gpui::test]
5080async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5081 init_test(cx, |_| {});
5082
5083 let mut cx = EditorTestContext::new(cx).await;
5084 cx.set_state(
5085 r#"let foo = 2;
5086lˇet foo = 2;
5087let fooˇ = 2;
5088let foo = 2;
5089let foo = ˇ2;"#,
5090 );
5091
5092 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5093 .unwrap();
5094 cx.assert_editor_state(
5095 r#"let foo = 2;
5096«letˇ» foo = 2;
5097let «fooˇ» = 2;
5098let foo = 2;
5099let foo = «2ˇ»;"#,
5100 );
5101
5102 // noop for multiple selections with different contents
5103 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5104 .unwrap();
5105 cx.assert_editor_state(
5106 r#"let foo = 2;
5107«letˇ» foo = 2;
5108let «fooˇ» = 2;
5109let foo = 2;
5110let foo = «2ˇ»;"#,
5111 );
5112}
5113
5114#[gpui::test]
5115async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5116 init_test(cx, |_| {});
5117
5118 let mut cx =
5119 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5120
5121 cx.assert_editor_state(indoc! {"
5122 ˇbbb
5123 ccc
5124
5125 bbb
5126 ccc
5127 "});
5128 cx.dispatch_action(SelectPrevious::default());
5129 cx.assert_editor_state(indoc! {"
5130 «bbbˇ»
5131 ccc
5132
5133 bbb
5134 ccc
5135 "});
5136 cx.dispatch_action(SelectPrevious::default());
5137 cx.assert_editor_state(indoc! {"
5138 «bbbˇ»
5139 ccc
5140
5141 «bbbˇ»
5142 ccc
5143 "});
5144}
5145
5146#[gpui::test]
5147async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5148 init_test(cx, |_| {});
5149
5150 let mut cx = EditorTestContext::new(cx).await;
5151 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5152
5153 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5154 .unwrap();
5155 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5156
5157 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5158 .unwrap();
5159 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5160
5161 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5162 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5163
5164 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5165 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5166
5167 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5168 .unwrap();
5169 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5170
5171 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5172 .unwrap();
5173 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5174
5175 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5176 .unwrap();
5177 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5178}
5179
5180#[gpui::test]
5181async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5182 init_test(cx, |_| {});
5183
5184 let mut cx = EditorTestContext::new(cx).await;
5185 cx.set_state(
5186 r#"let foo = 2;
5187lˇet foo = 2;
5188let fooˇ = 2;
5189let foo = 2;
5190let foo = ˇ2;"#,
5191 );
5192
5193 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5194 .unwrap();
5195 cx.assert_editor_state(
5196 r#"let foo = 2;
5197«letˇ» foo = 2;
5198let «fooˇ» = 2;
5199let foo = 2;
5200let foo = «2ˇ»;"#,
5201 );
5202
5203 // noop for multiple selections with different contents
5204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5205 .unwrap();
5206 cx.assert_editor_state(
5207 r#"let foo = 2;
5208«letˇ» foo = 2;
5209let «fooˇ» = 2;
5210let foo = 2;
5211let foo = «2ˇ»;"#,
5212 );
5213}
5214
5215#[gpui::test]
5216async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5217 init_test(cx, |_| {});
5218
5219 let mut cx = EditorTestContext::new(cx).await;
5220 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5221
5222 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5223 .unwrap();
5224 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5225
5226 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5227 .unwrap();
5228 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5229
5230 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5231 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5232
5233 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5234 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5235
5236 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5237 .unwrap();
5238 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5239
5240 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5241 .unwrap();
5242 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5243}
5244
5245#[gpui::test]
5246async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5247 init_test(cx, |_| {});
5248
5249 let language = Arc::new(Language::new(
5250 LanguageConfig::default(),
5251 Some(tree_sitter_rust::LANGUAGE.into()),
5252 ));
5253
5254 let text = r#"
5255 use mod1::mod2::{mod3, mod4};
5256
5257 fn fn_1(param1: bool, param2: &str) {
5258 let var1 = "text";
5259 }
5260 "#
5261 .unindent();
5262
5263 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5264 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5265 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5266
5267 editor
5268 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5269 .await;
5270
5271 editor.update(cx, |view, cx| {
5272 view.change_selections(None, cx, |s| {
5273 s.select_display_ranges([
5274 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5275 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5276 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5277 ]);
5278 });
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 editor.update(cx, |editor, cx| {
5299 assert_text_with_selections(
5300 editor,
5301 indoc! {r#"
5302 use mod1::mod2::«{mod3, mod4}ˇ»;
5303
5304 «ˇfn fn_1(param1: bool, param2: &str) {
5305 let var1 = "text";
5306 }»
5307 "#},
5308 cx,
5309 );
5310 });
5311
5312 editor.update(cx, |view, cx| {
5313 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5314 });
5315 assert_eq!(
5316 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5317 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5318 );
5319
5320 // Trying to expand the selected syntax node one more time has no effect.
5321 editor.update(cx, |view, cx| {
5322 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5323 });
5324 assert_eq!(
5325 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5326 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
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, «mod4ˇ»};
5354
5355 fn fn_1«ˇ(param1: bool, param2: &str)» {
5356 let var1 = "«textˇ»";
5357 }
5358 "#},
5359 cx,
5360 );
5361 });
5362
5363 editor.update(cx, |view, cx| {
5364 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5365 });
5366 editor.update(cx, |editor, cx| {
5367 assert_text_with_selections(
5368 editor,
5369 indoc! {r#"
5370 use mod1::mod2::{mod3, mo«ˇ»d4};
5371
5372 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5373 let var1 = "te«ˇ»xt";
5374 }
5375 "#},
5376 cx,
5377 );
5378 });
5379
5380 // Trying to shrink the selected syntax node one more time has no effect.
5381 editor.update(cx, |view, cx| {
5382 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5383 });
5384 editor.update(cx, |editor, cx| {
5385 assert_text_with_selections(
5386 editor,
5387 indoc! {r#"
5388 use mod1::mod2::{mod3, mo«ˇ»d4};
5389
5390 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5391 let var1 = "te«ˇ»xt";
5392 }
5393 "#},
5394 cx,
5395 );
5396 });
5397
5398 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5399 // a fold.
5400 editor.update(cx, |view, cx| {
5401 view.fold_ranges(
5402 vec![
5403 (
5404 Point::new(0, 21)..Point::new(0, 24),
5405 FoldPlaceholder::test(),
5406 ),
5407 (
5408 Point::new(3, 20)..Point::new(3, 22),
5409 FoldPlaceholder::test(),
5410 ),
5411 ],
5412 true,
5413 cx,
5414 );
5415 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5416 });
5417 editor.update(cx, |editor, cx| {
5418 assert_text_with_selections(
5419 editor,
5420 indoc! {r#"
5421 use mod1::mod2::«{mod3, mod4}ˇ»;
5422
5423 fn fn_1«ˇ(param1: bool, param2: &str)» {
5424 «let var1 = "text";ˇ»
5425 }
5426 "#},
5427 cx,
5428 );
5429 });
5430}
5431
5432#[gpui::test]
5433async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5434 init_test(cx, |_| {});
5435
5436 let language = Arc::new(
5437 Language::new(
5438 LanguageConfig {
5439 brackets: BracketPairConfig {
5440 pairs: vec![
5441 BracketPair {
5442 start: "{".to_string(),
5443 end: "}".to_string(),
5444 close: false,
5445 surround: false,
5446 newline: true,
5447 },
5448 BracketPair {
5449 start: "(".to_string(),
5450 end: ")".to_string(),
5451 close: false,
5452 surround: false,
5453 newline: true,
5454 },
5455 ],
5456 ..Default::default()
5457 },
5458 ..Default::default()
5459 },
5460 Some(tree_sitter_rust::LANGUAGE.into()),
5461 )
5462 .with_indents_query(
5463 r#"
5464 (_ "(" ")" @end) @indent
5465 (_ "{" "}" @end) @indent
5466 "#,
5467 )
5468 .unwrap(),
5469 );
5470
5471 let text = "fn a() {}";
5472
5473 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5474 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5475 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5476 editor
5477 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5478 .await;
5479
5480 editor.update(cx, |editor, cx| {
5481 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5482 editor.newline(&Newline, cx);
5483 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5484 assert_eq!(
5485 editor.selections.ranges(cx),
5486 &[
5487 Point::new(1, 4)..Point::new(1, 4),
5488 Point::new(3, 4)..Point::new(3, 4),
5489 Point::new(5, 0)..Point::new(5, 0)
5490 ]
5491 );
5492 });
5493}
5494
5495#[gpui::test]
5496async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5497 init_test(cx, |_| {});
5498
5499 let mut cx = EditorTestContext::new(cx).await;
5500
5501 let language = Arc::new(Language::new(
5502 LanguageConfig {
5503 brackets: BracketPairConfig {
5504 pairs: vec![
5505 BracketPair {
5506 start: "{".to_string(),
5507 end: "}".to_string(),
5508 close: true,
5509 surround: true,
5510 newline: true,
5511 },
5512 BracketPair {
5513 start: "(".to_string(),
5514 end: ")".to_string(),
5515 close: true,
5516 surround: true,
5517 newline: true,
5518 },
5519 BracketPair {
5520 start: "/*".to_string(),
5521 end: " */".to_string(),
5522 close: true,
5523 surround: true,
5524 newline: true,
5525 },
5526 BracketPair {
5527 start: "[".to_string(),
5528 end: "]".to_string(),
5529 close: false,
5530 surround: false,
5531 newline: true,
5532 },
5533 BracketPair {
5534 start: "\"".to_string(),
5535 end: "\"".to_string(),
5536 close: true,
5537 surround: true,
5538 newline: false,
5539 },
5540 BracketPair {
5541 start: "<".to_string(),
5542 end: ">".to_string(),
5543 close: false,
5544 surround: true,
5545 newline: true,
5546 },
5547 ],
5548 ..Default::default()
5549 },
5550 autoclose_before: "})]".to_string(),
5551 ..Default::default()
5552 },
5553 Some(tree_sitter_rust::LANGUAGE.into()),
5554 ));
5555
5556 cx.language_registry().add(language.clone());
5557 cx.update_buffer(|buffer, cx| {
5558 buffer.set_language(Some(language), cx);
5559 });
5560
5561 cx.set_state(
5562 &r#"
5563 🏀ˇ
5564 εˇ
5565 ❤️ˇ
5566 "#
5567 .unindent(),
5568 );
5569
5570 // autoclose multiple nested brackets at multiple cursors
5571 cx.update_editor(|view, cx| {
5572 view.handle_input("{", cx);
5573 view.handle_input("{", cx);
5574 view.handle_input("{", cx);
5575 });
5576 cx.assert_editor_state(
5577 &"
5578 🏀{{{ˇ}}}
5579 ε{{{ˇ}}}
5580 ❤️{{{ˇ}}}
5581 "
5582 .unindent(),
5583 );
5584
5585 // insert a different closing bracket
5586 cx.update_editor(|view, cx| {
5587 view.handle_input(")", cx);
5588 });
5589 cx.assert_editor_state(
5590 &"
5591 🏀{{{)ˇ}}}
5592 ε{{{)ˇ}}}
5593 ❤️{{{)ˇ}}}
5594 "
5595 .unindent(),
5596 );
5597
5598 // skip over the auto-closed brackets when typing a closing bracket
5599 cx.update_editor(|view, cx| {
5600 view.move_right(&MoveRight, cx);
5601 view.handle_input("}", cx);
5602 view.handle_input("}", cx);
5603 view.handle_input("}", cx);
5604 });
5605 cx.assert_editor_state(
5606 &"
5607 🏀{{{)}}}}ˇ
5608 ε{{{)}}}}ˇ
5609 ❤️{{{)}}}}ˇ
5610 "
5611 .unindent(),
5612 );
5613
5614 // autoclose multi-character pairs
5615 cx.set_state(
5616 &"
5617 ˇ
5618 ˇ
5619 "
5620 .unindent(),
5621 );
5622 cx.update_editor(|view, cx| {
5623 view.handle_input("/", cx);
5624 view.handle_input("*", cx);
5625 });
5626 cx.assert_editor_state(
5627 &"
5628 /*ˇ */
5629 /*ˇ */
5630 "
5631 .unindent(),
5632 );
5633
5634 // one cursor autocloses a multi-character pair, one cursor
5635 // does not autoclose.
5636 cx.set_state(
5637 &"
5638 /ˇ
5639 ˇ
5640 "
5641 .unindent(),
5642 );
5643 cx.update_editor(|view, cx| view.handle_input("*", cx));
5644 cx.assert_editor_state(
5645 &"
5646 /*ˇ */
5647 *ˇ
5648 "
5649 .unindent(),
5650 );
5651
5652 // Don't autoclose if the next character isn't whitespace and isn't
5653 // listed in the language's "autoclose_before" section.
5654 cx.set_state("ˇa b");
5655 cx.update_editor(|view, cx| view.handle_input("{", cx));
5656 cx.assert_editor_state("{ˇa b");
5657
5658 // Don't autoclose if `close` is false for the bracket pair
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
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 // Autclose pair where the start and end characters are the same
5669 cx.set_state("aˇ");
5670 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5671 cx.assert_editor_state("a\"ˇ\"");
5672 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5673 cx.assert_editor_state("a\"\"ˇ");
5674
5675 // Don't autoclose pair if autoclose is disabled
5676 cx.set_state("ˇ");
5677 cx.update_editor(|view, cx| view.handle_input("<", cx));
5678 cx.assert_editor_state("<ˇ");
5679
5680 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5681 cx.set_state("«aˇ» b");
5682 cx.update_editor(|view, cx| view.handle_input("<", cx));
5683 cx.assert_editor_state("<«aˇ»> b");
5684}
5685
5686#[gpui::test]
5687async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5688 init_test(cx, |settings| {
5689 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5690 });
5691
5692 let mut cx = EditorTestContext::new(cx).await;
5693
5694 let language = Arc::new(Language::new(
5695 LanguageConfig {
5696 brackets: BracketPairConfig {
5697 pairs: vec![
5698 BracketPair {
5699 start: "{".to_string(),
5700 end: "}".to_string(),
5701 close: true,
5702 surround: true,
5703 newline: true,
5704 },
5705 BracketPair {
5706 start: "(".to_string(),
5707 end: ")".to_string(),
5708 close: true,
5709 surround: true,
5710 newline: true,
5711 },
5712 BracketPair {
5713 start: "[".to_string(),
5714 end: "]".to_string(),
5715 close: false,
5716 surround: false,
5717 newline: true,
5718 },
5719 ],
5720 ..Default::default()
5721 },
5722 autoclose_before: "})]".to_string(),
5723 ..Default::default()
5724 },
5725 Some(tree_sitter_rust::LANGUAGE.into()),
5726 ));
5727
5728 cx.language_registry().add(language.clone());
5729 cx.update_buffer(|buffer, cx| {
5730 buffer.set_language(Some(language), cx);
5731 });
5732
5733 cx.set_state(
5734 &"
5735 ˇ
5736 ˇ
5737 ˇ
5738 "
5739 .unindent(),
5740 );
5741
5742 // ensure only matching closing brackets are skipped over
5743 cx.update_editor(|view, cx| {
5744 view.handle_input("}", cx);
5745 view.move_left(&MoveLeft, cx);
5746 view.handle_input(")", cx);
5747 view.move_left(&MoveLeft, cx);
5748 });
5749 cx.assert_editor_state(
5750 &"
5751 ˇ)}
5752 ˇ)}
5753 ˇ)}
5754 "
5755 .unindent(),
5756 );
5757
5758 // skip-over closing brackets at multiple cursors
5759 cx.update_editor(|view, cx| {
5760 view.handle_input(")", cx);
5761 view.handle_input("}", cx);
5762 });
5763 cx.assert_editor_state(
5764 &"
5765 )}ˇ
5766 )}ˇ
5767 )}ˇ
5768 "
5769 .unindent(),
5770 );
5771
5772 // ignore non-close brackets
5773 cx.update_editor(|view, cx| {
5774 view.handle_input("]", cx);
5775 view.move_left(&MoveLeft, cx);
5776 view.handle_input("]", cx);
5777 });
5778 cx.assert_editor_state(
5779 &"
5780 )}]ˇ]
5781 )}]ˇ]
5782 )}]ˇ]
5783 "
5784 .unindent(),
5785 );
5786}
5787
5788#[gpui::test]
5789async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5790 init_test(cx, |_| {});
5791
5792 let mut cx = EditorTestContext::new(cx).await;
5793
5794 let html_language = Arc::new(
5795 Language::new(
5796 LanguageConfig {
5797 name: "HTML".into(),
5798 brackets: BracketPairConfig {
5799 pairs: vec![
5800 BracketPair {
5801 start: "<".into(),
5802 end: ">".into(),
5803 close: true,
5804 ..Default::default()
5805 },
5806 BracketPair {
5807 start: "{".into(),
5808 end: "}".into(),
5809 close: true,
5810 ..Default::default()
5811 },
5812 BracketPair {
5813 start: "(".into(),
5814 end: ")".into(),
5815 close: true,
5816 ..Default::default()
5817 },
5818 ],
5819 ..Default::default()
5820 },
5821 autoclose_before: "})]>".into(),
5822 ..Default::default()
5823 },
5824 Some(tree_sitter_html::language()),
5825 )
5826 .with_injection_query(
5827 r#"
5828 (script_element
5829 (raw_text) @content
5830 (#set! "language" "javascript"))
5831 "#,
5832 )
5833 .unwrap(),
5834 );
5835
5836 let javascript_language = Arc::new(Language::new(
5837 LanguageConfig {
5838 name: "JavaScript".into(),
5839 brackets: BracketPairConfig {
5840 pairs: vec![
5841 BracketPair {
5842 start: "/*".into(),
5843 end: " */".into(),
5844 close: true,
5845 ..Default::default()
5846 },
5847 BracketPair {
5848 start: "{".into(),
5849 end: "}".into(),
5850 close: true,
5851 ..Default::default()
5852 },
5853 BracketPair {
5854 start: "(".into(),
5855 end: ")".into(),
5856 close: true,
5857 ..Default::default()
5858 },
5859 ],
5860 ..Default::default()
5861 },
5862 autoclose_before: "})]>".into(),
5863 ..Default::default()
5864 },
5865 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5866 ));
5867
5868 cx.language_registry().add(html_language.clone());
5869 cx.language_registry().add(javascript_language.clone());
5870
5871 cx.update_buffer(|buffer, cx| {
5872 buffer.set_language(Some(html_language), cx);
5873 });
5874
5875 cx.set_state(
5876 &r#"
5877 <body>ˇ
5878 <script>
5879 var x = 1;ˇ
5880 </script>
5881 </body>ˇ
5882 "#
5883 .unindent(),
5884 );
5885
5886 // Precondition: different languages are active at different locations.
5887 cx.update_editor(|editor, cx| {
5888 let snapshot = editor.snapshot(cx);
5889 let cursors = editor.selections.ranges::<usize>(cx);
5890 let languages = cursors
5891 .iter()
5892 .map(|c| snapshot.language_at(c.start).unwrap().name())
5893 .collect::<Vec<_>>();
5894 assert_eq!(
5895 languages,
5896 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5897 );
5898 });
5899
5900 // Angle brackets autoclose in HTML, but not JavaScript.
5901 cx.update_editor(|editor, cx| {
5902 editor.handle_input("<", cx);
5903 editor.handle_input("a", cx);
5904 });
5905 cx.assert_editor_state(
5906 &r#"
5907 <body><aˇ>
5908 <script>
5909 var x = 1;<aˇ
5910 </script>
5911 </body><aˇ>
5912 "#
5913 .unindent(),
5914 );
5915
5916 // Curly braces and parens autoclose in both HTML and JavaScript.
5917 cx.update_editor(|editor, cx| {
5918 editor.handle_input(" b=", cx);
5919 editor.handle_input("{", cx);
5920 editor.handle_input("c", cx);
5921 editor.handle_input("(", cx);
5922 });
5923 cx.assert_editor_state(
5924 &r#"
5925 <body><a b={c(ˇ)}>
5926 <script>
5927 var x = 1;<a b={c(ˇ)}
5928 </script>
5929 </body><a b={c(ˇ)}>
5930 "#
5931 .unindent(),
5932 );
5933
5934 // Brackets that were already autoclosed are skipped.
5935 cx.update_editor(|editor, cx| {
5936 editor.handle_input(")", cx);
5937 editor.handle_input("d", cx);
5938 editor.handle_input("}", cx);
5939 });
5940 cx.assert_editor_state(
5941 &r#"
5942 <body><a b={c()d}ˇ>
5943 <script>
5944 var x = 1;<a b={c()d}ˇ
5945 </script>
5946 </body><a b={c()d}ˇ>
5947 "#
5948 .unindent(),
5949 );
5950 cx.update_editor(|editor, cx| {
5951 editor.handle_input(">", cx);
5952 });
5953 cx.assert_editor_state(
5954 &r#"
5955 <body><a b={c()d}>ˇ
5956 <script>
5957 var x = 1;<a b={c()d}>ˇ
5958 </script>
5959 </body><a b={c()d}>ˇ
5960 "#
5961 .unindent(),
5962 );
5963
5964 // Reset
5965 cx.set_state(
5966 &r#"
5967 <body>ˇ
5968 <script>
5969 var x = 1;ˇ
5970 </script>
5971 </body>ˇ
5972 "#
5973 .unindent(),
5974 );
5975
5976 cx.update_editor(|editor, cx| {
5977 editor.handle_input("<", cx);
5978 });
5979 cx.assert_editor_state(
5980 &r#"
5981 <body><ˇ>
5982 <script>
5983 var x = 1;<ˇ
5984 </script>
5985 </body><ˇ>
5986 "#
5987 .unindent(),
5988 );
5989
5990 // When backspacing, the closing angle brackets are removed.
5991 cx.update_editor(|editor, cx| {
5992 editor.backspace(&Backspace, cx);
5993 });
5994 cx.assert_editor_state(
5995 &r#"
5996 <body>ˇ
5997 <script>
5998 var x = 1;ˇ
5999 </script>
6000 </body>ˇ
6001 "#
6002 .unindent(),
6003 );
6004
6005 // Block comments autoclose in JavaScript, but not HTML.
6006 cx.update_editor(|editor, cx| {
6007 editor.handle_input("/", cx);
6008 editor.handle_input("*", cx);
6009 });
6010 cx.assert_editor_state(
6011 &r#"
6012 <body>/*ˇ
6013 <script>
6014 var x = 1;/*ˇ */
6015 </script>
6016 </body>/*ˇ
6017 "#
6018 .unindent(),
6019 );
6020}
6021
6022#[gpui::test]
6023async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6024 init_test(cx, |_| {});
6025
6026 let mut cx = EditorTestContext::new(cx).await;
6027
6028 let rust_language = Arc::new(
6029 Language::new(
6030 LanguageConfig {
6031 name: "Rust".into(),
6032 brackets: serde_json::from_value(json!([
6033 { "start": "{", "end": "}", "close": true, "newline": true },
6034 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6035 ]))
6036 .unwrap(),
6037 autoclose_before: "})]>".into(),
6038 ..Default::default()
6039 },
6040 Some(tree_sitter_rust::LANGUAGE.into()),
6041 )
6042 .with_override_query("(string_literal) @string")
6043 .unwrap(),
6044 );
6045
6046 cx.language_registry().add(rust_language.clone());
6047 cx.update_buffer(|buffer, cx| {
6048 buffer.set_language(Some(rust_language), cx);
6049 });
6050
6051 cx.set_state(
6052 &r#"
6053 let x = ˇ
6054 "#
6055 .unindent(),
6056 );
6057
6058 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6059 cx.update_editor(|editor, cx| {
6060 editor.handle_input("\"", cx);
6061 });
6062 cx.assert_editor_state(
6063 &r#"
6064 let x = "ˇ"
6065 "#
6066 .unindent(),
6067 );
6068
6069 // Inserting another quotation mark. The cursor moves across the existing
6070 // automatically-inserted quotation mark.
6071 cx.update_editor(|editor, cx| {
6072 editor.handle_input("\"", cx);
6073 });
6074 cx.assert_editor_state(
6075 &r#"
6076 let x = ""ˇ
6077 "#
6078 .unindent(),
6079 );
6080
6081 // Reset
6082 cx.set_state(
6083 &r#"
6084 let x = ˇ
6085 "#
6086 .unindent(),
6087 );
6088
6089 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6090 cx.update_editor(|editor, cx| {
6091 editor.handle_input("\"", cx);
6092 editor.handle_input(" ", cx);
6093 editor.move_left(&Default::default(), cx);
6094 editor.handle_input("\\", cx);
6095 editor.handle_input("\"", cx);
6096 });
6097 cx.assert_editor_state(
6098 &r#"
6099 let x = "\"ˇ "
6100 "#
6101 .unindent(),
6102 );
6103
6104 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6105 // mark. Nothing is inserted.
6106 cx.update_editor(|editor, cx| {
6107 editor.move_right(&Default::default(), cx);
6108 editor.handle_input("\"", cx);
6109 });
6110 cx.assert_editor_state(
6111 &r#"
6112 let x = "\" "ˇ
6113 "#
6114 .unindent(),
6115 );
6116}
6117
6118#[gpui::test]
6119async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6120 init_test(cx, |_| {});
6121
6122 let language = Arc::new(Language::new(
6123 LanguageConfig {
6124 brackets: BracketPairConfig {
6125 pairs: vec![
6126 BracketPair {
6127 start: "{".to_string(),
6128 end: "}".to_string(),
6129 close: true,
6130 surround: true,
6131 newline: true,
6132 },
6133 BracketPair {
6134 start: "/* ".to_string(),
6135 end: "*/".to_string(),
6136 close: true,
6137 surround: true,
6138 ..Default::default()
6139 },
6140 ],
6141 ..Default::default()
6142 },
6143 ..Default::default()
6144 },
6145 Some(tree_sitter_rust::LANGUAGE.into()),
6146 ));
6147
6148 let text = r#"
6149 a
6150 b
6151 c
6152 "#
6153 .unindent();
6154
6155 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6156 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6157 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6158 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6159 .await;
6160
6161 view.update(cx, |view, cx| {
6162 view.change_selections(None, cx, |s| {
6163 s.select_display_ranges([
6164 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6165 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6166 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6167 ])
6168 });
6169
6170 view.handle_input("{", cx);
6171 view.handle_input("{", cx);
6172 view.handle_input("{", cx);
6173 assert_eq!(
6174 view.text(cx),
6175 "
6176 {{{a}}}
6177 {{{b}}}
6178 {{{c}}}
6179 "
6180 .unindent()
6181 );
6182 assert_eq!(
6183 view.selections.display_ranges(cx),
6184 [
6185 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6186 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6187 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6188 ]
6189 );
6190
6191 view.undo(&Undo, cx);
6192 view.undo(&Undo, cx);
6193 view.undo(&Undo, cx);
6194 assert_eq!(
6195 view.text(cx),
6196 "
6197 a
6198 b
6199 c
6200 "
6201 .unindent()
6202 );
6203 assert_eq!(
6204 view.selections.display_ranges(cx),
6205 [
6206 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6207 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6208 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6209 ]
6210 );
6211
6212 // Ensure inserting the first character of a multi-byte bracket pair
6213 // doesn't surround the selections with the bracket.
6214 view.handle_input("/", cx);
6215 assert_eq!(
6216 view.text(cx),
6217 "
6218 /
6219 /
6220 /
6221 "
6222 .unindent()
6223 );
6224 assert_eq!(
6225 view.selections.display_ranges(cx),
6226 [
6227 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6228 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6229 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6230 ]
6231 );
6232
6233 view.undo(&Undo, cx);
6234 assert_eq!(
6235 view.text(cx),
6236 "
6237 a
6238 b
6239 c
6240 "
6241 .unindent()
6242 );
6243 assert_eq!(
6244 view.selections.display_ranges(cx),
6245 [
6246 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6247 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6248 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6249 ]
6250 );
6251
6252 // Ensure inserting the last character of a multi-byte bracket pair
6253 // doesn't surround the selections with the bracket.
6254 view.handle_input("*", cx);
6255 assert_eq!(
6256 view.text(cx),
6257 "
6258 *
6259 *
6260 *
6261 "
6262 .unindent()
6263 );
6264 assert_eq!(
6265 view.selections.display_ranges(cx),
6266 [
6267 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6268 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6269 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6270 ]
6271 );
6272 });
6273}
6274
6275#[gpui::test]
6276async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6277 init_test(cx, |_| {});
6278
6279 let language = Arc::new(Language::new(
6280 LanguageConfig {
6281 brackets: BracketPairConfig {
6282 pairs: vec![BracketPair {
6283 start: "{".to_string(),
6284 end: "}".to_string(),
6285 close: true,
6286 surround: true,
6287 newline: true,
6288 }],
6289 ..Default::default()
6290 },
6291 autoclose_before: "}".to_string(),
6292 ..Default::default()
6293 },
6294 Some(tree_sitter_rust::LANGUAGE.into()),
6295 ));
6296
6297 let text = r#"
6298 a
6299 b
6300 c
6301 "#
6302 .unindent();
6303
6304 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6305 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6306 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6307 editor
6308 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6309 .await;
6310
6311 editor.update(cx, |editor, cx| {
6312 editor.change_selections(None, cx, |s| {
6313 s.select_ranges([
6314 Point::new(0, 1)..Point::new(0, 1),
6315 Point::new(1, 1)..Point::new(1, 1),
6316 Point::new(2, 1)..Point::new(2, 1),
6317 ])
6318 });
6319
6320 editor.handle_input("{", cx);
6321 editor.handle_input("{", cx);
6322 editor.handle_input("_", cx);
6323 assert_eq!(
6324 editor.text(cx),
6325 "
6326 a{{_}}
6327 b{{_}}
6328 c{{_}}
6329 "
6330 .unindent()
6331 );
6332 assert_eq!(
6333 editor.selections.ranges::<Point>(cx),
6334 [
6335 Point::new(0, 4)..Point::new(0, 4),
6336 Point::new(1, 4)..Point::new(1, 4),
6337 Point::new(2, 4)..Point::new(2, 4)
6338 ]
6339 );
6340
6341 editor.backspace(&Default::default(), cx);
6342 editor.backspace(&Default::default(), cx);
6343 assert_eq!(
6344 editor.text(cx),
6345 "
6346 a{}
6347 b{}
6348 c{}
6349 "
6350 .unindent()
6351 );
6352 assert_eq!(
6353 editor.selections.ranges::<Point>(cx),
6354 [
6355 Point::new(0, 2)..Point::new(0, 2),
6356 Point::new(1, 2)..Point::new(1, 2),
6357 Point::new(2, 2)..Point::new(2, 2)
6358 ]
6359 );
6360
6361 editor.delete_to_previous_word_start(&Default::default(), cx);
6362 assert_eq!(
6363 editor.text(cx),
6364 "
6365 a
6366 b
6367 c
6368 "
6369 .unindent()
6370 );
6371 assert_eq!(
6372 editor.selections.ranges::<Point>(cx),
6373 [
6374 Point::new(0, 1)..Point::new(0, 1),
6375 Point::new(1, 1)..Point::new(1, 1),
6376 Point::new(2, 1)..Point::new(2, 1)
6377 ]
6378 );
6379 });
6380}
6381
6382#[gpui::test]
6383async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6384 init_test(cx, |settings| {
6385 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6386 });
6387
6388 let mut cx = EditorTestContext::new(cx).await;
6389
6390 let language = Arc::new(Language::new(
6391 LanguageConfig {
6392 brackets: BracketPairConfig {
6393 pairs: vec![
6394 BracketPair {
6395 start: "{".to_string(),
6396 end: "}".to_string(),
6397 close: true,
6398 surround: true,
6399 newline: true,
6400 },
6401 BracketPair {
6402 start: "(".to_string(),
6403 end: ")".to_string(),
6404 close: true,
6405 surround: true,
6406 newline: true,
6407 },
6408 BracketPair {
6409 start: "[".to_string(),
6410 end: "]".to_string(),
6411 close: false,
6412 surround: true,
6413 newline: true,
6414 },
6415 ],
6416 ..Default::default()
6417 },
6418 autoclose_before: "})]".to_string(),
6419 ..Default::default()
6420 },
6421 Some(tree_sitter_rust::LANGUAGE.into()),
6422 ));
6423
6424 cx.language_registry().add(language.clone());
6425 cx.update_buffer(|buffer, cx| {
6426 buffer.set_language(Some(language), cx);
6427 });
6428
6429 cx.set_state(
6430 &"
6431 {(ˇ)}
6432 [[ˇ]]
6433 {(ˇ)}
6434 "
6435 .unindent(),
6436 );
6437
6438 cx.update_editor(|view, cx| {
6439 view.backspace(&Default::default(), cx);
6440 view.backspace(&Default::default(), cx);
6441 });
6442
6443 cx.assert_editor_state(
6444 &"
6445 ˇ
6446 ˇ]]
6447 ˇ
6448 "
6449 .unindent(),
6450 );
6451
6452 cx.update_editor(|view, cx| {
6453 view.handle_input("{", cx);
6454 view.handle_input("{", cx);
6455 view.move_right(&MoveRight, cx);
6456 view.move_right(&MoveRight, cx);
6457 view.move_left(&MoveLeft, cx);
6458 view.move_left(&MoveLeft, cx);
6459 view.backspace(&Default::default(), cx);
6460 });
6461
6462 cx.assert_editor_state(
6463 &"
6464 {ˇ}
6465 {ˇ}]]
6466 {ˇ}
6467 "
6468 .unindent(),
6469 );
6470
6471 cx.update_editor(|view, cx| {
6472 view.backspace(&Default::default(), cx);
6473 });
6474
6475 cx.assert_editor_state(
6476 &"
6477 ˇ
6478 ˇ]]
6479 ˇ
6480 "
6481 .unindent(),
6482 );
6483}
6484
6485#[gpui::test]
6486async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6487 init_test(cx, |_| {});
6488
6489 let language = Arc::new(Language::new(
6490 LanguageConfig::default(),
6491 Some(tree_sitter_rust::LANGUAGE.into()),
6492 ));
6493
6494 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6495 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6496 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6497 editor
6498 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6499 .await;
6500
6501 editor.update(cx, |editor, cx| {
6502 editor.set_auto_replace_emoji_shortcode(true);
6503
6504 editor.handle_input("Hello ", cx);
6505 editor.handle_input(":wave", cx);
6506 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6507
6508 editor.handle_input(":", cx);
6509 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6510
6511 editor.handle_input(" :smile", cx);
6512 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6513
6514 editor.handle_input(":", cx);
6515 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6516
6517 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6518 editor.handle_input(":wave", cx);
6519 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6520
6521 editor.handle_input(":", cx);
6522 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6523
6524 editor.handle_input(":1", cx);
6525 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6526
6527 editor.handle_input(":", cx);
6528 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6529
6530 // Ensure shortcode does not get replaced when it is part of a word
6531 editor.handle_input(" Test:wave", cx);
6532 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6533
6534 editor.handle_input(":", cx);
6535 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6536
6537 editor.set_auto_replace_emoji_shortcode(false);
6538
6539 // Ensure shortcode does not get replaced when auto replace is off
6540 editor.handle_input(" :wave", cx);
6541 assert_eq!(
6542 editor.text(cx),
6543 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6544 );
6545
6546 editor.handle_input(":", cx);
6547 assert_eq!(
6548 editor.text(cx),
6549 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6550 );
6551 });
6552}
6553
6554#[gpui::test]
6555async fn test_snippets(cx: &mut gpui::TestAppContext) {
6556 init_test(cx, |_| {});
6557
6558 let (text, insertion_ranges) = marked_text_ranges(
6559 indoc! {"
6560 a.ˇ b
6561 a.ˇ b
6562 a.ˇ b
6563 "},
6564 false,
6565 );
6566
6567 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6568 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6569
6570 editor.update(cx, |editor, cx| {
6571 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6572
6573 editor
6574 .insert_snippet(&insertion_ranges, snippet, cx)
6575 .unwrap();
6576
6577 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6578 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6579 assert_eq!(editor.text(cx), expected_text);
6580 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6581 }
6582
6583 assert(
6584 editor,
6585 cx,
6586 indoc! {"
6587 a.f(«one», two, «three») b
6588 a.f(«one», two, «three») b
6589 a.f(«one», two, «three») b
6590 "},
6591 );
6592
6593 // Can't move earlier than the first tab stop
6594 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6595 assert(
6596 editor,
6597 cx,
6598 indoc! {"
6599 a.f(«one», two, «three») b
6600 a.f(«one», two, «three») b
6601 a.f(«one», two, «three») b
6602 "},
6603 );
6604
6605 assert!(editor.move_to_next_snippet_tabstop(cx));
6606 assert(
6607 editor,
6608 cx,
6609 indoc! {"
6610 a.f(one, «two», three) b
6611 a.f(one, «two», three) b
6612 a.f(one, «two», three) b
6613 "},
6614 );
6615
6616 editor.move_to_prev_snippet_tabstop(cx);
6617 assert(
6618 editor,
6619 cx,
6620 indoc! {"
6621 a.f(«one», two, «three») b
6622 a.f(«one», two, «three») b
6623 a.f(«one», two, «three») b
6624 "},
6625 );
6626
6627 assert!(editor.move_to_next_snippet_tabstop(cx));
6628 assert(
6629 editor,
6630 cx,
6631 indoc! {"
6632 a.f(one, «two», three) b
6633 a.f(one, «two», three) b
6634 a.f(one, «two», three) b
6635 "},
6636 );
6637 assert!(editor.move_to_next_snippet_tabstop(cx));
6638 assert(
6639 editor,
6640 cx,
6641 indoc! {"
6642 a.f(one, two, three)ˇ b
6643 a.f(one, two, three)ˇ b
6644 a.f(one, two, three)ˇ b
6645 "},
6646 );
6647
6648 // As soon as the last tab stop is reached, snippet state is gone
6649 editor.move_to_prev_snippet_tabstop(cx);
6650 assert(
6651 editor,
6652 cx,
6653 indoc! {"
6654 a.f(one, two, three)ˇ b
6655 a.f(one, two, three)ˇ b
6656 a.f(one, two, three)ˇ b
6657 "},
6658 );
6659 });
6660}
6661
6662#[gpui::test]
6663async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6664 init_test(cx, |_| {});
6665
6666 let fs = FakeFs::new(cx.executor());
6667 fs.insert_file("/file.rs", Default::default()).await;
6668
6669 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6670
6671 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6672 language_registry.add(rust_lang());
6673 let mut fake_servers = language_registry.register_fake_lsp(
6674 "Rust",
6675 FakeLspAdapter {
6676 capabilities: lsp::ServerCapabilities {
6677 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6678 ..Default::default()
6679 },
6680 ..Default::default()
6681 },
6682 );
6683
6684 let buffer = project
6685 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6686 .await
6687 .unwrap();
6688
6689 cx.executor().start_waiting();
6690 let fake_server = fake_servers.next().await.unwrap();
6691
6692 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6693 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6694 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6695 assert!(cx.read(|cx| editor.is_dirty(cx)));
6696
6697 let save = editor
6698 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6699 .unwrap();
6700 fake_server
6701 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6702 assert_eq!(
6703 params.text_document.uri,
6704 lsp::Url::from_file_path("/file.rs").unwrap()
6705 );
6706 assert_eq!(params.options.tab_size, 4);
6707 Ok(Some(vec![lsp::TextEdit::new(
6708 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6709 ", ".to_string(),
6710 )]))
6711 })
6712 .next()
6713 .await;
6714 cx.executor().start_waiting();
6715 save.await;
6716
6717 assert_eq!(
6718 editor.update(cx, |editor, cx| editor.text(cx)),
6719 "one, two\nthree\n"
6720 );
6721 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6722
6723 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6724 assert!(cx.read(|cx| editor.is_dirty(cx)));
6725
6726 // Ensure we can still save even if formatting hangs.
6727 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6728 assert_eq!(
6729 params.text_document.uri,
6730 lsp::Url::from_file_path("/file.rs").unwrap()
6731 );
6732 futures::future::pending::<()>().await;
6733 unreachable!()
6734 });
6735 let save = editor
6736 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6737 .unwrap();
6738 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6739 cx.executor().start_waiting();
6740 save.await;
6741 assert_eq!(
6742 editor.update(cx, |editor, cx| editor.text(cx)),
6743 "one\ntwo\nthree\n"
6744 );
6745 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6746
6747 // For non-dirty buffer, no formatting request should be sent
6748 let save = editor
6749 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6750 .unwrap();
6751 let _pending_format_request = fake_server
6752 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6753 panic!("Should not be invoked on non-dirty buffer");
6754 })
6755 .next();
6756 cx.executor().start_waiting();
6757 save.await;
6758
6759 // Set rust language override and assert overridden tabsize is sent to language server
6760 update_test_language_settings(cx, |settings| {
6761 settings.languages.insert(
6762 "Rust".into(),
6763 LanguageSettingsContent {
6764 tab_size: NonZeroU32::new(8),
6765 ..Default::default()
6766 },
6767 );
6768 });
6769
6770 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6771 assert!(cx.read(|cx| editor.is_dirty(cx)));
6772 let save = editor
6773 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6774 .unwrap();
6775 fake_server
6776 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6777 assert_eq!(
6778 params.text_document.uri,
6779 lsp::Url::from_file_path("/file.rs").unwrap()
6780 );
6781 assert_eq!(params.options.tab_size, 8);
6782 Ok(Some(vec![]))
6783 })
6784 .next()
6785 .await;
6786 cx.executor().start_waiting();
6787 save.await;
6788}
6789
6790#[gpui::test]
6791async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6792 init_test(cx, |_| {});
6793
6794 let cols = 4;
6795 let rows = 10;
6796 let sample_text_1 = sample_text(rows, cols, 'a');
6797 assert_eq!(
6798 sample_text_1,
6799 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6800 );
6801 let sample_text_2 = sample_text(rows, cols, 'l');
6802 assert_eq!(
6803 sample_text_2,
6804 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6805 );
6806 let sample_text_3 = sample_text(rows, cols, 'v');
6807 assert_eq!(
6808 sample_text_3,
6809 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6810 );
6811
6812 let fs = FakeFs::new(cx.executor());
6813 fs.insert_tree(
6814 "/a",
6815 json!({
6816 "main.rs": sample_text_1,
6817 "other.rs": sample_text_2,
6818 "lib.rs": sample_text_3,
6819 }),
6820 )
6821 .await;
6822
6823 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6824 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6825 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6826
6827 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6828 language_registry.add(rust_lang());
6829 let mut fake_servers = language_registry.register_fake_lsp(
6830 "Rust",
6831 FakeLspAdapter {
6832 capabilities: lsp::ServerCapabilities {
6833 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6834 ..Default::default()
6835 },
6836 ..Default::default()
6837 },
6838 );
6839
6840 let worktree = project.update(cx, |project, cx| {
6841 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6842 assert_eq!(worktrees.len(), 1);
6843 worktrees.pop().unwrap()
6844 });
6845 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6846
6847 let buffer_1 = project
6848 .update(cx, |project, cx| {
6849 project.open_buffer((worktree_id, "main.rs"), cx)
6850 })
6851 .await
6852 .unwrap();
6853 let buffer_2 = project
6854 .update(cx, |project, cx| {
6855 project.open_buffer((worktree_id, "other.rs"), cx)
6856 })
6857 .await
6858 .unwrap();
6859 let buffer_3 = project
6860 .update(cx, |project, cx| {
6861 project.open_buffer((worktree_id, "lib.rs"), cx)
6862 })
6863 .await
6864 .unwrap();
6865
6866 let multi_buffer = cx.new_model(|cx| {
6867 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6868 multi_buffer.push_excerpts(
6869 buffer_1.clone(),
6870 [
6871 ExcerptRange {
6872 context: Point::new(0, 0)..Point::new(3, 0),
6873 primary: None,
6874 },
6875 ExcerptRange {
6876 context: Point::new(5, 0)..Point::new(7, 0),
6877 primary: None,
6878 },
6879 ExcerptRange {
6880 context: Point::new(9, 0)..Point::new(10, 4),
6881 primary: None,
6882 },
6883 ],
6884 cx,
6885 );
6886 multi_buffer.push_excerpts(
6887 buffer_2.clone(),
6888 [
6889 ExcerptRange {
6890 context: Point::new(0, 0)..Point::new(3, 0),
6891 primary: None,
6892 },
6893 ExcerptRange {
6894 context: Point::new(5, 0)..Point::new(7, 0),
6895 primary: None,
6896 },
6897 ExcerptRange {
6898 context: Point::new(9, 0)..Point::new(10, 4),
6899 primary: None,
6900 },
6901 ],
6902 cx,
6903 );
6904 multi_buffer.push_excerpts(
6905 buffer_3.clone(),
6906 [
6907 ExcerptRange {
6908 context: Point::new(0, 0)..Point::new(3, 0),
6909 primary: None,
6910 },
6911 ExcerptRange {
6912 context: Point::new(5, 0)..Point::new(7, 0),
6913 primary: None,
6914 },
6915 ExcerptRange {
6916 context: Point::new(9, 0)..Point::new(10, 4),
6917 primary: None,
6918 },
6919 ],
6920 cx,
6921 );
6922 multi_buffer
6923 });
6924 let multi_buffer_editor = cx.new_view(|cx| {
6925 Editor::new(
6926 EditorMode::Full,
6927 multi_buffer,
6928 Some(project.clone()),
6929 true,
6930 cx,
6931 )
6932 });
6933
6934 multi_buffer_editor.update(cx, |editor, cx| {
6935 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6936 editor.insert("|one|two|three|", cx);
6937 });
6938 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6939 multi_buffer_editor.update(cx, |editor, cx| {
6940 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6941 s.select_ranges(Some(60..70))
6942 });
6943 editor.insert("|four|five|six|", cx);
6944 });
6945 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6946
6947 // First two buffers should be edited, but not the third one.
6948 assert_eq!(
6949 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6950 "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}",
6951 );
6952 buffer_1.update(cx, |buffer, _| {
6953 assert!(buffer.is_dirty());
6954 assert_eq!(
6955 buffer.text(),
6956 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6957 )
6958 });
6959 buffer_2.update(cx, |buffer, _| {
6960 assert!(buffer.is_dirty());
6961 assert_eq!(
6962 buffer.text(),
6963 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6964 )
6965 });
6966 buffer_3.update(cx, |buffer, _| {
6967 assert!(!buffer.is_dirty());
6968 assert_eq!(buffer.text(), sample_text_3,)
6969 });
6970
6971 cx.executor().start_waiting();
6972 let save = multi_buffer_editor
6973 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6974 .unwrap();
6975
6976 let fake_server = fake_servers.next().await.unwrap();
6977 fake_server
6978 .server
6979 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6980 Ok(Some(vec![lsp::TextEdit::new(
6981 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6982 format!("[{} formatted]", params.text_document.uri),
6983 )]))
6984 })
6985 .detach();
6986 save.await;
6987
6988 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6989 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6990 assert_eq!(
6991 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6992 "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}",
6993 );
6994 buffer_1.update(cx, |buffer, _| {
6995 assert!(!buffer.is_dirty());
6996 assert_eq!(
6997 buffer.text(),
6998 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6999 )
7000 });
7001 buffer_2.update(cx, |buffer, _| {
7002 assert!(!buffer.is_dirty());
7003 assert_eq!(
7004 buffer.text(),
7005 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7006 )
7007 });
7008 buffer_3.update(cx, |buffer, _| {
7009 assert!(!buffer.is_dirty());
7010 assert_eq!(buffer.text(), sample_text_3,)
7011 });
7012}
7013
7014#[gpui::test]
7015async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7016 init_test(cx, |_| {});
7017
7018 let fs = FakeFs::new(cx.executor());
7019 fs.insert_file("/file.rs", Default::default()).await;
7020
7021 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7022
7023 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7024 language_registry.add(rust_lang());
7025 let mut fake_servers = language_registry.register_fake_lsp(
7026 "Rust",
7027 FakeLspAdapter {
7028 capabilities: lsp::ServerCapabilities {
7029 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7030 ..Default::default()
7031 },
7032 ..Default::default()
7033 },
7034 );
7035
7036 let buffer = project
7037 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7038 .await
7039 .unwrap();
7040
7041 cx.executor().start_waiting();
7042 let fake_server = fake_servers.next().await.unwrap();
7043
7044 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7045 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7046 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7047 assert!(cx.read(|cx| editor.is_dirty(cx)));
7048
7049 let save = editor
7050 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7051 .unwrap();
7052 fake_server
7053 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7054 assert_eq!(
7055 params.text_document.uri,
7056 lsp::Url::from_file_path("/file.rs").unwrap()
7057 );
7058 assert_eq!(params.options.tab_size, 4);
7059 Ok(Some(vec![lsp::TextEdit::new(
7060 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7061 ", ".to_string(),
7062 )]))
7063 })
7064 .next()
7065 .await;
7066 cx.executor().start_waiting();
7067 save.await;
7068 assert_eq!(
7069 editor.update(cx, |editor, cx| editor.text(cx)),
7070 "one, two\nthree\n"
7071 );
7072 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7073
7074 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7075 assert!(cx.read(|cx| editor.is_dirty(cx)));
7076
7077 // Ensure we can still save even if formatting hangs.
7078 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7079 move |params, _| async move {
7080 assert_eq!(
7081 params.text_document.uri,
7082 lsp::Url::from_file_path("/file.rs").unwrap()
7083 );
7084 futures::future::pending::<()>().await;
7085 unreachable!()
7086 },
7087 );
7088 let save = editor
7089 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7090 .unwrap();
7091 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7092 cx.executor().start_waiting();
7093 save.await;
7094 assert_eq!(
7095 editor.update(cx, |editor, cx| editor.text(cx)),
7096 "one\ntwo\nthree\n"
7097 );
7098 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7099
7100 // For non-dirty buffer, no formatting request should be sent
7101 let save = editor
7102 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7103 .unwrap();
7104 let _pending_format_request = fake_server
7105 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7106 panic!("Should not be invoked on non-dirty buffer");
7107 })
7108 .next();
7109 cx.executor().start_waiting();
7110 save.await;
7111
7112 // Set Rust language override and assert overridden tabsize is sent to language server
7113 update_test_language_settings(cx, |settings| {
7114 settings.languages.insert(
7115 "Rust".into(),
7116 LanguageSettingsContent {
7117 tab_size: NonZeroU32::new(8),
7118 ..Default::default()
7119 },
7120 );
7121 });
7122
7123 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7124 assert!(cx.read(|cx| editor.is_dirty(cx)));
7125 let save = editor
7126 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7127 .unwrap();
7128 fake_server
7129 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7130 assert_eq!(
7131 params.text_document.uri,
7132 lsp::Url::from_file_path("/file.rs").unwrap()
7133 );
7134 assert_eq!(params.options.tab_size, 8);
7135 Ok(Some(vec![]))
7136 })
7137 .next()
7138 .await;
7139 cx.executor().start_waiting();
7140 save.await;
7141}
7142
7143#[gpui::test]
7144async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7145 init_test(cx, |settings| {
7146 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7147 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7148 ))
7149 });
7150
7151 let fs = FakeFs::new(cx.executor());
7152 fs.insert_file("/file.rs", Default::default()).await;
7153
7154 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7155
7156 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7157 language_registry.add(Arc::new(Language::new(
7158 LanguageConfig {
7159 name: "Rust".into(),
7160 matcher: LanguageMatcher {
7161 path_suffixes: vec!["rs".to_string()],
7162 ..Default::default()
7163 },
7164 ..LanguageConfig::default()
7165 },
7166 Some(tree_sitter_rust::LANGUAGE.into()),
7167 )));
7168 update_test_language_settings(cx, |settings| {
7169 // Enable Prettier formatting for the same buffer, and ensure
7170 // LSP is called instead of Prettier.
7171 settings.defaults.prettier = Some(PrettierSettings {
7172 allowed: true,
7173 ..PrettierSettings::default()
7174 });
7175 });
7176 let mut fake_servers = language_registry.register_fake_lsp(
7177 "Rust",
7178 FakeLspAdapter {
7179 capabilities: lsp::ServerCapabilities {
7180 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7181 ..Default::default()
7182 },
7183 ..Default::default()
7184 },
7185 );
7186
7187 let buffer = project
7188 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7189 .await
7190 .unwrap();
7191
7192 cx.executor().start_waiting();
7193 let fake_server = fake_servers.next().await.unwrap();
7194
7195 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7196 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7197 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7198
7199 let format = editor
7200 .update(cx, |editor, cx| {
7201 editor.perform_format(
7202 project.clone(),
7203 FormatTrigger::Manual,
7204 FormatTarget::Buffer,
7205 cx,
7206 )
7207 })
7208 .unwrap();
7209 fake_server
7210 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7211 assert_eq!(
7212 params.text_document.uri,
7213 lsp::Url::from_file_path("/file.rs").unwrap()
7214 );
7215 assert_eq!(params.options.tab_size, 4);
7216 Ok(Some(vec![lsp::TextEdit::new(
7217 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7218 ", ".to_string(),
7219 )]))
7220 })
7221 .next()
7222 .await;
7223 cx.executor().start_waiting();
7224 format.await;
7225 assert_eq!(
7226 editor.update(cx, |editor, cx| editor.text(cx)),
7227 "one, two\nthree\n"
7228 );
7229
7230 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7231 // Ensure we don't lock if formatting hangs.
7232 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7233 assert_eq!(
7234 params.text_document.uri,
7235 lsp::Url::from_file_path("/file.rs").unwrap()
7236 );
7237 futures::future::pending::<()>().await;
7238 unreachable!()
7239 });
7240 let format = editor
7241 .update(cx, |editor, cx| {
7242 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7243 })
7244 .unwrap();
7245 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7246 cx.executor().start_waiting();
7247 format.await;
7248 assert_eq!(
7249 editor.update(cx, |editor, cx| editor.text(cx)),
7250 "one\ntwo\nthree\n"
7251 );
7252}
7253
7254#[gpui::test]
7255async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7256 init_test(cx, |_| {});
7257
7258 let mut cx = EditorLspTestContext::new_rust(
7259 lsp::ServerCapabilities {
7260 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7261 ..Default::default()
7262 },
7263 cx,
7264 )
7265 .await;
7266
7267 cx.set_state(indoc! {"
7268 one.twoˇ
7269 "});
7270
7271 // The format request takes a long time. When it completes, it inserts
7272 // a newline and an indent before the `.`
7273 cx.lsp
7274 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7275 let executor = cx.background_executor().clone();
7276 async move {
7277 executor.timer(Duration::from_millis(100)).await;
7278 Ok(Some(vec![lsp::TextEdit {
7279 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7280 new_text: "\n ".into(),
7281 }]))
7282 }
7283 });
7284
7285 // Submit a format request.
7286 let format_1 = cx
7287 .update_editor(|editor, cx| editor.format(&Format, cx))
7288 .unwrap();
7289 cx.executor().run_until_parked();
7290
7291 // Submit a second format request.
7292 let format_2 = cx
7293 .update_editor(|editor, cx| editor.format(&Format, cx))
7294 .unwrap();
7295 cx.executor().run_until_parked();
7296
7297 // Wait for both format requests to complete
7298 cx.executor().advance_clock(Duration::from_millis(200));
7299 cx.executor().start_waiting();
7300 format_1.await.unwrap();
7301 cx.executor().start_waiting();
7302 format_2.await.unwrap();
7303
7304 // The formatting edits only happens once.
7305 cx.assert_editor_state(indoc! {"
7306 one
7307 .twoˇ
7308 "});
7309}
7310
7311#[gpui::test]
7312async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7313 init_test(cx, |settings| {
7314 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7315 });
7316
7317 let mut cx = EditorLspTestContext::new_rust(
7318 lsp::ServerCapabilities {
7319 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7320 ..Default::default()
7321 },
7322 cx,
7323 )
7324 .await;
7325
7326 // Set up a buffer white some trailing whitespace and no trailing newline.
7327 cx.set_state(
7328 &[
7329 "one ", //
7330 "twoˇ", //
7331 "three ", //
7332 "four", //
7333 ]
7334 .join("\n"),
7335 );
7336
7337 // Submit a format request.
7338 let format = cx
7339 .update_editor(|editor, cx| editor.format(&Format, cx))
7340 .unwrap();
7341
7342 // Record which buffer changes have been sent to the language server
7343 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7344 cx.lsp
7345 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7346 let buffer_changes = buffer_changes.clone();
7347 move |params, _| {
7348 buffer_changes.lock().extend(
7349 params
7350 .content_changes
7351 .into_iter()
7352 .map(|e| (e.range.unwrap(), e.text)),
7353 );
7354 }
7355 });
7356
7357 // Handle formatting requests to the language server.
7358 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7359 let buffer_changes = buffer_changes.clone();
7360 move |_, _| {
7361 // When formatting is requested, trailing whitespace has already been stripped,
7362 // and the trailing newline has already been added.
7363 assert_eq!(
7364 &buffer_changes.lock()[1..],
7365 &[
7366 (
7367 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7368 "".into()
7369 ),
7370 (
7371 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7372 "".into()
7373 ),
7374 (
7375 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7376 "\n".into()
7377 ),
7378 ]
7379 );
7380
7381 // Insert blank lines between each line of the buffer.
7382 async move {
7383 Ok(Some(vec![
7384 lsp::TextEdit {
7385 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7386 new_text: "\n".into(),
7387 },
7388 lsp::TextEdit {
7389 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7390 new_text: "\n".into(),
7391 },
7392 ]))
7393 }
7394 }
7395 });
7396
7397 // After formatting the buffer, the trailing whitespace is stripped,
7398 // a newline is appended, and the edits provided by the language server
7399 // have been applied.
7400 format.await.unwrap();
7401 cx.assert_editor_state(
7402 &[
7403 "one", //
7404 "", //
7405 "twoˇ", //
7406 "", //
7407 "three", //
7408 "four", //
7409 "", //
7410 ]
7411 .join("\n"),
7412 );
7413
7414 // Undoing the formatting undoes the trailing whitespace removal, the
7415 // trailing newline, and the LSP edits.
7416 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7417 cx.assert_editor_state(
7418 &[
7419 "one ", //
7420 "twoˇ", //
7421 "three ", //
7422 "four", //
7423 ]
7424 .join("\n"),
7425 );
7426}
7427
7428#[gpui::test]
7429async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7430 cx: &mut gpui::TestAppContext,
7431) {
7432 init_test(cx, |_| {});
7433
7434 cx.update(|cx| {
7435 cx.update_global::<SettingsStore, _>(|settings, cx| {
7436 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7437 settings.auto_signature_help = Some(true);
7438 });
7439 });
7440 });
7441
7442 let mut cx = EditorLspTestContext::new_rust(
7443 lsp::ServerCapabilities {
7444 signature_help_provider: Some(lsp::SignatureHelpOptions {
7445 ..Default::default()
7446 }),
7447 ..Default::default()
7448 },
7449 cx,
7450 )
7451 .await;
7452
7453 let language = Language::new(
7454 LanguageConfig {
7455 name: "Rust".into(),
7456 brackets: BracketPairConfig {
7457 pairs: vec![
7458 BracketPair {
7459 start: "{".to_string(),
7460 end: "}".to_string(),
7461 close: true,
7462 surround: true,
7463 newline: true,
7464 },
7465 BracketPair {
7466 start: "(".to_string(),
7467 end: ")".to_string(),
7468 close: true,
7469 surround: true,
7470 newline: true,
7471 },
7472 BracketPair {
7473 start: "/*".to_string(),
7474 end: " */".to_string(),
7475 close: true,
7476 surround: true,
7477 newline: true,
7478 },
7479 BracketPair {
7480 start: "[".to_string(),
7481 end: "]".to_string(),
7482 close: false,
7483 surround: false,
7484 newline: true,
7485 },
7486 BracketPair {
7487 start: "\"".to_string(),
7488 end: "\"".to_string(),
7489 close: true,
7490 surround: true,
7491 newline: false,
7492 },
7493 BracketPair {
7494 start: "<".to_string(),
7495 end: ">".to_string(),
7496 close: false,
7497 surround: true,
7498 newline: true,
7499 },
7500 ],
7501 ..Default::default()
7502 },
7503 autoclose_before: "})]".to_string(),
7504 ..Default::default()
7505 },
7506 Some(tree_sitter_rust::LANGUAGE.into()),
7507 );
7508 let language = Arc::new(language);
7509
7510 cx.language_registry().add(language.clone());
7511 cx.update_buffer(|buffer, cx| {
7512 buffer.set_language(Some(language), cx);
7513 });
7514
7515 cx.set_state(
7516 &r#"
7517 fn main() {
7518 sampleˇ
7519 }
7520 "#
7521 .unindent(),
7522 );
7523
7524 cx.update_editor(|view, cx| {
7525 view.handle_input("(", cx);
7526 });
7527 cx.assert_editor_state(
7528 &"
7529 fn main() {
7530 sample(ˇ)
7531 }
7532 "
7533 .unindent(),
7534 );
7535
7536 let mocked_response = lsp::SignatureHelp {
7537 signatures: vec![lsp::SignatureInformation {
7538 label: "fn sample(param1: u8, param2: u8)".to_string(),
7539 documentation: None,
7540 parameters: Some(vec![
7541 lsp::ParameterInformation {
7542 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7543 documentation: None,
7544 },
7545 lsp::ParameterInformation {
7546 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7547 documentation: None,
7548 },
7549 ]),
7550 active_parameter: None,
7551 }],
7552 active_signature: Some(0),
7553 active_parameter: Some(0),
7554 };
7555 handle_signature_help_request(&mut cx, mocked_response).await;
7556
7557 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7558 .await;
7559
7560 cx.editor(|editor, _| {
7561 let signature_help_state = editor.signature_help_state.popover().cloned();
7562 assert!(signature_help_state.is_some());
7563 let ParsedMarkdown {
7564 text, highlights, ..
7565 } = signature_help_state.unwrap().parsed_content;
7566 assert_eq!(text, "param1: u8, param2: u8");
7567 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7568 });
7569}
7570
7571#[gpui::test]
7572async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7573 init_test(cx, |_| {});
7574
7575 cx.update(|cx| {
7576 cx.update_global::<SettingsStore, _>(|settings, cx| {
7577 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7578 settings.auto_signature_help = Some(false);
7579 settings.show_signature_help_after_edits = Some(false);
7580 });
7581 });
7582 });
7583
7584 let mut cx = EditorLspTestContext::new_rust(
7585 lsp::ServerCapabilities {
7586 signature_help_provider: Some(lsp::SignatureHelpOptions {
7587 ..Default::default()
7588 }),
7589 ..Default::default()
7590 },
7591 cx,
7592 )
7593 .await;
7594
7595 let language = Language::new(
7596 LanguageConfig {
7597 name: "Rust".into(),
7598 brackets: BracketPairConfig {
7599 pairs: vec![
7600 BracketPair {
7601 start: "{".to_string(),
7602 end: "}".to_string(),
7603 close: true,
7604 surround: true,
7605 newline: true,
7606 },
7607 BracketPair {
7608 start: "(".to_string(),
7609 end: ")".to_string(),
7610 close: true,
7611 surround: true,
7612 newline: true,
7613 },
7614 BracketPair {
7615 start: "/*".to_string(),
7616 end: " */".to_string(),
7617 close: true,
7618 surround: true,
7619 newline: true,
7620 },
7621 BracketPair {
7622 start: "[".to_string(),
7623 end: "]".to_string(),
7624 close: false,
7625 surround: false,
7626 newline: true,
7627 },
7628 BracketPair {
7629 start: "\"".to_string(),
7630 end: "\"".to_string(),
7631 close: true,
7632 surround: true,
7633 newline: false,
7634 },
7635 BracketPair {
7636 start: "<".to_string(),
7637 end: ">".to_string(),
7638 close: false,
7639 surround: true,
7640 newline: true,
7641 },
7642 ],
7643 ..Default::default()
7644 },
7645 autoclose_before: "})]".to_string(),
7646 ..Default::default()
7647 },
7648 Some(tree_sitter_rust::LANGUAGE.into()),
7649 );
7650 let language = Arc::new(language);
7651
7652 cx.language_registry().add(language.clone());
7653 cx.update_buffer(|buffer, cx| {
7654 buffer.set_language(Some(language), cx);
7655 });
7656
7657 // Ensure that signature_help is not called when no signature help is enabled.
7658 cx.set_state(
7659 &r#"
7660 fn main() {
7661 sampleˇ
7662 }
7663 "#
7664 .unindent(),
7665 );
7666 cx.update_editor(|view, cx| {
7667 view.handle_input("(", cx);
7668 });
7669 cx.assert_editor_state(
7670 &"
7671 fn main() {
7672 sample(ˇ)
7673 }
7674 "
7675 .unindent(),
7676 );
7677 cx.editor(|editor, _| {
7678 assert!(editor.signature_help_state.task().is_none());
7679 });
7680
7681 let mocked_response = lsp::SignatureHelp {
7682 signatures: vec![lsp::SignatureInformation {
7683 label: "fn sample(param1: u8, param2: u8)".to_string(),
7684 documentation: None,
7685 parameters: Some(vec![
7686 lsp::ParameterInformation {
7687 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7688 documentation: None,
7689 },
7690 lsp::ParameterInformation {
7691 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7692 documentation: None,
7693 },
7694 ]),
7695 active_parameter: None,
7696 }],
7697 active_signature: Some(0),
7698 active_parameter: Some(0),
7699 };
7700
7701 // Ensure that signature_help is called when enabled afte edits
7702 cx.update(|cx| {
7703 cx.update_global::<SettingsStore, _>(|settings, cx| {
7704 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7705 settings.auto_signature_help = Some(false);
7706 settings.show_signature_help_after_edits = Some(true);
7707 });
7708 });
7709 });
7710 cx.set_state(
7711 &r#"
7712 fn main() {
7713 sampleˇ
7714 }
7715 "#
7716 .unindent(),
7717 );
7718 cx.update_editor(|view, cx| {
7719 view.handle_input("(", cx);
7720 });
7721 cx.assert_editor_state(
7722 &"
7723 fn main() {
7724 sample(ˇ)
7725 }
7726 "
7727 .unindent(),
7728 );
7729 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7730 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7731 .await;
7732 cx.update_editor(|editor, _| {
7733 let signature_help_state = editor.signature_help_state.popover().cloned();
7734 assert!(signature_help_state.is_some());
7735 let ParsedMarkdown {
7736 text, highlights, ..
7737 } = signature_help_state.unwrap().parsed_content;
7738 assert_eq!(text, "param1: u8, param2: u8");
7739 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7740 editor.signature_help_state = SignatureHelpState::default();
7741 });
7742
7743 // Ensure that signature_help is called when auto signature help override is enabled
7744 cx.update(|cx| {
7745 cx.update_global::<SettingsStore, _>(|settings, cx| {
7746 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7747 settings.auto_signature_help = Some(true);
7748 settings.show_signature_help_after_edits = Some(false);
7749 });
7750 });
7751 });
7752 cx.set_state(
7753 &r#"
7754 fn main() {
7755 sampleˇ
7756 }
7757 "#
7758 .unindent(),
7759 );
7760 cx.update_editor(|view, cx| {
7761 view.handle_input("(", cx);
7762 });
7763 cx.assert_editor_state(
7764 &"
7765 fn main() {
7766 sample(ˇ)
7767 }
7768 "
7769 .unindent(),
7770 );
7771 handle_signature_help_request(&mut cx, mocked_response).await;
7772 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7773 .await;
7774 cx.editor(|editor, _| {
7775 let signature_help_state = editor.signature_help_state.popover().cloned();
7776 assert!(signature_help_state.is_some());
7777 let ParsedMarkdown {
7778 text, highlights, ..
7779 } = signature_help_state.unwrap().parsed_content;
7780 assert_eq!(text, "param1: u8, param2: u8");
7781 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7782 });
7783}
7784
7785#[gpui::test]
7786async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7787 init_test(cx, |_| {});
7788 cx.update(|cx| {
7789 cx.update_global::<SettingsStore, _>(|settings, cx| {
7790 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7791 settings.auto_signature_help = Some(true);
7792 });
7793 });
7794 });
7795
7796 let mut cx = EditorLspTestContext::new_rust(
7797 lsp::ServerCapabilities {
7798 signature_help_provider: Some(lsp::SignatureHelpOptions {
7799 ..Default::default()
7800 }),
7801 ..Default::default()
7802 },
7803 cx,
7804 )
7805 .await;
7806
7807 // A test that directly calls `show_signature_help`
7808 cx.update_editor(|editor, cx| {
7809 editor.show_signature_help(&ShowSignatureHelp, cx);
7810 });
7811
7812 let mocked_response = lsp::SignatureHelp {
7813 signatures: vec![lsp::SignatureInformation {
7814 label: "fn sample(param1: u8, param2: u8)".to_string(),
7815 documentation: None,
7816 parameters: Some(vec![
7817 lsp::ParameterInformation {
7818 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7819 documentation: None,
7820 },
7821 lsp::ParameterInformation {
7822 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7823 documentation: None,
7824 },
7825 ]),
7826 active_parameter: None,
7827 }],
7828 active_signature: Some(0),
7829 active_parameter: Some(0),
7830 };
7831 handle_signature_help_request(&mut cx, mocked_response).await;
7832
7833 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7834 .await;
7835
7836 cx.editor(|editor, _| {
7837 let signature_help_state = editor.signature_help_state.popover().cloned();
7838 assert!(signature_help_state.is_some());
7839 let ParsedMarkdown {
7840 text, highlights, ..
7841 } = signature_help_state.unwrap().parsed_content;
7842 assert_eq!(text, "param1: u8, param2: u8");
7843 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7844 });
7845
7846 // When exiting outside from inside the brackets, `signature_help` is closed.
7847 cx.set_state(indoc! {"
7848 fn main() {
7849 sample(ˇ);
7850 }
7851
7852 fn sample(param1: u8, param2: u8) {}
7853 "});
7854
7855 cx.update_editor(|editor, cx| {
7856 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7857 });
7858
7859 let mocked_response = lsp::SignatureHelp {
7860 signatures: Vec::new(),
7861 active_signature: None,
7862 active_parameter: None,
7863 };
7864 handle_signature_help_request(&mut cx, mocked_response).await;
7865
7866 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7867 .await;
7868
7869 cx.editor(|editor, _| {
7870 assert!(!editor.signature_help_state.is_shown());
7871 });
7872
7873 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7874 cx.set_state(indoc! {"
7875 fn main() {
7876 sample(ˇ);
7877 }
7878
7879 fn sample(param1: u8, param2: u8) {}
7880 "});
7881
7882 let mocked_response = lsp::SignatureHelp {
7883 signatures: vec![lsp::SignatureInformation {
7884 label: "fn sample(param1: u8, param2: u8)".to_string(),
7885 documentation: None,
7886 parameters: Some(vec![
7887 lsp::ParameterInformation {
7888 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7889 documentation: None,
7890 },
7891 lsp::ParameterInformation {
7892 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7893 documentation: None,
7894 },
7895 ]),
7896 active_parameter: None,
7897 }],
7898 active_signature: Some(0),
7899 active_parameter: Some(0),
7900 };
7901 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7902 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7903 .await;
7904 cx.editor(|editor, _| {
7905 assert!(editor.signature_help_state.is_shown());
7906 });
7907
7908 // Restore the popover with more parameter input
7909 cx.set_state(indoc! {"
7910 fn main() {
7911 sample(param1, param2ˇ);
7912 }
7913
7914 fn sample(param1: u8, param2: u8) {}
7915 "});
7916
7917 let mocked_response = lsp::SignatureHelp {
7918 signatures: vec![lsp::SignatureInformation {
7919 label: "fn sample(param1: u8, param2: u8)".to_string(),
7920 documentation: None,
7921 parameters: Some(vec![
7922 lsp::ParameterInformation {
7923 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7924 documentation: None,
7925 },
7926 lsp::ParameterInformation {
7927 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7928 documentation: None,
7929 },
7930 ]),
7931 active_parameter: None,
7932 }],
7933 active_signature: Some(0),
7934 active_parameter: Some(1),
7935 };
7936 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7937 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7938 .await;
7939
7940 // When selecting a range, the popover is gone.
7941 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7942 cx.update_editor(|editor, cx| {
7943 editor.change_selections(None, cx, |s| {
7944 s.select_ranges(Some(Point::new(1, 25)..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 cx.editor(|editor, _| {
7955 assert!(!editor.signature_help_state.is_shown());
7956 });
7957
7958 // When unselecting again, the popover is back if within the brackets.
7959 cx.update_editor(|editor, cx| {
7960 editor.change_selections(None, cx, |s| {
7961 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7962 })
7963 });
7964 cx.assert_editor_state(indoc! {"
7965 fn main() {
7966 sample(param1, ˇparam2);
7967 }
7968
7969 fn sample(param1: u8, param2: u8) {}
7970 "});
7971 handle_signature_help_request(&mut cx, mocked_response).await;
7972 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7973 .await;
7974 cx.editor(|editor, _| {
7975 assert!(editor.signature_help_state.is_shown());
7976 });
7977
7978 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7979 cx.update_editor(|editor, cx| {
7980 editor.change_selections(None, cx, |s| {
7981 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7982 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7983 })
7984 });
7985 cx.assert_editor_state(indoc! {"
7986 fn main() {
7987 sample(param1, ˇparam2);
7988 }
7989
7990 fn sample(param1: u8, param2: u8) {}
7991 "});
7992
7993 let mocked_response = lsp::SignatureHelp {
7994 signatures: vec![lsp::SignatureInformation {
7995 label: "fn sample(param1: u8, param2: u8)".to_string(),
7996 documentation: None,
7997 parameters: Some(vec![
7998 lsp::ParameterInformation {
7999 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8000 documentation: None,
8001 },
8002 lsp::ParameterInformation {
8003 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8004 documentation: None,
8005 },
8006 ]),
8007 active_parameter: None,
8008 }],
8009 active_signature: Some(0),
8010 active_parameter: Some(1),
8011 };
8012 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8013 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8014 .await;
8015 cx.update_editor(|editor, cx| {
8016 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8017 });
8018 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8019 .await;
8020 cx.update_editor(|editor, cx| {
8021 editor.change_selections(None, cx, |s| {
8022 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8023 })
8024 });
8025 cx.assert_editor_state(indoc! {"
8026 fn main() {
8027 sample(param1, «ˇparam2»);
8028 }
8029
8030 fn sample(param1: u8, param2: u8) {}
8031 "});
8032 cx.update_editor(|editor, cx| {
8033 editor.change_selections(None, cx, |s| {
8034 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8035 })
8036 });
8037 cx.assert_editor_state(indoc! {"
8038 fn main() {
8039 sample(param1, ˇparam2);
8040 }
8041
8042 fn sample(param1: u8, param2: u8) {}
8043 "});
8044 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8045 .await;
8046}
8047
8048#[gpui::test]
8049async fn test_completion(cx: &mut gpui::TestAppContext) {
8050 init_test(cx, |_| {});
8051
8052 let mut cx = EditorLspTestContext::new_rust(
8053 lsp::ServerCapabilities {
8054 completion_provider: Some(lsp::CompletionOptions {
8055 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8056 resolve_provider: Some(true),
8057 ..Default::default()
8058 }),
8059 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8060 ..Default::default()
8061 },
8062 cx,
8063 )
8064 .await;
8065 let counter = Arc::new(AtomicUsize::new(0));
8066
8067 cx.set_state(indoc! {"
8068 oneˇ
8069 two
8070 three
8071 "});
8072 cx.simulate_keystroke(".");
8073 handle_completion_request(
8074 &mut cx,
8075 indoc! {"
8076 one.|<>
8077 two
8078 three
8079 "},
8080 vec!["first_completion", "second_completion"],
8081 counter.clone(),
8082 )
8083 .await;
8084 cx.condition(|editor, _| editor.context_menu_visible())
8085 .await;
8086 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8087
8088 let _handler = handle_signature_help_request(
8089 &mut cx,
8090 lsp::SignatureHelp {
8091 signatures: vec![lsp::SignatureInformation {
8092 label: "test signature".to_string(),
8093 documentation: None,
8094 parameters: Some(vec![lsp::ParameterInformation {
8095 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8096 documentation: None,
8097 }]),
8098 active_parameter: None,
8099 }],
8100 active_signature: None,
8101 active_parameter: None,
8102 },
8103 );
8104 cx.update_editor(|editor, cx| {
8105 assert!(
8106 !editor.signature_help_state.is_shown(),
8107 "No signature help was called for"
8108 );
8109 editor.show_signature_help(&ShowSignatureHelp, cx);
8110 });
8111 cx.run_until_parked();
8112 cx.update_editor(|editor, _| {
8113 assert!(
8114 !editor.signature_help_state.is_shown(),
8115 "No signature help should be shown when completions menu is open"
8116 );
8117 });
8118
8119 let apply_additional_edits = cx.update_editor(|editor, cx| {
8120 editor.context_menu_next(&Default::default(), cx);
8121 editor
8122 .confirm_completion(&ConfirmCompletion::default(), cx)
8123 .unwrap()
8124 });
8125 cx.assert_editor_state(indoc! {"
8126 one.second_completionˇ
8127 two
8128 three
8129 "});
8130
8131 handle_resolve_completion_request(
8132 &mut cx,
8133 Some(vec![
8134 (
8135 //This overlaps with the primary completion edit which is
8136 //misbehavior from the LSP spec, test that we filter it out
8137 indoc! {"
8138 one.second_ˇcompletion
8139 two
8140 threeˇ
8141 "},
8142 "overlapping additional edit",
8143 ),
8144 (
8145 indoc! {"
8146 one.second_completion
8147 two
8148 threeˇ
8149 "},
8150 "\nadditional edit",
8151 ),
8152 ]),
8153 )
8154 .await;
8155 apply_additional_edits.await.unwrap();
8156 cx.assert_editor_state(indoc! {"
8157 one.second_completionˇ
8158 two
8159 three
8160 additional edit
8161 "});
8162
8163 cx.set_state(indoc! {"
8164 one.second_completion
8165 twoˇ
8166 threeˇ
8167 additional edit
8168 "});
8169 cx.simulate_keystroke(" ");
8170 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8171 cx.simulate_keystroke("s");
8172 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8173
8174 cx.assert_editor_state(indoc! {"
8175 one.second_completion
8176 two sˇ
8177 three sˇ
8178 additional edit
8179 "});
8180 handle_completion_request(
8181 &mut cx,
8182 indoc! {"
8183 one.second_completion
8184 two s
8185 three <s|>
8186 additional edit
8187 "},
8188 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8189 counter.clone(),
8190 )
8191 .await;
8192 cx.condition(|editor, _| editor.context_menu_visible())
8193 .await;
8194 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8195
8196 cx.simulate_keystroke("i");
8197
8198 handle_completion_request(
8199 &mut cx,
8200 indoc! {"
8201 one.second_completion
8202 two si
8203 three <si|>
8204 additional edit
8205 "},
8206 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8207 counter.clone(),
8208 )
8209 .await;
8210 cx.condition(|editor, _| editor.context_menu_visible())
8211 .await;
8212 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8213
8214 let apply_additional_edits = cx.update_editor(|editor, cx| {
8215 editor
8216 .confirm_completion(&ConfirmCompletion::default(), cx)
8217 .unwrap()
8218 });
8219 cx.assert_editor_state(indoc! {"
8220 one.second_completion
8221 two sixth_completionˇ
8222 three sixth_completionˇ
8223 additional edit
8224 "});
8225
8226 handle_resolve_completion_request(&mut cx, None).await;
8227 apply_additional_edits.await.unwrap();
8228
8229 cx.update(|cx| {
8230 cx.update_global::<SettingsStore, _>(|settings, cx| {
8231 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8232 settings.show_completions_on_input = Some(false);
8233 });
8234 })
8235 });
8236 cx.set_state("editorˇ");
8237 cx.simulate_keystroke(".");
8238 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8239 cx.simulate_keystroke("c");
8240 cx.simulate_keystroke("l");
8241 cx.simulate_keystroke("o");
8242 cx.assert_editor_state("editor.cloˇ");
8243 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8244 cx.update_editor(|editor, cx| {
8245 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8246 });
8247 handle_completion_request(
8248 &mut cx,
8249 "editor.<clo|>",
8250 vec!["close", "clobber"],
8251 counter.clone(),
8252 )
8253 .await;
8254 cx.condition(|editor, _| editor.context_menu_visible())
8255 .await;
8256 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8257
8258 let apply_additional_edits = cx.update_editor(|editor, cx| {
8259 editor
8260 .confirm_completion(&ConfirmCompletion::default(), cx)
8261 .unwrap()
8262 });
8263 cx.assert_editor_state("editor.closeˇ");
8264 handle_resolve_completion_request(&mut cx, None).await;
8265 apply_additional_edits.await.unwrap();
8266}
8267
8268#[gpui::test]
8269async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8270 init_test(cx, |_| {});
8271 let mut cx = EditorLspTestContext::new_rust(
8272 lsp::ServerCapabilities {
8273 completion_provider: Some(lsp::CompletionOptions {
8274 trigger_characters: Some(vec![".".to_string()]),
8275 ..Default::default()
8276 }),
8277 ..Default::default()
8278 },
8279 cx,
8280 )
8281 .await;
8282 cx.lsp
8283 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8284 Ok(Some(lsp::CompletionResponse::Array(vec![
8285 lsp::CompletionItem {
8286 label: "first".into(),
8287 ..Default::default()
8288 },
8289 lsp::CompletionItem {
8290 label: "last".into(),
8291 ..Default::default()
8292 },
8293 ])))
8294 });
8295 cx.set_state("variableˇ");
8296 cx.simulate_keystroke(".");
8297 cx.executor().run_until_parked();
8298
8299 cx.update_editor(|editor, _| {
8300 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8301 assert_eq!(
8302 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8303 &["first", "last"]
8304 );
8305 } else {
8306 panic!("expected completion menu to be open");
8307 }
8308 });
8309
8310 cx.update_editor(|editor, cx| {
8311 editor.move_page_down(&MovePageDown::default(), cx);
8312 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8313 assert!(
8314 menu.selected_item == 1,
8315 "expected PageDown to select the last item from the context menu"
8316 );
8317 } else {
8318 panic!("expected completion menu to stay open after PageDown");
8319 }
8320 });
8321
8322 cx.update_editor(|editor, cx| {
8323 editor.move_page_up(&MovePageUp::default(), cx);
8324 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8325 assert!(
8326 menu.selected_item == 0,
8327 "expected PageUp to select the first item from the context menu"
8328 );
8329 } else {
8330 panic!("expected completion menu to stay open after PageUp");
8331 }
8332 });
8333}
8334
8335#[gpui::test]
8336async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8337 init_test(cx, |_| {});
8338 let mut cx = EditorLspTestContext::new_rust(
8339 lsp::ServerCapabilities {
8340 completion_provider: Some(lsp::CompletionOptions {
8341 trigger_characters: Some(vec![".".to_string()]),
8342 ..Default::default()
8343 }),
8344 ..Default::default()
8345 },
8346 cx,
8347 )
8348 .await;
8349 cx.lsp
8350 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8351 Ok(Some(lsp::CompletionResponse::Array(vec![
8352 lsp::CompletionItem {
8353 label: "Range".into(),
8354 sort_text: Some("a".into()),
8355 ..Default::default()
8356 },
8357 lsp::CompletionItem {
8358 label: "r".into(),
8359 sort_text: Some("b".into()),
8360 ..Default::default()
8361 },
8362 lsp::CompletionItem {
8363 label: "ret".into(),
8364 sort_text: Some("c".into()),
8365 ..Default::default()
8366 },
8367 lsp::CompletionItem {
8368 label: "return".into(),
8369 sort_text: Some("d".into()),
8370 ..Default::default()
8371 },
8372 lsp::CompletionItem {
8373 label: "slice".into(),
8374 sort_text: Some("d".into()),
8375 ..Default::default()
8376 },
8377 ])))
8378 });
8379 cx.set_state("rˇ");
8380 cx.executor().run_until_parked();
8381 cx.update_editor(|editor, cx| {
8382 editor.show_completions(
8383 &ShowCompletions {
8384 trigger: Some("r".into()),
8385 },
8386 cx,
8387 );
8388 });
8389 cx.executor().run_until_parked();
8390
8391 cx.update_editor(|editor, _| {
8392 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8393 assert_eq!(
8394 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8395 &["r", "ret", "Range", "return"]
8396 );
8397 } else {
8398 panic!("expected completion menu to be open");
8399 }
8400 });
8401}
8402
8403#[gpui::test]
8404async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8405 init_test(cx, |_| {});
8406
8407 let mut cx = EditorLspTestContext::new_rust(
8408 lsp::ServerCapabilities {
8409 completion_provider: Some(lsp::CompletionOptions {
8410 trigger_characters: Some(vec![".".to_string()]),
8411 resolve_provider: Some(true),
8412 ..Default::default()
8413 }),
8414 ..Default::default()
8415 },
8416 cx,
8417 )
8418 .await;
8419
8420 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8421 cx.simulate_keystroke(".");
8422 let completion_item = lsp::CompletionItem {
8423 label: "Some".into(),
8424 kind: Some(lsp::CompletionItemKind::SNIPPET),
8425 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8426 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8427 kind: lsp::MarkupKind::Markdown,
8428 value: "```rust\nSome(2)\n```".to_string(),
8429 })),
8430 deprecated: Some(false),
8431 sort_text: Some("Some".to_string()),
8432 filter_text: Some("Some".to_string()),
8433 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8434 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8435 range: lsp::Range {
8436 start: lsp::Position {
8437 line: 0,
8438 character: 22,
8439 },
8440 end: lsp::Position {
8441 line: 0,
8442 character: 22,
8443 },
8444 },
8445 new_text: "Some(2)".to_string(),
8446 })),
8447 additional_text_edits: Some(vec![lsp::TextEdit {
8448 range: lsp::Range {
8449 start: lsp::Position {
8450 line: 0,
8451 character: 20,
8452 },
8453 end: lsp::Position {
8454 line: 0,
8455 character: 22,
8456 },
8457 },
8458 new_text: "".to_string(),
8459 }]),
8460 ..Default::default()
8461 };
8462
8463 let closure_completion_item = completion_item.clone();
8464 let counter = Arc::new(AtomicUsize::new(0));
8465 let counter_clone = counter.clone();
8466 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8467 let task_completion_item = closure_completion_item.clone();
8468 counter_clone.fetch_add(1, atomic::Ordering::Release);
8469 async move {
8470 Ok(Some(lsp::CompletionResponse::Array(vec![
8471 task_completion_item,
8472 ])))
8473 }
8474 });
8475
8476 cx.condition(|editor, _| editor.context_menu_visible())
8477 .await;
8478 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8479 assert!(request.next().await.is_some());
8480 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8481
8482 cx.simulate_keystroke("S");
8483 cx.simulate_keystroke("o");
8484 cx.simulate_keystroke("m");
8485 cx.condition(|editor, _| editor.context_menu_visible())
8486 .await;
8487 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8488 assert!(request.next().await.is_some());
8489 assert!(request.next().await.is_some());
8490 assert!(request.next().await.is_some());
8491 request.close();
8492 assert!(request.next().await.is_none());
8493 assert_eq!(
8494 counter.load(atomic::Ordering::Acquire),
8495 4,
8496 "With the completions menu open, only one LSP request should happen per input"
8497 );
8498}
8499
8500#[gpui::test]
8501async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8502 init_test(cx, |_| {});
8503 let mut cx = EditorTestContext::new(cx).await;
8504 let language = Arc::new(Language::new(
8505 LanguageConfig {
8506 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8507 ..Default::default()
8508 },
8509 Some(tree_sitter_rust::LANGUAGE.into()),
8510 ));
8511 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8512
8513 // If multiple selections intersect a line, the line is only toggled once.
8514 cx.set_state(indoc! {"
8515 fn a() {
8516 «//b();
8517 ˇ»// «c();
8518 //ˇ» d();
8519 }
8520 "});
8521
8522 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8523
8524 cx.assert_editor_state(indoc! {"
8525 fn a() {
8526 «b();
8527 c();
8528 ˇ» d();
8529 }
8530 "});
8531
8532 // The comment prefix is inserted at the same column for every line in a
8533 // selection.
8534 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8535
8536 cx.assert_editor_state(indoc! {"
8537 fn a() {
8538 // «b();
8539 // c();
8540 ˇ»// d();
8541 }
8542 "});
8543
8544 // If a selection ends at the beginning of a line, that line is not toggled.
8545 cx.set_selections_state(indoc! {"
8546 fn a() {
8547 // b();
8548 «// c();
8549 ˇ» // d();
8550 }
8551 "});
8552
8553 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8554
8555 cx.assert_editor_state(indoc! {"
8556 fn a() {
8557 // b();
8558 «c();
8559 ˇ» // d();
8560 }
8561 "});
8562
8563 // If a selection span a single line and is empty, the line is toggled.
8564 cx.set_state(indoc! {"
8565 fn a() {
8566 a();
8567 b();
8568 ˇ
8569 }
8570 "});
8571
8572 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8573
8574 cx.assert_editor_state(indoc! {"
8575 fn a() {
8576 a();
8577 b();
8578 //•ˇ
8579 }
8580 "});
8581
8582 // If a selection span multiple lines, empty lines are not toggled.
8583 cx.set_state(indoc! {"
8584 fn a() {
8585 «a();
8586
8587 c();ˇ»
8588 }
8589 "});
8590
8591 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8592
8593 cx.assert_editor_state(indoc! {"
8594 fn a() {
8595 // «a();
8596
8597 // c();ˇ»
8598 }
8599 "});
8600
8601 // If a selection includes multiple comment prefixes, all lines are uncommented.
8602 cx.set_state(indoc! {"
8603 fn a() {
8604 «// a();
8605 /// b();
8606 //! c();ˇ»
8607 }
8608 "});
8609
8610 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8611
8612 cx.assert_editor_state(indoc! {"
8613 fn a() {
8614 «a();
8615 b();
8616 c();ˇ»
8617 }
8618 "});
8619}
8620
8621#[gpui::test]
8622async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8623 init_test(cx, |_| {});
8624 let mut cx = EditorTestContext::new(cx).await;
8625 let language = Arc::new(Language::new(
8626 LanguageConfig {
8627 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8628 ..Default::default()
8629 },
8630 Some(tree_sitter_rust::LANGUAGE.into()),
8631 ));
8632 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8633
8634 let toggle_comments = &ToggleComments {
8635 advance_downwards: false,
8636 ignore_indent: true,
8637 };
8638
8639 // If multiple selections intersect a line, the line is only toggled once.
8640 cx.set_state(indoc! {"
8641 fn a() {
8642 // «b();
8643 // c();
8644 // ˇ» d();
8645 }
8646 "});
8647
8648 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8649
8650 cx.assert_editor_state(indoc! {"
8651 fn a() {
8652 «b();
8653 c();
8654 ˇ» d();
8655 }
8656 "});
8657
8658 // The comment prefix is inserted at the beginning of each line
8659 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8660
8661 cx.assert_editor_state(indoc! {"
8662 fn a() {
8663 // «b();
8664 // c();
8665 // ˇ» d();
8666 }
8667 "});
8668
8669 // If a selection ends at the beginning of a line, that line is not toggled.
8670 cx.set_selections_state(indoc! {"
8671 fn a() {
8672 // b();
8673 // «c();
8674 ˇ»// d();
8675 }
8676 "});
8677
8678 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8679
8680 cx.assert_editor_state(indoc! {"
8681 fn a() {
8682 // b();
8683 «c();
8684 ˇ»// d();
8685 }
8686 "});
8687
8688 // If a selection span a single line and is empty, the line is toggled.
8689 cx.set_state(indoc! {"
8690 fn a() {
8691 a();
8692 b();
8693 ˇ
8694 }
8695 "});
8696
8697 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8698
8699 cx.assert_editor_state(indoc! {"
8700 fn a() {
8701 a();
8702 b();
8703 //ˇ
8704 }
8705 "});
8706
8707 // If a selection span multiple lines, empty lines are not toggled.
8708 cx.set_state(indoc! {"
8709 fn a() {
8710 «a();
8711
8712 c();ˇ»
8713 }
8714 "});
8715
8716 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8717
8718 cx.assert_editor_state(indoc! {"
8719 fn a() {
8720 // «a();
8721
8722 // c();ˇ»
8723 }
8724 "});
8725
8726 // If a selection includes multiple comment prefixes, all lines are uncommented.
8727 cx.set_state(indoc! {"
8728 fn a() {
8729 // «a();
8730 /// b();
8731 //! c();ˇ»
8732 }
8733 "});
8734
8735 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8736
8737 cx.assert_editor_state(indoc! {"
8738 fn a() {
8739 «a();
8740 b();
8741 c();ˇ»
8742 }
8743 "});
8744}
8745
8746#[gpui::test]
8747async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8748 init_test(cx, |_| {});
8749
8750 let language = Arc::new(Language::new(
8751 LanguageConfig {
8752 line_comments: vec!["// ".into()],
8753 ..Default::default()
8754 },
8755 Some(tree_sitter_rust::LANGUAGE.into()),
8756 ));
8757
8758 let mut cx = EditorTestContext::new(cx).await;
8759
8760 cx.language_registry().add(language.clone());
8761 cx.update_buffer(|buffer, cx| {
8762 buffer.set_language(Some(language), cx);
8763 });
8764
8765 let toggle_comments = &ToggleComments {
8766 advance_downwards: true,
8767 ignore_indent: false,
8768 };
8769
8770 // Single cursor on one line -> advance
8771 // Cursor moves horizontally 3 characters as well on non-blank line
8772 cx.set_state(indoc!(
8773 "fn a() {
8774 ˇdog();
8775 cat();
8776 }"
8777 ));
8778 cx.update_editor(|editor, cx| {
8779 editor.toggle_comments(toggle_comments, cx);
8780 });
8781 cx.assert_editor_state(indoc!(
8782 "fn a() {
8783 // dog();
8784 catˇ();
8785 }"
8786 ));
8787
8788 // Single selection on one line -> don't advance
8789 cx.set_state(indoc!(
8790 "fn a() {
8791 «dog()ˇ»;
8792 cat();
8793 }"
8794 ));
8795 cx.update_editor(|editor, cx| {
8796 editor.toggle_comments(toggle_comments, cx);
8797 });
8798 cx.assert_editor_state(indoc!(
8799 "fn a() {
8800 // «dog()ˇ»;
8801 cat();
8802 }"
8803 ));
8804
8805 // Multiple cursors on one line -> advance
8806 cx.set_state(indoc!(
8807 "fn a() {
8808 ˇdˇog();
8809 cat();
8810 }"
8811 ));
8812 cx.update_editor(|editor, cx| {
8813 editor.toggle_comments(toggle_comments, cx);
8814 });
8815 cx.assert_editor_state(indoc!(
8816 "fn a() {
8817 // dog();
8818 catˇ(ˇ);
8819 }"
8820 ));
8821
8822 // Multiple cursors on one line, with selection -> don't advance
8823 cx.set_state(indoc!(
8824 "fn a() {
8825 ˇdˇog«()ˇ»;
8826 cat();
8827 }"
8828 ));
8829 cx.update_editor(|editor, cx| {
8830 editor.toggle_comments(toggle_comments, cx);
8831 });
8832 cx.assert_editor_state(indoc!(
8833 "fn a() {
8834 // ˇdˇog«()ˇ»;
8835 cat();
8836 }"
8837 ));
8838
8839 // Single cursor on one line -> advance
8840 // Cursor moves to column 0 on blank line
8841 cx.set_state(indoc!(
8842 "fn a() {
8843 ˇdog();
8844
8845 cat();
8846 }"
8847 ));
8848 cx.update_editor(|editor, cx| {
8849 editor.toggle_comments(toggle_comments, cx);
8850 });
8851 cx.assert_editor_state(indoc!(
8852 "fn a() {
8853 // dog();
8854 ˇ
8855 cat();
8856 }"
8857 ));
8858
8859 // Single cursor on one line -> advance
8860 // Cursor starts and ends at column 0
8861 cx.set_state(indoc!(
8862 "fn a() {
8863 ˇ dog();
8864 cat();
8865 }"
8866 ));
8867 cx.update_editor(|editor, cx| {
8868 editor.toggle_comments(toggle_comments, cx);
8869 });
8870 cx.assert_editor_state(indoc!(
8871 "fn a() {
8872 // dog();
8873 ˇ cat();
8874 }"
8875 ));
8876}
8877
8878#[gpui::test]
8879async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8880 init_test(cx, |_| {});
8881
8882 let mut cx = EditorTestContext::new(cx).await;
8883
8884 let html_language = Arc::new(
8885 Language::new(
8886 LanguageConfig {
8887 name: "HTML".into(),
8888 block_comment: Some(("<!-- ".into(), " -->".into())),
8889 ..Default::default()
8890 },
8891 Some(tree_sitter_html::language()),
8892 )
8893 .with_injection_query(
8894 r#"
8895 (script_element
8896 (raw_text) @content
8897 (#set! "language" "javascript"))
8898 "#,
8899 )
8900 .unwrap(),
8901 );
8902
8903 let javascript_language = Arc::new(Language::new(
8904 LanguageConfig {
8905 name: "JavaScript".into(),
8906 line_comments: vec!["// ".into()],
8907 ..Default::default()
8908 },
8909 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8910 ));
8911
8912 cx.language_registry().add(html_language.clone());
8913 cx.language_registry().add(javascript_language.clone());
8914 cx.update_buffer(|buffer, cx| {
8915 buffer.set_language(Some(html_language), cx);
8916 });
8917
8918 // Toggle comments for empty selections
8919 cx.set_state(
8920 &r#"
8921 <p>A</p>ˇ
8922 <p>B</p>ˇ
8923 <p>C</p>ˇ
8924 "#
8925 .unindent(),
8926 );
8927 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8928 cx.assert_editor_state(
8929 &r#"
8930 <!-- <p>A</p>ˇ -->
8931 <!-- <p>B</p>ˇ -->
8932 <!-- <p>C</p>ˇ -->
8933 "#
8934 .unindent(),
8935 );
8936 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8937 cx.assert_editor_state(
8938 &r#"
8939 <p>A</p>ˇ
8940 <p>B</p>ˇ
8941 <p>C</p>ˇ
8942 "#
8943 .unindent(),
8944 );
8945
8946 // Toggle comments for mixture of empty and non-empty selections, where
8947 // multiple selections occupy a given line.
8948 cx.set_state(
8949 &r#"
8950 <p>A«</p>
8951 <p>ˇ»B</p>ˇ
8952 <p>C«</p>
8953 <p>ˇ»D</p>ˇ
8954 "#
8955 .unindent(),
8956 );
8957
8958 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8959 cx.assert_editor_state(
8960 &r#"
8961 <!-- <p>A«</p>
8962 <p>ˇ»B</p>ˇ -->
8963 <!-- <p>C«</p>
8964 <p>ˇ»D</p>ˇ -->
8965 "#
8966 .unindent(),
8967 );
8968 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8969 cx.assert_editor_state(
8970 &r#"
8971 <p>A«</p>
8972 <p>ˇ»B</p>ˇ
8973 <p>C«</p>
8974 <p>ˇ»D</p>ˇ
8975 "#
8976 .unindent(),
8977 );
8978
8979 // Toggle comments when different languages are active for different
8980 // selections.
8981 cx.set_state(
8982 &r#"
8983 ˇ<script>
8984 ˇvar x = new Y();
8985 ˇ</script>
8986 "#
8987 .unindent(),
8988 );
8989 cx.executor().run_until_parked();
8990 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8991 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8992 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8993 cx.assert_editor_state(
8994 &r#"
8995 <!-- ˇ<script> -->
8996 // ˇvar x = new Y();
8997 // ˇ</script>
8998 "#
8999 .unindent(),
9000 );
9001}
9002
9003#[gpui::test]
9004fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9005 init_test(cx, |_| {});
9006
9007 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9008 let multibuffer = cx.new_model(|cx| {
9009 let mut multibuffer = MultiBuffer::new(ReadWrite);
9010 multibuffer.push_excerpts(
9011 buffer.clone(),
9012 [
9013 ExcerptRange {
9014 context: Point::new(0, 0)..Point::new(0, 4),
9015 primary: None,
9016 },
9017 ExcerptRange {
9018 context: Point::new(1, 0)..Point::new(1, 4),
9019 primary: None,
9020 },
9021 ],
9022 cx,
9023 );
9024 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9025 multibuffer
9026 });
9027
9028 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9029 view.update(cx, |view, cx| {
9030 assert_eq!(view.text(cx), "aaaa\nbbbb");
9031 view.change_selections(None, cx, |s| {
9032 s.select_ranges([
9033 Point::new(0, 0)..Point::new(0, 0),
9034 Point::new(1, 0)..Point::new(1, 0),
9035 ])
9036 });
9037
9038 view.handle_input("X", cx);
9039 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9040 assert_eq!(
9041 view.selections.ranges(cx),
9042 [
9043 Point::new(0, 1)..Point::new(0, 1),
9044 Point::new(1, 1)..Point::new(1, 1),
9045 ]
9046 );
9047
9048 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9049 view.change_selections(None, cx, |s| {
9050 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9051 });
9052 view.backspace(&Default::default(), cx);
9053 assert_eq!(view.text(cx), "Xa\nbbb");
9054 assert_eq!(
9055 view.selections.ranges(cx),
9056 [Point::new(1, 0)..Point::new(1, 0)]
9057 );
9058
9059 view.change_selections(None, cx, |s| {
9060 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9061 });
9062 view.backspace(&Default::default(), cx);
9063 assert_eq!(view.text(cx), "X\nbb");
9064 assert_eq!(
9065 view.selections.ranges(cx),
9066 [Point::new(0, 1)..Point::new(0, 1)]
9067 );
9068 });
9069}
9070
9071#[gpui::test]
9072fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9073 init_test(cx, |_| {});
9074
9075 let markers = vec![('[', ']').into(), ('(', ')').into()];
9076 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9077 indoc! {"
9078 [aaaa
9079 (bbbb]
9080 cccc)",
9081 },
9082 markers.clone(),
9083 );
9084 let excerpt_ranges = markers.into_iter().map(|marker| {
9085 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9086 ExcerptRange {
9087 context,
9088 primary: None,
9089 }
9090 });
9091 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9092 let multibuffer = cx.new_model(|cx| {
9093 let mut multibuffer = MultiBuffer::new(ReadWrite);
9094 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9095 multibuffer
9096 });
9097
9098 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9099 view.update(cx, |view, cx| {
9100 let (expected_text, selection_ranges) = marked_text_ranges(
9101 indoc! {"
9102 aaaa
9103 bˇbbb
9104 bˇbbˇb
9105 cccc"
9106 },
9107 true,
9108 );
9109 assert_eq!(view.text(cx), expected_text);
9110 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9111
9112 view.handle_input("X", cx);
9113
9114 let (expected_text, expected_selections) = marked_text_ranges(
9115 indoc! {"
9116 aaaa
9117 bXˇbbXb
9118 bXˇbbXˇb
9119 cccc"
9120 },
9121 false,
9122 );
9123 assert_eq!(view.text(cx), expected_text);
9124 assert_eq!(view.selections.ranges(cx), expected_selections);
9125
9126 view.newline(&Newline, cx);
9127 let (expected_text, expected_selections) = marked_text_ranges(
9128 indoc! {"
9129 aaaa
9130 bX
9131 ˇbbX
9132 b
9133 bX
9134 ˇbbX
9135 ˇb
9136 cccc"
9137 },
9138 false,
9139 );
9140 assert_eq!(view.text(cx), expected_text);
9141 assert_eq!(view.selections.ranges(cx), expected_selections);
9142 });
9143}
9144
9145#[gpui::test]
9146fn test_refresh_selections(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.change_selections(None, cx, |s| {
9178 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9179 });
9180 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9181 assert_eq!(
9182 editor.selections.ranges(cx),
9183 [
9184 Point::new(1, 3)..Point::new(1, 3),
9185 Point::new(2, 1)..Point::new(2, 1),
9186 ]
9187 );
9188 editor
9189 });
9190
9191 // Refreshing selections is a no-op when excerpts haven't changed.
9192 _ = editor.update(cx, |editor, cx| {
9193 editor.change_selections(None, cx, |s| s.refresh());
9194 assert_eq!(
9195 editor.selections.ranges(cx),
9196 [
9197 Point::new(1, 3)..Point::new(1, 3),
9198 Point::new(2, 1)..Point::new(2, 1),
9199 ]
9200 );
9201 });
9202
9203 multibuffer.update(cx, |multibuffer, cx| {
9204 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9205 });
9206 _ = editor.update(cx, |editor, cx| {
9207 // Removing an excerpt causes the first selection to become degenerate.
9208 assert_eq!(
9209 editor.selections.ranges(cx),
9210 [
9211 Point::new(0, 0)..Point::new(0, 0),
9212 Point::new(0, 1)..Point::new(0, 1)
9213 ]
9214 );
9215
9216 // Refreshing selections will relocate the first selection to the original buffer
9217 // location.
9218 editor.change_selections(None, cx, |s| s.refresh());
9219 assert_eq!(
9220 editor.selections.ranges(cx),
9221 [
9222 Point::new(0, 1)..Point::new(0, 1),
9223 Point::new(0, 3)..Point::new(0, 3)
9224 ]
9225 );
9226 assert!(editor.selections.pending_anchor().is_some());
9227 });
9228}
9229
9230#[gpui::test]
9231fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9232 init_test(cx, |_| {});
9233
9234 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9235 let mut excerpt1_id = None;
9236 let multibuffer = cx.new_model(|cx| {
9237 let mut multibuffer = MultiBuffer::new(ReadWrite);
9238 excerpt1_id = multibuffer
9239 .push_excerpts(
9240 buffer.clone(),
9241 [
9242 ExcerptRange {
9243 context: Point::new(0, 0)..Point::new(1, 4),
9244 primary: None,
9245 },
9246 ExcerptRange {
9247 context: Point::new(1, 0)..Point::new(2, 4),
9248 primary: None,
9249 },
9250 ],
9251 cx,
9252 )
9253 .into_iter()
9254 .next();
9255 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9256 multibuffer
9257 });
9258
9259 let editor = cx.add_window(|cx| {
9260 let mut editor = build_editor(multibuffer.clone(), cx);
9261 let snapshot = editor.snapshot(cx);
9262 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9263 assert_eq!(
9264 editor.selections.ranges(cx),
9265 [Point::new(1, 3)..Point::new(1, 3)]
9266 );
9267 editor
9268 });
9269
9270 multibuffer.update(cx, |multibuffer, cx| {
9271 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9272 });
9273 _ = editor.update(cx, |editor, cx| {
9274 assert_eq!(
9275 editor.selections.ranges(cx),
9276 [Point::new(0, 0)..Point::new(0, 0)]
9277 );
9278
9279 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9280 editor.change_selections(None, cx, |s| s.refresh());
9281 assert_eq!(
9282 editor.selections.ranges(cx),
9283 [Point::new(0, 3)..Point::new(0, 3)]
9284 );
9285 assert!(editor.selections.pending_anchor().is_some());
9286 });
9287}
9288
9289#[gpui::test]
9290async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9291 init_test(cx, |_| {});
9292
9293 let language = Arc::new(
9294 Language::new(
9295 LanguageConfig {
9296 brackets: BracketPairConfig {
9297 pairs: vec![
9298 BracketPair {
9299 start: "{".to_string(),
9300 end: "}".to_string(),
9301 close: true,
9302 surround: true,
9303 newline: true,
9304 },
9305 BracketPair {
9306 start: "/* ".to_string(),
9307 end: " */".to_string(),
9308 close: true,
9309 surround: true,
9310 newline: true,
9311 },
9312 ],
9313 ..Default::default()
9314 },
9315 ..Default::default()
9316 },
9317 Some(tree_sitter_rust::LANGUAGE.into()),
9318 )
9319 .with_indents_query("")
9320 .unwrap(),
9321 );
9322
9323 let text = concat!(
9324 "{ }\n", //
9325 " x\n", //
9326 " /* */\n", //
9327 "x\n", //
9328 "{{} }\n", //
9329 );
9330
9331 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9332 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9333 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9334 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9335 .await;
9336
9337 view.update(cx, |view, cx| {
9338 view.change_selections(None, cx, |s| {
9339 s.select_display_ranges([
9340 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9341 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9342 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9343 ])
9344 });
9345 view.newline(&Newline, cx);
9346
9347 assert_eq!(
9348 view.buffer().read(cx).read(cx).text(),
9349 concat!(
9350 "{ \n", // Suppress rustfmt
9351 "\n", //
9352 "}\n", //
9353 " x\n", //
9354 " /* \n", //
9355 " \n", //
9356 " */\n", //
9357 "x\n", //
9358 "{{} \n", //
9359 "}\n", //
9360 )
9361 );
9362 });
9363}
9364
9365#[gpui::test]
9366fn test_highlighted_ranges(cx: &mut TestAppContext) {
9367 init_test(cx, |_| {});
9368
9369 let editor = cx.add_window(|cx| {
9370 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9371 build_editor(buffer.clone(), cx)
9372 });
9373
9374 _ = editor.update(cx, |editor, cx| {
9375 struct Type1;
9376 struct Type2;
9377
9378 let buffer = editor.buffer.read(cx).snapshot(cx);
9379
9380 let anchor_range =
9381 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9382
9383 editor.highlight_background::<Type1>(
9384 &[
9385 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9386 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9387 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9388 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9389 ],
9390 |_| Hsla::red(),
9391 cx,
9392 );
9393 editor.highlight_background::<Type2>(
9394 &[
9395 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9396 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9397 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9398 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9399 ],
9400 |_| Hsla::green(),
9401 cx,
9402 );
9403
9404 let snapshot = editor.snapshot(cx);
9405 let mut highlighted_ranges = editor.background_highlights_in_range(
9406 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9407 &snapshot,
9408 cx.theme().colors(),
9409 );
9410 // Enforce a consistent ordering based on color without relying on the ordering of the
9411 // highlight's `TypeId` which is non-executor.
9412 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9413 assert_eq!(
9414 highlighted_ranges,
9415 &[
9416 (
9417 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9418 Hsla::red(),
9419 ),
9420 (
9421 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9422 Hsla::red(),
9423 ),
9424 (
9425 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9426 Hsla::green(),
9427 ),
9428 (
9429 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9430 Hsla::green(),
9431 ),
9432 ]
9433 );
9434 assert_eq!(
9435 editor.background_highlights_in_range(
9436 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9437 &snapshot,
9438 cx.theme().colors(),
9439 ),
9440 &[(
9441 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9442 Hsla::red(),
9443 )]
9444 );
9445 });
9446}
9447
9448#[gpui::test]
9449async fn test_following(cx: &mut gpui::TestAppContext) {
9450 init_test(cx, |_| {});
9451
9452 let fs = FakeFs::new(cx.executor());
9453 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9454
9455 let buffer = project.update(cx, |project, cx| {
9456 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9457 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9458 });
9459 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9460 let follower = cx.update(|cx| {
9461 cx.open_window(
9462 WindowOptions {
9463 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9464 gpui::Point::new(px(0.), px(0.)),
9465 gpui::Point::new(px(10.), px(80.)),
9466 ))),
9467 ..Default::default()
9468 },
9469 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9470 )
9471 .unwrap()
9472 });
9473
9474 let is_still_following = Rc::new(RefCell::new(true));
9475 let follower_edit_event_count = Rc::new(RefCell::new(0));
9476 let pending_update = Rc::new(RefCell::new(None));
9477 _ = follower.update(cx, {
9478 let update = pending_update.clone();
9479 let is_still_following = is_still_following.clone();
9480 let follower_edit_event_count = follower_edit_event_count.clone();
9481 |_, cx| {
9482 cx.subscribe(
9483 &leader.root_view(cx).unwrap(),
9484 move |_, leader, event, cx| {
9485 leader
9486 .read(cx)
9487 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9488 },
9489 )
9490 .detach();
9491
9492 cx.subscribe(
9493 &follower.root_view(cx).unwrap(),
9494 move |_, _, event: &EditorEvent, _cx| {
9495 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9496 *is_still_following.borrow_mut() = false;
9497 }
9498
9499 if let EditorEvent::BufferEdited = event {
9500 *follower_edit_event_count.borrow_mut() += 1;
9501 }
9502 },
9503 )
9504 .detach();
9505 }
9506 });
9507
9508 // Update the selections only
9509 _ = leader.update(cx, |leader, cx| {
9510 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9511 });
9512 follower
9513 .update(cx, |follower, cx| {
9514 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9515 })
9516 .unwrap()
9517 .await
9518 .unwrap();
9519 _ = follower.update(cx, |follower, cx| {
9520 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9521 });
9522 assert!(*is_still_following.borrow());
9523 assert_eq!(*follower_edit_event_count.borrow(), 0);
9524
9525 // Update the scroll position only
9526 _ = leader.update(cx, |leader, cx| {
9527 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9528 });
9529 follower
9530 .update(cx, |follower, cx| {
9531 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9532 })
9533 .unwrap()
9534 .await
9535 .unwrap();
9536 assert_eq!(
9537 follower
9538 .update(cx, |follower, cx| follower.scroll_position(cx))
9539 .unwrap(),
9540 gpui::Point::new(1.5, 3.5)
9541 );
9542 assert!(*is_still_following.borrow());
9543 assert_eq!(*follower_edit_event_count.borrow(), 0);
9544
9545 // Update the selections and scroll position. The follower's scroll position is updated
9546 // via autoscroll, not via the leader's exact scroll position.
9547 _ = leader.update(cx, |leader, cx| {
9548 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9549 leader.request_autoscroll(Autoscroll::newest(), cx);
9550 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9551 });
9552 follower
9553 .update(cx, |follower, cx| {
9554 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9555 })
9556 .unwrap()
9557 .await
9558 .unwrap();
9559 _ = follower.update(cx, |follower, cx| {
9560 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9561 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9562 });
9563 assert!(*is_still_following.borrow());
9564
9565 // Creating a pending selection that precedes another selection
9566 _ = leader.update(cx, |leader, cx| {
9567 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9568 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9569 });
9570 follower
9571 .update(cx, |follower, cx| {
9572 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9573 })
9574 .unwrap()
9575 .await
9576 .unwrap();
9577 _ = follower.update(cx, |follower, cx| {
9578 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9579 });
9580 assert!(*is_still_following.borrow());
9581
9582 // Extend the pending selection so that it surrounds another selection
9583 _ = leader.update(cx, |leader, cx| {
9584 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9585 });
9586 follower
9587 .update(cx, |follower, cx| {
9588 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9589 })
9590 .unwrap()
9591 .await
9592 .unwrap();
9593 _ = follower.update(cx, |follower, cx| {
9594 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9595 });
9596
9597 // Scrolling locally breaks the follow
9598 _ = follower.update(cx, |follower, cx| {
9599 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9600 follower.set_scroll_anchor(
9601 ScrollAnchor {
9602 anchor: top_anchor,
9603 offset: gpui::Point::new(0.0, 0.5),
9604 },
9605 cx,
9606 );
9607 });
9608 assert!(!(*is_still_following.borrow()));
9609}
9610
9611#[gpui::test]
9612async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9613 init_test(cx, |_| {});
9614
9615 let fs = FakeFs::new(cx.executor());
9616 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9617 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9618 let pane = workspace
9619 .update(cx, |workspace, _| workspace.active_pane().clone())
9620 .unwrap();
9621
9622 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9623
9624 let leader = pane.update(cx, |_, cx| {
9625 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9626 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9627 });
9628
9629 // Start following the editor when it has no excerpts.
9630 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9631 let follower_1 = cx
9632 .update_window(*workspace.deref(), |_, cx| {
9633 Editor::from_state_proto(
9634 workspace.root_view(cx).unwrap(),
9635 ViewId {
9636 creator: Default::default(),
9637 id: 0,
9638 },
9639 &mut state_message,
9640 cx,
9641 )
9642 })
9643 .unwrap()
9644 .unwrap()
9645 .await
9646 .unwrap();
9647
9648 let update_message = Rc::new(RefCell::new(None));
9649 follower_1.update(cx, {
9650 let update = update_message.clone();
9651 |_, cx| {
9652 cx.subscribe(&leader, move |_, leader, event, cx| {
9653 leader
9654 .read(cx)
9655 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9656 })
9657 .detach();
9658 }
9659 });
9660
9661 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9662 (
9663 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9664 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9665 )
9666 });
9667
9668 // Insert some excerpts.
9669 leader.update(cx, |leader, cx| {
9670 leader.buffer.update(cx, |multibuffer, cx| {
9671 let excerpt_ids = multibuffer.push_excerpts(
9672 buffer_1.clone(),
9673 [
9674 ExcerptRange {
9675 context: 1..6,
9676 primary: None,
9677 },
9678 ExcerptRange {
9679 context: 12..15,
9680 primary: None,
9681 },
9682 ExcerptRange {
9683 context: 0..3,
9684 primary: None,
9685 },
9686 ],
9687 cx,
9688 );
9689 multibuffer.insert_excerpts_after(
9690 excerpt_ids[0],
9691 buffer_2.clone(),
9692 [
9693 ExcerptRange {
9694 context: 8..12,
9695 primary: None,
9696 },
9697 ExcerptRange {
9698 context: 0..6,
9699 primary: None,
9700 },
9701 ],
9702 cx,
9703 );
9704 });
9705 });
9706
9707 // Apply the update of adding the excerpts.
9708 follower_1
9709 .update(cx, |follower, cx| {
9710 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9711 })
9712 .await
9713 .unwrap();
9714 assert_eq!(
9715 follower_1.update(cx, |editor, cx| editor.text(cx)),
9716 leader.update(cx, |editor, cx| editor.text(cx))
9717 );
9718 update_message.borrow_mut().take();
9719
9720 // Start following separately after it already has excerpts.
9721 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9722 let follower_2 = cx
9723 .update_window(*workspace.deref(), |_, cx| {
9724 Editor::from_state_proto(
9725 workspace.root_view(cx).unwrap().clone(),
9726 ViewId {
9727 creator: Default::default(),
9728 id: 0,
9729 },
9730 &mut state_message,
9731 cx,
9732 )
9733 })
9734 .unwrap()
9735 .unwrap()
9736 .await
9737 .unwrap();
9738 assert_eq!(
9739 follower_2.update(cx, |editor, cx| editor.text(cx)),
9740 leader.update(cx, |editor, cx| editor.text(cx))
9741 );
9742
9743 // Remove some excerpts.
9744 leader.update(cx, |leader, cx| {
9745 leader.buffer.update(cx, |multibuffer, cx| {
9746 let excerpt_ids = multibuffer.excerpt_ids();
9747 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9748 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9749 });
9750 });
9751
9752 // Apply the update of removing the excerpts.
9753 follower_1
9754 .update(cx, |follower, cx| {
9755 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9756 })
9757 .await
9758 .unwrap();
9759 follower_2
9760 .update(cx, |follower, cx| {
9761 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9762 })
9763 .await
9764 .unwrap();
9765 update_message.borrow_mut().take();
9766 assert_eq!(
9767 follower_1.update(cx, |editor, cx| editor.text(cx)),
9768 leader.update(cx, |editor, cx| editor.text(cx))
9769 );
9770}
9771
9772#[gpui::test]
9773async fn go_to_prev_overlapping_diagnostic(
9774 executor: BackgroundExecutor,
9775 cx: &mut gpui::TestAppContext,
9776) {
9777 init_test(cx, |_| {});
9778
9779 let mut cx = EditorTestContext::new(cx).await;
9780 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9781
9782 cx.set_state(indoc! {"
9783 ˇfn func(abc def: i32) -> u32 {
9784 }
9785 "});
9786
9787 cx.update(|cx| {
9788 project.update(cx, |project, cx| {
9789 project
9790 .update_diagnostics(
9791 LanguageServerId(0),
9792 lsp::PublishDiagnosticsParams {
9793 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9794 version: None,
9795 diagnostics: vec![
9796 lsp::Diagnostic {
9797 range: lsp::Range::new(
9798 lsp::Position::new(0, 11),
9799 lsp::Position::new(0, 12),
9800 ),
9801 severity: Some(lsp::DiagnosticSeverity::ERROR),
9802 ..Default::default()
9803 },
9804 lsp::Diagnostic {
9805 range: lsp::Range::new(
9806 lsp::Position::new(0, 12),
9807 lsp::Position::new(0, 15),
9808 ),
9809 severity: Some(lsp::DiagnosticSeverity::ERROR),
9810 ..Default::default()
9811 },
9812 lsp::Diagnostic {
9813 range: lsp::Range::new(
9814 lsp::Position::new(0, 25),
9815 lsp::Position::new(0, 28),
9816 ),
9817 severity: Some(lsp::DiagnosticSeverity::ERROR),
9818 ..Default::default()
9819 },
9820 ],
9821 },
9822 &[],
9823 cx,
9824 )
9825 .unwrap()
9826 });
9827 });
9828
9829 executor.run_until_parked();
9830
9831 cx.update_editor(|editor, cx| {
9832 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9833 });
9834
9835 cx.assert_editor_state(indoc! {"
9836 fn func(abc def: i32) -> ˇu32 {
9837 }
9838 "});
9839
9840 cx.update_editor(|editor, cx| {
9841 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9842 });
9843
9844 cx.assert_editor_state(indoc! {"
9845 fn func(abc ˇdef: i32) -> u32 {
9846 }
9847 "});
9848
9849 cx.update_editor(|editor, cx| {
9850 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9851 });
9852
9853 cx.assert_editor_state(indoc! {"
9854 fn func(abcˇ def: i32) -> u32 {
9855 }
9856 "});
9857
9858 cx.update_editor(|editor, cx| {
9859 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9860 });
9861
9862 cx.assert_editor_state(indoc! {"
9863 fn func(abc def: i32) -> ˇu32 {
9864 }
9865 "});
9866}
9867
9868#[gpui::test]
9869async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9870 init_test(cx, |_| {});
9871
9872 let mut cx = EditorTestContext::new(cx).await;
9873
9874 cx.set_state(indoc! {"
9875 fn func(abˇc def: i32) -> u32 {
9876 }
9877 "});
9878 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9879
9880 cx.update(|cx| {
9881 project.update(cx, |project, cx| {
9882 project.update_diagnostics(
9883 LanguageServerId(0),
9884 lsp::PublishDiagnosticsParams {
9885 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9886 version: None,
9887 diagnostics: vec![lsp::Diagnostic {
9888 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9889 severity: Some(lsp::DiagnosticSeverity::ERROR),
9890 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9891 ..Default::default()
9892 }],
9893 },
9894 &[],
9895 cx,
9896 )
9897 })
9898 }).unwrap();
9899 cx.run_until_parked();
9900 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9901 cx.run_until_parked();
9902 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9903}
9904
9905#[gpui::test]
9906async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9907 init_test(cx, |_| {});
9908
9909 let mut cx = EditorTestContext::new(cx).await;
9910
9911 let diff_base = r#"
9912 use some::mod;
9913
9914 const A: u32 = 42;
9915
9916 fn main() {
9917 println!("hello");
9918
9919 println!("world");
9920 }
9921 "#
9922 .unindent();
9923
9924 // Edits are modified, removed, modified, added
9925 cx.set_state(
9926 &r#"
9927 use some::modified;
9928
9929 ˇ
9930 fn main() {
9931 println!("hello there");
9932
9933 println!("around the");
9934 println!("world");
9935 }
9936 "#
9937 .unindent(),
9938 );
9939
9940 cx.set_diff_base(Some(&diff_base));
9941 executor.run_until_parked();
9942
9943 cx.update_editor(|editor, cx| {
9944 //Wrap around the bottom of the buffer
9945 for _ in 0..3 {
9946 editor.go_to_next_hunk(&GoToHunk, cx);
9947 }
9948 });
9949
9950 cx.assert_editor_state(
9951 &r#"
9952 ˇuse some::modified;
9953
9954
9955 fn main() {
9956 println!("hello there");
9957
9958 println!("around the");
9959 println!("world");
9960 }
9961 "#
9962 .unindent(),
9963 );
9964
9965 cx.update_editor(|editor, cx| {
9966 //Wrap around the top of the buffer
9967 for _ in 0..2 {
9968 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9969 }
9970 });
9971
9972 cx.assert_editor_state(
9973 &r#"
9974 use some::modified;
9975
9976
9977 fn main() {
9978 ˇ println!("hello there");
9979
9980 println!("around the");
9981 println!("world");
9982 }
9983 "#
9984 .unindent(),
9985 );
9986
9987 cx.update_editor(|editor, cx| {
9988 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9989 });
9990
9991 cx.assert_editor_state(
9992 &r#"
9993 use some::modified;
9994
9995 ˇ
9996 fn main() {
9997 println!("hello there");
9998
9999 println!("around the");
10000 println!("world");
10001 }
10002 "#
10003 .unindent(),
10004 );
10005
10006 cx.update_editor(|editor, cx| {
10007 for _ in 0..3 {
10008 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10009 }
10010 });
10011
10012 cx.assert_editor_state(
10013 &r#"
10014 use some::modified;
10015
10016
10017 fn main() {
10018 ˇ println!("hello there");
10019
10020 println!("around the");
10021 println!("world");
10022 }
10023 "#
10024 .unindent(),
10025 );
10026
10027 cx.update_editor(|editor, cx| {
10028 editor.fold(&Fold, cx);
10029
10030 //Make sure that the fold only gets one hunk
10031 for _ in 0..4 {
10032 editor.go_to_next_hunk(&GoToHunk, cx);
10033 }
10034 });
10035
10036 cx.assert_editor_state(
10037 &r#"
10038 ˇuse some::modified;
10039
10040
10041 fn main() {
10042 println!("hello there");
10043
10044 println!("around the");
10045 println!("world");
10046 }
10047 "#
10048 .unindent(),
10049 );
10050}
10051
10052#[test]
10053fn test_split_words() {
10054 fn split(text: &str) -> Vec<&str> {
10055 split_words(text).collect()
10056 }
10057
10058 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10059 assert_eq!(split("hello_world"), &["hello_", "world"]);
10060 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10061 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10062 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10063 assert_eq!(split("helloworld"), &["helloworld"]);
10064
10065 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10066}
10067
10068#[gpui::test]
10069async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10070 init_test(cx, |_| {});
10071
10072 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10073 let mut assert = |before, after| {
10074 let _state_context = cx.set_state(before);
10075 cx.update_editor(|editor, cx| {
10076 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10077 });
10078 cx.assert_editor_state(after);
10079 };
10080
10081 // Outside bracket jumps to outside of matching bracket
10082 assert("console.logˇ(var);", "console.log(var)ˇ;");
10083 assert("console.log(var)ˇ;", "console.logˇ(var);");
10084
10085 // Inside bracket jumps to inside of matching bracket
10086 assert("console.log(ˇvar);", "console.log(varˇ);");
10087 assert("console.log(varˇ);", "console.log(ˇvar);");
10088
10089 // When outside a bracket and inside, favor jumping to the inside bracket
10090 assert(
10091 "console.log('foo', [1, 2, 3]ˇ);",
10092 "console.log(ˇ'foo', [1, 2, 3]);",
10093 );
10094 assert(
10095 "console.log(ˇ'foo', [1, 2, 3]);",
10096 "console.log('foo', [1, 2, 3]ˇ);",
10097 );
10098
10099 // Bias forward if two options are equally likely
10100 assert(
10101 "let result = curried_fun()ˇ();",
10102 "let result = curried_fun()()ˇ;",
10103 );
10104
10105 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10106 assert(
10107 indoc! {"
10108 function test() {
10109 console.log('test')ˇ
10110 }"},
10111 indoc! {"
10112 function test() {
10113 console.logˇ('test')
10114 }"},
10115 );
10116}
10117
10118#[gpui::test]
10119async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10120 init_test(cx, |_| {});
10121
10122 let fs = FakeFs::new(cx.executor());
10123 fs.insert_tree(
10124 "/a",
10125 json!({
10126 "main.rs": "fn main() { let a = 5; }",
10127 "other.rs": "// Test file",
10128 }),
10129 )
10130 .await;
10131 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10132
10133 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10134 language_registry.add(Arc::new(Language::new(
10135 LanguageConfig {
10136 name: "Rust".into(),
10137 matcher: LanguageMatcher {
10138 path_suffixes: vec!["rs".to_string()],
10139 ..Default::default()
10140 },
10141 brackets: BracketPairConfig {
10142 pairs: vec![BracketPair {
10143 start: "{".to_string(),
10144 end: "}".to_string(),
10145 close: true,
10146 surround: true,
10147 newline: true,
10148 }],
10149 disabled_scopes_by_bracket_ix: Vec::new(),
10150 },
10151 ..Default::default()
10152 },
10153 Some(tree_sitter_rust::LANGUAGE.into()),
10154 )));
10155 let mut fake_servers = language_registry.register_fake_lsp(
10156 "Rust",
10157 FakeLspAdapter {
10158 capabilities: lsp::ServerCapabilities {
10159 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10160 first_trigger_character: "{".to_string(),
10161 more_trigger_character: None,
10162 }),
10163 ..Default::default()
10164 },
10165 ..Default::default()
10166 },
10167 );
10168
10169 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10170
10171 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10172
10173 let worktree_id = workspace
10174 .update(cx, |workspace, cx| {
10175 workspace.project().update(cx, |project, cx| {
10176 project.worktrees(cx).next().unwrap().read(cx).id()
10177 })
10178 })
10179 .unwrap();
10180
10181 let buffer = project
10182 .update(cx, |project, cx| {
10183 project.open_local_buffer("/a/main.rs", cx)
10184 })
10185 .await
10186 .unwrap();
10187 cx.executor().run_until_parked();
10188 cx.executor().start_waiting();
10189 let fake_server = fake_servers.next().await.unwrap();
10190 let editor_handle = workspace
10191 .update(cx, |workspace, cx| {
10192 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10193 })
10194 .unwrap()
10195 .await
10196 .unwrap()
10197 .downcast::<Editor>()
10198 .unwrap();
10199
10200 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10201 assert_eq!(
10202 params.text_document_position.text_document.uri,
10203 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10204 );
10205 assert_eq!(
10206 params.text_document_position.position,
10207 lsp::Position::new(0, 21),
10208 );
10209
10210 Ok(Some(vec![lsp::TextEdit {
10211 new_text: "]".to_string(),
10212 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10213 }]))
10214 });
10215
10216 editor_handle.update(cx, |editor, cx| {
10217 editor.focus(cx);
10218 editor.change_selections(None, cx, |s| {
10219 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10220 });
10221 editor.handle_input("{", cx);
10222 });
10223
10224 cx.executor().run_until_parked();
10225
10226 buffer.update(cx, |buffer, _| {
10227 assert_eq!(
10228 buffer.text(),
10229 "fn main() { let a = {5}; }",
10230 "No extra braces from on type formatting should appear in the buffer"
10231 )
10232 });
10233}
10234
10235#[gpui::test]
10236async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10237 init_test(cx, |_| {});
10238
10239 let fs = FakeFs::new(cx.executor());
10240 fs.insert_tree(
10241 "/a",
10242 json!({
10243 "main.rs": "fn main() { let a = 5; }",
10244 "other.rs": "// Test file",
10245 }),
10246 )
10247 .await;
10248
10249 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10250
10251 let server_restarts = Arc::new(AtomicUsize::new(0));
10252 let closure_restarts = Arc::clone(&server_restarts);
10253 let language_server_name = "test language server";
10254 let language_name: LanguageName = "Rust".into();
10255
10256 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10257 language_registry.add(Arc::new(Language::new(
10258 LanguageConfig {
10259 name: language_name.clone(),
10260 matcher: LanguageMatcher {
10261 path_suffixes: vec!["rs".to_string()],
10262 ..Default::default()
10263 },
10264 ..Default::default()
10265 },
10266 Some(tree_sitter_rust::LANGUAGE.into()),
10267 )));
10268 let mut fake_servers = language_registry.register_fake_lsp(
10269 "Rust",
10270 FakeLspAdapter {
10271 name: language_server_name,
10272 initialization_options: Some(json!({
10273 "testOptionValue": true
10274 })),
10275 initializer: Some(Box::new(move |fake_server| {
10276 let task_restarts = Arc::clone(&closure_restarts);
10277 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10278 task_restarts.fetch_add(1, atomic::Ordering::Release);
10279 futures::future::ready(Ok(()))
10280 });
10281 })),
10282 ..Default::default()
10283 },
10284 );
10285
10286 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10287 let _buffer = project
10288 .update(cx, |project, cx| {
10289 project.open_local_buffer("/a/main.rs", cx)
10290 })
10291 .await
10292 .unwrap();
10293 let _fake_server = fake_servers.next().await.unwrap();
10294 update_test_language_settings(cx, |language_settings| {
10295 language_settings.languages.insert(
10296 language_name.clone(),
10297 LanguageSettingsContent {
10298 tab_size: NonZeroU32::new(8),
10299 ..Default::default()
10300 },
10301 );
10302 });
10303 cx.executor().run_until_parked();
10304 assert_eq!(
10305 server_restarts.load(atomic::Ordering::Acquire),
10306 0,
10307 "Should not restart LSP server on an unrelated change"
10308 );
10309
10310 update_test_project_settings(cx, |project_settings| {
10311 project_settings.lsp.insert(
10312 "Some other server name".into(),
10313 LspSettings {
10314 binary: None,
10315 settings: None,
10316 initialization_options: Some(json!({
10317 "some other init value": false
10318 })),
10319 },
10320 );
10321 });
10322 cx.executor().run_until_parked();
10323 assert_eq!(
10324 server_restarts.load(atomic::Ordering::Acquire),
10325 0,
10326 "Should not restart LSP server on an unrelated LSP settings change"
10327 );
10328
10329 update_test_project_settings(cx, |project_settings| {
10330 project_settings.lsp.insert(
10331 language_server_name.into(),
10332 LspSettings {
10333 binary: None,
10334 settings: None,
10335 initialization_options: Some(json!({
10336 "anotherInitValue": false
10337 })),
10338 },
10339 );
10340 });
10341 cx.executor().run_until_parked();
10342 assert_eq!(
10343 server_restarts.load(atomic::Ordering::Acquire),
10344 1,
10345 "Should restart LSP server on a related LSP settings change"
10346 );
10347
10348 update_test_project_settings(cx, |project_settings| {
10349 project_settings.lsp.insert(
10350 language_server_name.into(),
10351 LspSettings {
10352 binary: None,
10353 settings: None,
10354 initialization_options: Some(json!({
10355 "anotherInitValue": false
10356 })),
10357 },
10358 );
10359 });
10360 cx.executor().run_until_parked();
10361 assert_eq!(
10362 server_restarts.load(atomic::Ordering::Acquire),
10363 1,
10364 "Should not restart LSP server on a related LSP settings change that is the same"
10365 );
10366
10367 update_test_project_settings(cx, |project_settings| {
10368 project_settings.lsp.insert(
10369 language_server_name.into(),
10370 LspSettings {
10371 binary: None,
10372 settings: None,
10373 initialization_options: None,
10374 },
10375 );
10376 });
10377 cx.executor().run_until_parked();
10378 assert_eq!(
10379 server_restarts.load(atomic::Ordering::Acquire),
10380 2,
10381 "Should restart LSP server on another related LSP settings change"
10382 );
10383}
10384
10385#[gpui::test]
10386async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10387 init_test(cx, |_| {});
10388
10389 let mut cx = EditorLspTestContext::new_rust(
10390 lsp::ServerCapabilities {
10391 completion_provider: Some(lsp::CompletionOptions {
10392 trigger_characters: Some(vec![".".to_string()]),
10393 resolve_provider: Some(true),
10394 ..Default::default()
10395 }),
10396 ..Default::default()
10397 },
10398 cx,
10399 )
10400 .await;
10401
10402 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10403 cx.simulate_keystroke(".");
10404 let completion_item = lsp::CompletionItem {
10405 label: "some".into(),
10406 kind: Some(lsp::CompletionItemKind::SNIPPET),
10407 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10408 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10409 kind: lsp::MarkupKind::Markdown,
10410 value: "```rust\nSome(2)\n```".to_string(),
10411 })),
10412 deprecated: Some(false),
10413 sort_text: Some("fffffff2".to_string()),
10414 filter_text: Some("some".to_string()),
10415 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10416 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10417 range: lsp::Range {
10418 start: lsp::Position {
10419 line: 0,
10420 character: 22,
10421 },
10422 end: lsp::Position {
10423 line: 0,
10424 character: 22,
10425 },
10426 },
10427 new_text: "Some(2)".to_string(),
10428 })),
10429 additional_text_edits: Some(vec![lsp::TextEdit {
10430 range: lsp::Range {
10431 start: lsp::Position {
10432 line: 0,
10433 character: 20,
10434 },
10435 end: lsp::Position {
10436 line: 0,
10437 character: 22,
10438 },
10439 },
10440 new_text: "".to_string(),
10441 }]),
10442 ..Default::default()
10443 };
10444
10445 let closure_completion_item = completion_item.clone();
10446 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10447 let task_completion_item = closure_completion_item.clone();
10448 async move {
10449 Ok(Some(lsp::CompletionResponse::Array(vec![
10450 task_completion_item,
10451 ])))
10452 }
10453 });
10454
10455 request.next().await;
10456
10457 cx.condition(|editor, _| editor.context_menu_visible())
10458 .await;
10459 let apply_additional_edits = cx.update_editor(|editor, cx| {
10460 editor
10461 .confirm_completion(&ConfirmCompletion::default(), cx)
10462 .unwrap()
10463 });
10464 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10465
10466 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10467 let task_completion_item = completion_item.clone();
10468 async move { Ok(task_completion_item) }
10469 })
10470 .next()
10471 .await
10472 .unwrap();
10473 apply_additional_edits.await.unwrap();
10474 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10475}
10476
10477#[gpui::test]
10478async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10479 init_test(cx, |_| {});
10480
10481 let mut cx = EditorLspTestContext::new(
10482 Language::new(
10483 LanguageConfig {
10484 matcher: LanguageMatcher {
10485 path_suffixes: vec!["jsx".into()],
10486 ..Default::default()
10487 },
10488 overrides: [(
10489 "element".into(),
10490 LanguageConfigOverride {
10491 word_characters: Override::Set(['-'].into_iter().collect()),
10492 ..Default::default()
10493 },
10494 )]
10495 .into_iter()
10496 .collect(),
10497 ..Default::default()
10498 },
10499 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10500 )
10501 .with_override_query("(jsx_self_closing_element) @element")
10502 .unwrap(),
10503 lsp::ServerCapabilities {
10504 completion_provider: Some(lsp::CompletionOptions {
10505 trigger_characters: Some(vec![":".to_string()]),
10506 ..Default::default()
10507 }),
10508 ..Default::default()
10509 },
10510 cx,
10511 )
10512 .await;
10513
10514 cx.lsp
10515 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10516 Ok(Some(lsp::CompletionResponse::Array(vec![
10517 lsp::CompletionItem {
10518 label: "bg-blue".into(),
10519 ..Default::default()
10520 },
10521 lsp::CompletionItem {
10522 label: "bg-red".into(),
10523 ..Default::default()
10524 },
10525 lsp::CompletionItem {
10526 label: "bg-yellow".into(),
10527 ..Default::default()
10528 },
10529 ])))
10530 });
10531
10532 cx.set_state(r#"<p class="bgˇ" />"#);
10533
10534 // Trigger completion when typing a dash, because the dash is an extra
10535 // word character in the 'element' scope, which contains the cursor.
10536 cx.simulate_keystroke("-");
10537 cx.executor().run_until_parked();
10538 cx.update_editor(|editor, _| {
10539 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10540 assert_eq!(
10541 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10542 &["bg-red", "bg-blue", "bg-yellow"]
10543 );
10544 } else {
10545 panic!("expected completion menu to be open");
10546 }
10547 });
10548
10549 cx.simulate_keystroke("l");
10550 cx.executor().run_until_parked();
10551 cx.update_editor(|editor, _| {
10552 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10553 assert_eq!(
10554 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10555 &["bg-blue", "bg-yellow"]
10556 );
10557 } else {
10558 panic!("expected completion menu to be open");
10559 }
10560 });
10561
10562 // When filtering completions, consider the character after the '-' to
10563 // be the start of a subword.
10564 cx.set_state(r#"<p class="yelˇ" />"#);
10565 cx.simulate_keystroke("l");
10566 cx.executor().run_until_parked();
10567 cx.update_editor(|editor, _| {
10568 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10569 assert_eq!(
10570 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10571 &["bg-yellow"]
10572 );
10573 } else {
10574 panic!("expected completion menu to be open");
10575 }
10576 });
10577}
10578
10579#[gpui::test]
10580async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10581 init_test(cx, |settings| {
10582 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10583 FormatterList(vec![Formatter::Prettier].into()),
10584 ))
10585 });
10586
10587 let fs = FakeFs::new(cx.executor());
10588 fs.insert_file("/file.ts", Default::default()).await;
10589
10590 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10591 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10592
10593 language_registry.add(Arc::new(Language::new(
10594 LanguageConfig {
10595 name: "TypeScript".into(),
10596 matcher: LanguageMatcher {
10597 path_suffixes: vec!["ts".to_string()],
10598 ..Default::default()
10599 },
10600 ..Default::default()
10601 },
10602 Some(tree_sitter_rust::LANGUAGE.into()),
10603 )));
10604 update_test_language_settings(cx, |settings| {
10605 settings.defaults.prettier = Some(PrettierSettings {
10606 allowed: true,
10607 ..PrettierSettings::default()
10608 });
10609 });
10610
10611 let test_plugin = "test_plugin";
10612 let _ = language_registry.register_fake_lsp(
10613 "TypeScript",
10614 FakeLspAdapter {
10615 prettier_plugins: vec![test_plugin],
10616 ..Default::default()
10617 },
10618 );
10619
10620 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10621 let buffer = project
10622 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10623 .await
10624 .unwrap();
10625
10626 let buffer_text = "one\ntwo\nthree\n";
10627 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10628 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10629 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10630
10631 editor
10632 .update(cx, |editor, cx| {
10633 editor.perform_format(
10634 project.clone(),
10635 FormatTrigger::Manual,
10636 FormatTarget::Buffer,
10637 cx,
10638 )
10639 })
10640 .unwrap()
10641 .await;
10642 assert_eq!(
10643 editor.update(cx, |editor, cx| editor.text(cx)),
10644 buffer_text.to_string() + prettier_format_suffix,
10645 "Test prettier formatting was not applied to the original buffer text",
10646 );
10647
10648 update_test_language_settings(cx, |settings| {
10649 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10650 });
10651 let format = editor.update(cx, |editor, cx| {
10652 editor.perform_format(
10653 project.clone(),
10654 FormatTrigger::Manual,
10655 FormatTarget::Buffer,
10656 cx,
10657 )
10658 });
10659 format.await.unwrap();
10660 assert_eq!(
10661 editor.update(cx, |editor, cx| editor.text(cx)),
10662 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10663 "Autoformatting (via test prettier) was not applied to the original buffer text",
10664 );
10665}
10666
10667#[gpui::test]
10668async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10669 init_test(cx, |_| {});
10670 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10671 let base_text = indoc! {r#"struct Row;
10672struct Row1;
10673struct Row2;
10674
10675struct Row4;
10676struct Row5;
10677struct Row6;
10678
10679struct Row8;
10680struct Row9;
10681struct Row10;"#};
10682
10683 // When addition hunks are not adjacent to carets, no hunk revert is performed
10684 assert_hunk_revert(
10685 indoc! {r#"struct Row;
10686 struct Row1;
10687 struct Row1.1;
10688 struct Row1.2;
10689 struct Row2;ˇ
10690
10691 struct Row4;
10692 struct Row5;
10693 struct Row6;
10694
10695 struct Row8;
10696 ˇstruct Row9;
10697 struct Row9.1;
10698 struct Row9.2;
10699 struct Row9.3;
10700 struct Row10;"#},
10701 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10702 indoc! {r#"struct Row;
10703 struct Row1;
10704 struct Row1.1;
10705 struct Row1.2;
10706 struct Row2;ˇ
10707
10708 struct Row4;
10709 struct Row5;
10710 struct Row6;
10711
10712 struct Row8;
10713 ˇstruct Row9;
10714 struct Row9.1;
10715 struct Row9.2;
10716 struct Row9.3;
10717 struct Row10;"#},
10718 base_text,
10719 &mut cx,
10720 );
10721 // Same for selections
10722 assert_hunk_revert(
10723 indoc! {r#"struct Row;
10724 struct Row1;
10725 struct Row2;
10726 struct Row2.1;
10727 struct Row2.2;
10728 «ˇ
10729 struct Row4;
10730 struct» Row5;
10731 «struct Row6;
10732 ˇ»
10733 struct Row9.1;
10734 struct Row9.2;
10735 struct Row9.3;
10736 struct Row8;
10737 struct Row9;
10738 struct Row10;"#},
10739 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10740 indoc! {r#"struct Row;
10741 struct Row1;
10742 struct Row2;
10743 struct Row2.1;
10744 struct Row2.2;
10745 «ˇ
10746 struct Row4;
10747 struct» Row5;
10748 «struct Row6;
10749 ˇ»
10750 struct Row9.1;
10751 struct Row9.2;
10752 struct Row9.3;
10753 struct Row8;
10754 struct Row9;
10755 struct Row10;"#},
10756 base_text,
10757 &mut cx,
10758 );
10759
10760 // When carets and selections intersect the addition hunks, those are reverted.
10761 // Adjacent carets got merged.
10762 assert_hunk_revert(
10763 indoc! {r#"struct Row;
10764 ˇ// something on the top
10765 struct Row1;
10766 struct Row2;
10767 struct Roˇw3.1;
10768 struct Row2.2;
10769 struct Row2.3;ˇ
10770
10771 struct Row4;
10772 struct ˇRow5.1;
10773 struct Row5.2;
10774 struct «Rowˇ»5.3;
10775 struct Row5;
10776 struct Row6;
10777 ˇ
10778 struct Row9.1;
10779 struct «Rowˇ»9.2;
10780 struct «ˇRow»9.3;
10781 struct Row8;
10782 struct Row9;
10783 «ˇ// something on bottom»
10784 struct Row10;"#},
10785 vec![
10786 DiffHunkStatus::Added,
10787 DiffHunkStatus::Added,
10788 DiffHunkStatus::Added,
10789 DiffHunkStatus::Added,
10790 DiffHunkStatus::Added,
10791 ],
10792 indoc! {r#"struct Row;
10793 ˇstruct Row1;
10794 struct Row2;
10795 ˇ
10796 struct Row4;
10797 ˇstruct Row5;
10798 struct Row6;
10799 ˇ
10800 ˇstruct Row8;
10801 struct Row9;
10802 ˇstruct Row10;"#},
10803 base_text,
10804 &mut cx,
10805 );
10806}
10807
10808#[gpui::test]
10809async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10810 init_test(cx, |_| {});
10811 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10812 let base_text = indoc! {r#"struct Row;
10813struct Row1;
10814struct Row2;
10815
10816struct Row4;
10817struct Row5;
10818struct Row6;
10819
10820struct Row8;
10821struct Row9;
10822struct Row10;"#};
10823
10824 // Modification hunks behave the same as the addition ones.
10825 assert_hunk_revert(
10826 indoc! {r#"struct Row;
10827 struct Row1;
10828 struct Row33;
10829 ˇ
10830 struct Row4;
10831 struct Row5;
10832 struct Row6;
10833 ˇ
10834 struct Row99;
10835 struct Row9;
10836 struct Row10;"#},
10837 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10838 indoc! {r#"struct Row;
10839 struct Row1;
10840 struct Row33;
10841 ˇ
10842 struct Row4;
10843 struct Row5;
10844 struct Row6;
10845 ˇ
10846 struct Row99;
10847 struct Row9;
10848 struct Row10;"#},
10849 base_text,
10850 &mut cx,
10851 );
10852 assert_hunk_revert(
10853 indoc! {r#"struct Row;
10854 struct Row1;
10855 struct Row33;
10856 «ˇ
10857 struct Row4;
10858 struct» Row5;
10859 «struct Row6;
10860 ˇ»
10861 struct Row99;
10862 struct Row9;
10863 struct Row10;"#},
10864 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10865 indoc! {r#"struct Row;
10866 struct Row1;
10867 struct Row33;
10868 «ˇ
10869 struct Row4;
10870 struct» Row5;
10871 «struct Row6;
10872 ˇ»
10873 struct Row99;
10874 struct Row9;
10875 struct Row10;"#},
10876 base_text,
10877 &mut cx,
10878 );
10879
10880 assert_hunk_revert(
10881 indoc! {r#"ˇstruct Row1.1;
10882 struct Row1;
10883 «ˇstr»uct Row22;
10884
10885 struct ˇRow44;
10886 struct Row5;
10887 struct «Rˇ»ow66;ˇ
10888
10889 «struˇ»ct Row88;
10890 struct Row9;
10891 struct Row1011;ˇ"#},
10892 vec![
10893 DiffHunkStatus::Modified,
10894 DiffHunkStatus::Modified,
10895 DiffHunkStatus::Modified,
10896 DiffHunkStatus::Modified,
10897 DiffHunkStatus::Modified,
10898 DiffHunkStatus::Modified,
10899 ],
10900 indoc! {r#"struct Row;
10901 ˇstruct Row1;
10902 struct Row2;
10903 ˇ
10904 struct Row4;
10905 ˇstruct Row5;
10906 struct Row6;
10907 ˇ
10908 struct Row8;
10909 ˇstruct Row9;
10910 struct Row10;ˇ"#},
10911 base_text,
10912 &mut cx,
10913 );
10914}
10915
10916#[gpui::test]
10917async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10918 init_test(cx, |_| {});
10919 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10920 let base_text = indoc! {r#"struct Row;
10921struct Row1;
10922struct Row2;
10923
10924struct Row4;
10925struct Row5;
10926struct Row6;
10927
10928struct Row8;
10929struct Row9;
10930struct Row10;"#};
10931
10932 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10933 assert_hunk_revert(
10934 indoc! {r#"struct Row;
10935 struct Row2;
10936
10937 ˇstruct Row4;
10938 struct Row5;
10939 struct Row6;
10940 ˇ
10941 struct Row8;
10942 struct Row10;"#},
10943 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10944 indoc! {r#"struct Row;
10945 struct Row2;
10946
10947 ˇstruct Row4;
10948 struct Row5;
10949 struct Row6;
10950 ˇ
10951 struct Row8;
10952 struct Row10;"#},
10953 base_text,
10954 &mut cx,
10955 );
10956 assert_hunk_revert(
10957 indoc! {r#"struct Row;
10958 struct Row2;
10959
10960 «ˇstruct Row4;
10961 struct» Row5;
10962 «struct Row6;
10963 ˇ»
10964 struct Row8;
10965 struct Row10;"#},
10966 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10967 indoc! {r#"struct Row;
10968 struct Row2;
10969
10970 «ˇstruct Row4;
10971 struct» Row5;
10972 «struct Row6;
10973 ˇ»
10974 struct Row8;
10975 struct Row10;"#},
10976 base_text,
10977 &mut cx,
10978 );
10979
10980 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10981 assert_hunk_revert(
10982 indoc! {r#"struct Row;
10983 ˇstruct Row2;
10984
10985 struct Row4;
10986 struct Row5;
10987 struct Row6;
10988
10989 struct Row8;ˇ
10990 struct Row10;"#},
10991 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10992 indoc! {r#"struct Row;
10993 struct Row1;
10994 ˇstruct Row2;
10995
10996 struct Row4;
10997 struct Row5;
10998 struct Row6;
10999
11000 struct Row8;ˇ
11001 struct Row9;
11002 struct Row10;"#},
11003 base_text,
11004 &mut cx,
11005 );
11006 assert_hunk_revert(
11007 indoc! {r#"struct Row;
11008 struct Row2«ˇ;
11009 struct Row4;
11010 struct» Row5;
11011 «struct Row6;
11012
11013 struct Row8;ˇ»
11014 struct Row10;"#},
11015 vec![
11016 DiffHunkStatus::Removed,
11017 DiffHunkStatus::Removed,
11018 DiffHunkStatus::Removed,
11019 ],
11020 indoc! {r#"struct Row;
11021 struct Row1;
11022 struct Row2«ˇ;
11023
11024 struct Row4;
11025 struct» Row5;
11026 «struct Row6;
11027
11028 struct Row8;ˇ»
11029 struct Row9;
11030 struct Row10;"#},
11031 base_text,
11032 &mut cx,
11033 );
11034}
11035
11036#[gpui::test]
11037async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11038 init_test(cx, |_| {});
11039
11040 let cols = 4;
11041 let rows = 10;
11042 let sample_text_1 = sample_text(rows, cols, 'a');
11043 assert_eq!(
11044 sample_text_1,
11045 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11046 );
11047 let sample_text_2 = sample_text(rows, cols, 'l');
11048 assert_eq!(
11049 sample_text_2,
11050 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11051 );
11052 let sample_text_3 = sample_text(rows, cols, 'v');
11053 assert_eq!(
11054 sample_text_3,
11055 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11056 );
11057
11058 fn diff_every_buffer_row(
11059 buffer: &Model<Buffer>,
11060 sample_text: String,
11061 cols: usize,
11062 cx: &mut gpui::TestAppContext,
11063 ) {
11064 // revert first character in each row, creating one large diff hunk per buffer
11065 let is_first_char = |offset: usize| offset % cols == 0;
11066 buffer.update(cx, |buffer, cx| {
11067 buffer.set_text(
11068 sample_text
11069 .chars()
11070 .enumerate()
11071 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11072 .collect::<String>(),
11073 cx,
11074 );
11075 buffer.set_diff_base(Some(sample_text), cx);
11076 });
11077 cx.executor().run_until_parked();
11078 }
11079
11080 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11081 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11082
11083 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11084 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11085
11086 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11087 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11088
11089 let multibuffer = cx.new_model(|cx| {
11090 let mut multibuffer = MultiBuffer::new(ReadWrite);
11091 multibuffer.push_excerpts(
11092 buffer_1.clone(),
11093 [
11094 ExcerptRange {
11095 context: Point::new(0, 0)..Point::new(3, 0),
11096 primary: None,
11097 },
11098 ExcerptRange {
11099 context: Point::new(5, 0)..Point::new(7, 0),
11100 primary: None,
11101 },
11102 ExcerptRange {
11103 context: Point::new(9, 0)..Point::new(10, 4),
11104 primary: None,
11105 },
11106 ],
11107 cx,
11108 );
11109 multibuffer.push_excerpts(
11110 buffer_2.clone(),
11111 [
11112 ExcerptRange {
11113 context: Point::new(0, 0)..Point::new(3, 0),
11114 primary: None,
11115 },
11116 ExcerptRange {
11117 context: Point::new(5, 0)..Point::new(7, 0),
11118 primary: None,
11119 },
11120 ExcerptRange {
11121 context: Point::new(9, 0)..Point::new(10, 4),
11122 primary: None,
11123 },
11124 ],
11125 cx,
11126 );
11127 multibuffer.push_excerpts(
11128 buffer_3.clone(),
11129 [
11130 ExcerptRange {
11131 context: Point::new(0, 0)..Point::new(3, 0),
11132 primary: None,
11133 },
11134 ExcerptRange {
11135 context: Point::new(5, 0)..Point::new(7, 0),
11136 primary: None,
11137 },
11138 ExcerptRange {
11139 context: Point::new(9, 0)..Point::new(10, 4),
11140 primary: None,
11141 },
11142 ],
11143 cx,
11144 );
11145 multibuffer
11146 });
11147
11148 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11149 editor.update(cx, |editor, cx| {
11150 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");
11151 editor.select_all(&SelectAll, cx);
11152 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11153 });
11154 cx.executor().run_until_parked();
11155 // When all ranges are selected, all buffer hunks are reverted.
11156 editor.update(cx, |editor, cx| {
11157 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");
11158 });
11159 buffer_1.update(cx, |buffer, _| {
11160 assert_eq!(buffer.text(), sample_text_1);
11161 });
11162 buffer_2.update(cx, |buffer, _| {
11163 assert_eq!(buffer.text(), sample_text_2);
11164 });
11165 buffer_3.update(cx, |buffer, _| {
11166 assert_eq!(buffer.text(), sample_text_3);
11167 });
11168
11169 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11170 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11171 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11172 editor.update(cx, |editor, cx| {
11173 editor.change_selections(None, cx, |s| {
11174 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11175 });
11176 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11177 });
11178 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11179 // but not affect buffer_2 and its related excerpts.
11180 editor.update(cx, |editor, cx| {
11181 assert_eq!(
11182 editor.text(cx),
11183 "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"
11184 );
11185 });
11186 buffer_1.update(cx, |buffer, _| {
11187 assert_eq!(buffer.text(), sample_text_1);
11188 });
11189 buffer_2.update(cx, |buffer, _| {
11190 assert_eq!(
11191 buffer.text(),
11192 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11193 );
11194 });
11195 buffer_3.update(cx, |buffer, _| {
11196 assert_eq!(
11197 buffer.text(),
11198 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11199 );
11200 });
11201}
11202
11203#[gpui::test]
11204async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11205 init_test(cx, |_| {});
11206
11207 let cols = 4;
11208 let rows = 10;
11209 let sample_text_1 = sample_text(rows, cols, 'a');
11210 assert_eq!(
11211 sample_text_1,
11212 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11213 );
11214 let sample_text_2 = sample_text(rows, cols, 'l');
11215 assert_eq!(
11216 sample_text_2,
11217 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11218 );
11219 let sample_text_3 = sample_text(rows, cols, 'v');
11220 assert_eq!(
11221 sample_text_3,
11222 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11223 );
11224
11225 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11226 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11227 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11228
11229 let multi_buffer = cx.new_model(|cx| {
11230 let mut multibuffer = MultiBuffer::new(ReadWrite);
11231 multibuffer.push_excerpts(
11232 buffer_1.clone(),
11233 [
11234 ExcerptRange {
11235 context: Point::new(0, 0)..Point::new(3, 0),
11236 primary: None,
11237 },
11238 ExcerptRange {
11239 context: Point::new(5, 0)..Point::new(7, 0),
11240 primary: None,
11241 },
11242 ExcerptRange {
11243 context: Point::new(9, 0)..Point::new(10, 4),
11244 primary: None,
11245 },
11246 ],
11247 cx,
11248 );
11249 multibuffer.push_excerpts(
11250 buffer_2.clone(),
11251 [
11252 ExcerptRange {
11253 context: Point::new(0, 0)..Point::new(3, 0),
11254 primary: None,
11255 },
11256 ExcerptRange {
11257 context: Point::new(5, 0)..Point::new(7, 0),
11258 primary: None,
11259 },
11260 ExcerptRange {
11261 context: Point::new(9, 0)..Point::new(10, 4),
11262 primary: None,
11263 },
11264 ],
11265 cx,
11266 );
11267 multibuffer.push_excerpts(
11268 buffer_3.clone(),
11269 [
11270 ExcerptRange {
11271 context: Point::new(0, 0)..Point::new(3, 0),
11272 primary: None,
11273 },
11274 ExcerptRange {
11275 context: Point::new(5, 0)..Point::new(7, 0),
11276 primary: None,
11277 },
11278 ExcerptRange {
11279 context: Point::new(9, 0)..Point::new(10, 4),
11280 primary: None,
11281 },
11282 ],
11283 cx,
11284 );
11285 multibuffer
11286 });
11287
11288 let fs = FakeFs::new(cx.executor());
11289 fs.insert_tree(
11290 "/a",
11291 json!({
11292 "main.rs": sample_text_1,
11293 "other.rs": sample_text_2,
11294 "lib.rs": sample_text_3,
11295 }),
11296 )
11297 .await;
11298 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11299 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11300 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11301 let multi_buffer_editor = cx.new_view(|cx| {
11302 Editor::new(
11303 EditorMode::Full,
11304 multi_buffer,
11305 Some(project.clone()),
11306 true,
11307 cx,
11308 )
11309 });
11310 let multibuffer_item_id = workspace
11311 .update(cx, |workspace, cx| {
11312 assert!(
11313 workspace.active_item(cx).is_none(),
11314 "active item should be None before the first item is added"
11315 );
11316 workspace.add_item_to_active_pane(
11317 Box::new(multi_buffer_editor.clone()),
11318 None,
11319 true,
11320 cx,
11321 );
11322 let active_item = workspace
11323 .active_item(cx)
11324 .expect("should have an active item after adding the multi buffer");
11325 assert!(
11326 !active_item.is_singleton(cx),
11327 "A multi buffer was expected to active after adding"
11328 );
11329 active_item.item_id()
11330 })
11331 .unwrap();
11332 cx.executor().run_until_parked();
11333
11334 multi_buffer_editor.update(cx, |editor, cx| {
11335 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11336 editor.open_excerpts(&OpenExcerpts, cx);
11337 });
11338 cx.executor().run_until_parked();
11339 let first_item_id = workspace
11340 .update(cx, |workspace, cx| {
11341 let active_item = workspace
11342 .active_item(cx)
11343 .expect("should have an active item after navigating into the 1st buffer");
11344 let first_item_id = active_item.item_id();
11345 assert_ne!(
11346 first_item_id, multibuffer_item_id,
11347 "Should navigate into the 1st buffer and activate it"
11348 );
11349 assert!(
11350 active_item.is_singleton(cx),
11351 "New active item should be a singleton buffer"
11352 );
11353 assert_eq!(
11354 active_item
11355 .act_as::<Editor>(cx)
11356 .expect("should have navigated into an editor for the 1st buffer")
11357 .read(cx)
11358 .text(cx),
11359 sample_text_1
11360 );
11361
11362 workspace
11363 .go_back(workspace.active_pane().downgrade(), cx)
11364 .detach_and_log_err(cx);
11365
11366 first_item_id
11367 })
11368 .unwrap();
11369 cx.executor().run_until_parked();
11370 workspace
11371 .update(cx, |workspace, cx| {
11372 let active_item = workspace
11373 .active_item(cx)
11374 .expect("should have an active item after navigating back");
11375 assert_eq!(
11376 active_item.item_id(),
11377 multibuffer_item_id,
11378 "Should navigate back to the multi buffer"
11379 );
11380 assert!(!active_item.is_singleton(cx));
11381 })
11382 .unwrap();
11383
11384 multi_buffer_editor.update(cx, |editor, cx| {
11385 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11386 s.select_ranges(Some(39..40))
11387 });
11388 editor.open_excerpts(&OpenExcerpts, cx);
11389 });
11390 cx.executor().run_until_parked();
11391 let second_item_id = workspace
11392 .update(cx, |workspace, cx| {
11393 let active_item = workspace
11394 .active_item(cx)
11395 .expect("should have an active item after navigating into the 2nd buffer");
11396 let second_item_id = active_item.item_id();
11397 assert_ne!(
11398 second_item_id, multibuffer_item_id,
11399 "Should navigate away from the multibuffer"
11400 );
11401 assert_ne!(
11402 second_item_id, first_item_id,
11403 "Should navigate into the 2nd buffer and activate it"
11404 );
11405 assert!(
11406 active_item.is_singleton(cx),
11407 "New active item should be a singleton buffer"
11408 );
11409 assert_eq!(
11410 active_item
11411 .act_as::<Editor>(cx)
11412 .expect("should have navigated into an editor")
11413 .read(cx)
11414 .text(cx),
11415 sample_text_2
11416 );
11417
11418 workspace
11419 .go_back(workspace.active_pane().downgrade(), cx)
11420 .detach_and_log_err(cx);
11421
11422 second_item_id
11423 })
11424 .unwrap();
11425 cx.executor().run_until_parked();
11426 workspace
11427 .update(cx, |workspace, cx| {
11428 let active_item = workspace
11429 .active_item(cx)
11430 .expect("should have an active item after navigating back from the 2nd buffer");
11431 assert_eq!(
11432 active_item.item_id(),
11433 multibuffer_item_id,
11434 "Should navigate back from the 2nd buffer to the multi buffer"
11435 );
11436 assert!(!active_item.is_singleton(cx));
11437 })
11438 .unwrap();
11439
11440 multi_buffer_editor.update(cx, |editor, cx| {
11441 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11442 s.select_ranges(Some(60..70))
11443 });
11444 editor.open_excerpts(&OpenExcerpts, cx);
11445 });
11446 cx.executor().run_until_parked();
11447 workspace
11448 .update(cx, |workspace, cx| {
11449 let active_item = workspace
11450 .active_item(cx)
11451 .expect("should have an active item after navigating into the 3rd buffer");
11452 let third_item_id = active_item.item_id();
11453 assert_ne!(
11454 third_item_id, multibuffer_item_id,
11455 "Should navigate into the 3rd buffer and activate it"
11456 );
11457 assert_ne!(third_item_id, first_item_id);
11458 assert_ne!(third_item_id, second_item_id);
11459 assert!(
11460 active_item.is_singleton(cx),
11461 "New active item should be a singleton buffer"
11462 );
11463 assert_eq!(
11464 active_item
11465 .act_as::<Editor>(cx)
11466 .expect("should have navigated into an editor")
11467 .read(cx)
11468 .text(cx),
11469 sample_text_3
11470 );
11471
11472 workspace
11473 .go_back(workspace.active_pane().downgrade(), cx)
11474 .detach_and_log_err(cx);
11475 })
11476 .unwrap();
11477 cx.executor().run_until_parked();
11478 workspace
11479 .update(cx, |workspace, cx| {
11480 let active_item = workspace
11481 .active_item(cx)
11482 .expect("should have an active item after navigating back from the 3rd buffer");
11483 assert_eq!(
11484 active_item.item_id(),
11485 multibuffer_item_id,
11486 "Should navigate back from the 3rd buffer to the multi buffer"
11487 );
11488 assert!(!active_item.is_singleton(cx));
11489 })
11490 .unwrap();
11491}
11492
11493#[gpui::test]
11494async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11495 init_test(cx, |_| {});
11496
11497 let mut cx = EditorTestContext::new(cx).await;
11498
11499 let diff_base = r#"
11500 use some::mod;
11501
11502 const A: u32 = 42;
11503
11504 fn main() {
11505 println!("hello");
11506
11507 println!("world");
11508 }
11509 "#
11510 .unindent();
11511
11512 cx.set_state(
11513 &r#"
11514 use some::modified;
11515
11516 ˇ
11517 fn main() {
11518 println!("hello there");
11519
11520 println!("around the");
11521 println!("world");
11522 }
11523 "#
11524 .unindent(),
11525 );
11526
11527 cx.set_diff_base(Some(&diff_base));
11528 executor.run_until_parked();
11529
11530 cx.update_editor(|editor, cx| {
11531 editor.go_to_next_hunk(&GoToHunk, cx);
11532 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11533 });
11534 executor.run_until_parked();
11535 cx.assert_diff_hunks(
11536 r#"
11537 use some::modified;
11538
11539
11540 fn main() {
11541 - println!("hello");
11542 + println!("hello there");
11543
11544 println!("around the");
11545 println!("world");
11546 }
11547 "#
11548 .unindent(),
11549 );
11550
11551 cx.update_editor(|editor, cx| {
11552 for _ in 0..3 {
11553 editor.go_to_next_hunk(&GoToHunk, cx);
11554 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11555 }
11556 });
11557 executor.run_until_parked();
11558 cx.assert_editor_state(
11559 &r#"
11560 use some::modified;
11561
11562 ˇ
11563 fn main() {
11564 println!("hello there");
11565
11566 println!("around the");
11567 println!("world");
11568 }
11569 "#
11570 .unindent(),
11571 );
11572
11573 cx.assert_diff_hunks(
11574 r#"
11575 - use some::mod;
11576 + use some::modified;
11577
11578 - const A: u32 = 42;
11579
11580 fn main() {
11581 - println!("hello");
11582 + println!("hello there");
11583
11584 + println!("around the");
11585 println!("world");
11586 }
11587 "#
11588 .unindent(),
11589 );
11590
11591 cx.update_editor(|editor, cx| {
11592 editor.cancel(&Cancel, cx);
11593 });
11594
11595 cx.assert_diff_hunks(
11596 r#"
11597 use some::modified;
11598
11599
11600 fn main() {
11601 println!("hello there");
11602
11603 println!("around the");
11604 println!("world");
11605 }
11606 "#
11607 .unindent(),
11608 );
11609}
11610
11611#[gpui::test]
11612async fn test_diff_base_change_with_expanded_diff_hunks(
11613 executor: BackgroundExecutor,
11614 cx: &mut gpui::TestAppContext,
11615) {
11616 init_test(cx, |_| {});
11617
11618 let mut cx = EditorTestContext::new(cx).await;
11619
11620 let diff_base = r#"
11621 use some::mod1;
11622 use some::mod2;
11623
11624 const A: u32 = 42;
11625 const B: u32 = 42;
11626 const C: u32 = 42;
11627
11628 fn main() {
11629 println!("hello");
11630
11631 println!("world");
11632 }
11633 "#
11634 .unindent();
11635
11636 cx.set_state(
11637 &r#"
11638 use some::mod2;
11639
11640 const A: u32 = 42;
11641 const C: u32 = 42;
11642
11643 fn main(ˇ) {
11644 //println!("hello");
11645
11646 println!("world");
11647 //
11648 //
11649 }
11650 "#
11651 .unindent(),
11652 );
11653
11654 cx.set_diff_base(Some(&diff_base));
11655 executor.run_until_parked();
11656
11657 cx.update_editor(|editor, cx| {
11658 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11659 });
11660 executor.run_until_parked();
11661 cx.assert_diff_hunks(
11662 r#"
11663 - use some::mod1;
11664 use some::mod2;
11665
11666 const A: u32 = 42;
11667 - const B: u32 = 42;
11668 const C: u32 = 42;
11669
11670 fn main() {
11671 - println!("hello");
11672 + //println!("hello");
11673
11674 println!("world");
11675 + //
11676 + //
11677 }
11678 "#
11679 .unindent(),
11680 );
11681
11682 cx.set_diff_base(Some("new diff base!"));
11683 executor.run_until_parked();
11684 cx.assert_diff_hunks(
11685 r#"
11686 use some::mod2;
11687
11688 const A: u32 = 42;
11689 const C: u32 = 42;
11690
11691 fn main() {
11692 //println!("hello");
11693
11694 println!("world");
11695 //
11696 //
11697 }
11698 "#
11699 .unindent(),
11700 );
11701
11702 cx.update_editor(|editor, cx| {
11703 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11704 });
11705 executor.run_until_parked();
11706 cx.assert_diff_hunks(
11707 r#"
11708 - new diff base!
11709 + use some::mod2;
11710 +
11711 + const A: u32 = 42;
11712 + const C: u32 = 42;
11713 +
11714 + fn main() {
11715 + //println!("hello");
11716 +
11717 + println!("world");
11718 + //
11719 + //
11720 + }
11721 "#
11722 .unindent(),
11723 );
11724}
11725
11726#[gpui::test]
11727async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11728 init_test(cx, |_| {});
11729
11730 let mut cx = EditorTestContext::new(cx).await;
11731
11732 let diff_base = r#"
11733 use some::mod1;
11734 use some::mod2;
11735
11736 const A: u32 = 42;
11737 const B: u32 = 42;
11738 const C: u32 = 42;
11739
11740 fn main() {
11741 println!("hello");
11742
11743 println!("world");
11744 }
11745
11746 fn another() {
11747 println!("another");
11748 }
11749
11750 fn another2() {
11751 println!("another2");
11752 }
11753 "#
11754 .unindent();
11755
11756 cx.set_state(
11757 &r#"
11758 «use some::mod2;
11759
11760 const A: u32 = 42;
11761 const C: u32 = 42;
11762
11763 fn main() {
11764 //println!("hello");
11765
11766 println!("world");
11767 //
11768 //ˇ»
11769 }
11770
11771 fn another() {
11772 println!("another");
11773 println!("another");
11774 }
11775
11776 println!("another2");
11777 }
11778 "#
11779 .unindent(),
11780 );
11781
11782 cx.set_diff_base(Some(&diff_base));
11783 executor.run_until_parked();
11784
11785 cx.update_editor(|editor, cx| {
11786 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11787 });
11788 executor.run_until_parked();
11789
11790 cx.assert_diff_hunks(
11791 r#"
11792 - use some::mod1;
11793 use some::mod2;
11794
11795 const A: u32 = 42;
11796 - const B: u32 = 42;
11797 const C: u32 = 42;
11798
11799 fn main() {
11800 - println!("hello");
11801 + //println!("hello");
11802
11803 println!("world");
11804 + //
11805 + //
11806 }
11807
11808 fn another() {
11809 println!("another");
11810 + println!("another");
11811 }
11812
11813 - fn another2() {
11814 println!("another2");
11815 }
11816 "#
11817 .unindent(),
11818 );
11819
11820 // Fold across some of the diff hunks. They should no longer appear expanded.
11821 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11822 cx.executor().run_until_parked();
11823
11824 // Hunks are not shown if their position is within a fold
11825 cx.assert_diff_hunks(
11826 r#"
11827 use some::mod2;
11828
11829 const A: u32 = 42;
11830 const C: u32 = 42;
11831
11832 fn main() {
11833 //println!("hello");
11834
11835 println!("world");
11836 //
11837 //
11838 }
11839
11840 fn another() {
11841 println!("another");
11842 + println!("another");
11843 }
11844
11845 - fn another2() {
11846 println!("another2");
11847 }
11848 "#
11849 .unindent(),
11850 );
11851
11852 cx.update_editor(|editor, cx| {
11853 editor.select_all(&SelectAll, cx);
11854 editor.unfold_lines(&UnfoldLines, cx);
11855 });
11856 cx.executor().run_until_parked();
11857
11858 // The deletions reappear when unfolding.
11859 cx.assert_diff_hunks(
11860 r#"
11861 - use some::mod1;
11862 use some::mod2;
11863
11864 const A: u32 = 42;
11865 - const B: u32 = 42;
11866 const C: u32 = 42;
11867
11868 fn main() {
11869 - println!("hello");
11870 + //println!("hello");
11871
11872 println!("world");
11873 + //
11874 + //
11875 }
11876
11877 fn another() {
11878 println!("another");
11879 + println!("another");
11880 }
11881
11882 - fn another2() {
11883 println!("another2");
11884 }
11885 "#
11886 .unindent(),
11887 );
11888}
11889
11890#[gpui::test]
11891async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11892 init_test(cx, |_| {});
11893
11894 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11895 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11896 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11897 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11898 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11899 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11900
11901 let buffer_1 = cx.new_model(|cx| {
11902 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11903 buffer.set_diff_base(Some(file_1_old.into()), cx);
11904 buffer
11905 });
11906 let buffer_2 = cx.new_model(|cx| {
11907 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11908 buffer.set_diff_base(Some(file_2_old.into()), cx);
11909 buffer
11910 });
11911 let buffer_3 = cx.new_model(|cx| {
11912 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11913 buffer.set_diff_base(Some(file_3_old.into()), cx);
11914 buffer
11915 });
11916
11917 let multi_buffer = cx.new_model(|cx| {
11918 let mut multibuffer = MultiBuffer::new(ReadWrite);
11919 multibuffer.push_excerpts(
11920 buffer_1.clone(),
11921 [
11922 ExcerptRange {
11923 context: Point::new(0, 0)..Point::new(3, 0),
11924 primary: None,
11925 },
11926 ExcerptRange {
11927 context: Point::new(5, 0)..Point::new(7, 0),
11928 primary: None,
11929 },
11930 ExcerptRange {
11931 context: Point::new(9, 0)..Point::new(10, 3),
11932 primary: None,
11933 },
11934 ],
11935 cx,
11936 );
11937 multibuffer.push_excerpts(
11938 buffer_2.clone(),
11939 [
11940 ExcerptRange {
11941 context: Point::new(0, 0)..Point::new(3, 0),
11942 primary: None,
11943 },
11944 ExcerptRange {
11945 context: Point::new(5, 0)..Point::new(7, 0),
11946 primary: None,
11947 },
11948 ExcerptRange {
11949 context: Point::new(9, 0)..Point::new(10, 3),
11950 primary: None,
11951 },
11952 ],
11953 cx,
11954 );
11955 multibuffer.push_excerpts(
11956 buffer_3.clone(),
11957 [
11958 ExcerptRange {
11959 context: Point::new(0, 0)..Point::new(3, 0),
11960 primary: None,
11961 },
11962 ExcerptRange {
11963 context: Point::new(5, 0)..Point::new(7, 0),
11964 primary: None,
11965 },
11966 ExcerptRange {
11967 context: Point::new(9, 0)..Point::new(10, 3),
11968 primary: None,
11969 },
11970 ],
11971 cx,
11972 );
11973 multibuffer
11974 });
11975
11976 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
11977 let mut cx = EditorTestContext::for_editor(editor, cx).await;
11978 cx.run_until_parked();
11979
11980 cx.assert_editor_state(
11981 &"
11982 ˇaaa
11983 ccc
11984 ddd
11985
11986 ggg
11987 hhh
11988
11989
11990 lll
11991 mmm
11992 NNN
11993
11994 qqq
11995 rrr
11996
11997 uuu
11998 111
11999 222
12000 333
12001
12002 666
12003 777
12004
12005 000
12006 !!!"
12007 .unindent(),
12008 );
12009
12010 cx.update_editor(|editor, cx| {
12011 editor.select_all(&SelectAll, cx);
12012 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12013 });
12014 cx.executor().run_until_parked();
12015
12016 cx.assert_diff_hunks(
12017 "
12018 aaa
12019 - bbb
12020 ccc
12021 ddd
12022
12023 ggg
12024 hhh
12025
12026
12027 lll
12028 mmm
12029 - nnn
12030 + NNN
12031
12032 qqq
12033 rrr
12034
12035 uuu
12036 111
12037 222
12038 333
12039
12040 + 666
12041 777
12042
12043 000
12044 !!!"
12045 .unindent(),
12046 );
12047}
12048
12049#[gpui::test]
12050async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12051 init_test(cx, |_| {});
12052
12053 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12054 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12055
12056 let buffer = cx.new_model(|cx| {
12057 let mut buffer = Buffer::local(text.to_string(), cx);
12058 buffer.set_diff_base(Some(base.into()), cx);
12059 buffer
12060 });
12061
12062 let multi_buffer = cx.new_model(|cx| {
12063 let mut multibuffer = MultiBuffer::new(ReadWrite);
12064 multibuffer.push_excerpts(
12065 buffer.clone(),
12066 [
12067 ExcerptRange {
12068 context: Point::new(0, 0)..Point::new(2, 0),
12069 primary: None,
12070 },
12071 ExcerptRange {
12072 context: Point::new(5, 0)..Point::new(7, 0),
12073 primary: None,
12074 },
12075 ],
12076 cx,
12077 );
12078 multibuffer
12079 });
12080
12081 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12082 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12083 cx.run_until_parked();
12084
12085 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12086 cx.executor().run_until_parked();
12087
12088 cx.assert_diff_hunks(
12089 "
12090 aaa
12091 - bbb
12092 + BBB
12093
12094 - ddd
12095 - eee
12096 + EEE
12097 fff
12098 "
12099 .unindent(),
12100 );
12101}
12102
12103#[gpui::test]
12104async fn test_edits_around_expanded_insertion_hunks(
12105 executor: BackgroundExecutor,
12106 cx: &mut gpui::TestAppContext,
12107) {
12108 init_test(cx, |_| {});
12109
12110 let mut cx = EditorTestContext::new(cx).await;
12111
12112 let diff_base = r#"
12113 use some::mod1;
12114 use some::mod2;
12115
12116 const A: u32 = 42;
12117
12118 fn main() {
12119 println!("hello");
12120
12121 println!("world");
12122 }
12123 "#
12124 .unindent();
12125 executor.run_until_parked();
12126 cx.set_state(
12127 &r#"
12128 use some::mod1;
12129 use some::mod2;
12130
12131 const A: u32 = 42;
12132 const B: u32 = 42;
12133 const C: u32 = 42;
12134 ˇ
12135
12136 fn main() {
12137 println!("hello");
12138
12139 println!("world");
12140 }
12141 "#
12142 .unindent(),
12143 );
12144
12145 cx.set_diff_base(Some(&diff_base));
12146 executor.run_until_parked();
12147
12148 cx.update_editor(|editor, cx| {
12149 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12150 });
12151 executor.run_until_parked();
12152
12153 cx.assert_diff_hunks(
12154 r#"
12155 use some::mod1;
12156 use some::mod2;
12157
12158 const A: u32 = 42;
12159 + const B: u32 = 42;
12160 + const C: u32 = 42;
12161 +
12162
12163 fn main() {
12164 println!("hello");
12165
12166 println!("world");
12167 }
12168 "#
12169 .unindent(),
12170 );
12171
12172 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12173 executor.run_until_parked();
12174
12175 cx.assert_diff_hunks(
12176 r#"
12177 use some::mod1;
12178 use some::mod2;
12179
12180 const A: u32 = 42;
12181 + const B: u32 = 42;
12182 + const C: u32 = 42;
12183 + const D: u32 = 42;
12184 +
12185
12186 fn main() {
12187 println!("hello");
12188
12189 println!("world");
12190 }
12191 "#
12192 .unindent(),
12193 );
12194
12195 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12196 executor.run_until_parked();
12197
12198 cx.assert_diff_hunks(
12199 r#"
12200 use some::mod1;
12201 use some::mod2;
12202
12203 const A: u32 = 42;
12204 + const B: u32 = 42;
12205 + const C: u32 = 42;
12206 + const D: u32 = 42;
12207 + const E: u32 = 42;
12208 +
12209
12210 fn main() {
12211 println!("hello");
12212
12213 println!("world");
12214 }
12215 "#
12216 .unindent(),
12217 );
12218
12219 cx.update_editor(|editor, cx| {
12220 editor.delete_line(&DeleteLine, cx);
12221 });
12222 executor.run_until_parked();
12223
12224 cx.assert_diff_hunks(
12225 r#"
12226 use some::mod1;
12227 use some::mod2;
12228
12229 const A: u32 = 42;
12230 + const B: u32 = 42;
12231 + const C: u32 = 42;
12232 + const D: u32 = 42;
12233 + const E: u32 = 42;
12234
12235 fn main() {
12236 println!("hello");
12237
12238 println!("world");
12239 }
12240 "#
12241 .unindent(),
12242 );
12243
12244 cx.update_editor(|editor, cx| {
12245 editor.move_up(&MoveUp, cx);
12246 editor.delete_line(&DeleteLine, cx);
12247 editor.move_up(&MoveUp, cx);
12248 editor.delete_line(&DeleteLine, cx);
12249 editor.move_up(&MoveUp, cx);
12250 editor.delete_line(&DeleteLine, cx);
12251 });
12252 executor.run_until_parked();
12253 cx.assert_editor_state(
12254 &r#"
12255 use some::mod1;
12256 use some::mod2;
12257
12258 const A: u32 = 42;
12259 const B: u32 = 42;
12260 ˇ
12261 fn main() {
12262 println!("hello");
12263
12264 println!("world");
12265 }
12266 "#
12267 .unindent(),
12268 );
12269
12270 cx.assert_diff_hunks(
12271 r#"
12272 use some::mod1;
12273 use some::mod2;
12274
12275 const A: u32 = 42;
12276 + const B: u32 = 42;
12277
12278 fn main() {
12279 println!("hello");
12280
12281 println!("world");
12282 }
12283 "#
12284 .unindent(),
12285 );
12286
12287 cx.update_editor(|editor, cx| {
12288 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12289 editor.delete_line(&DeleteLine, cx);
12290 });
12291 executor.run_until_parked();
12292 cx.assert_diff_hunks(
12293 r#"
12294 use some::mod1;
12295 - use some::mod2;
12296 -
12297 - const A: u32 = 42;
12298
12299 fn main() {
12300 println!("hello");
12301
12302 println!("world");
12303 }
12304 "#
12305 .unindent(),
12306 );
12307}
12308
12309#[gpui::test]
12310async fn test_edits_around_expanded_deletion_hunks(
12311 executor: BackgroundExecutor,
12312 cx: &mut gpui::TestAppContext,
12313) {
12314 init_test(cx, |_| {});
12315
12316 let mut cx = EditorTestContext::new(cx).await;
12317
12318 let diff_base = r#"
12319 use some::mod1;
12320 use some::mod2;
12321
12322 const A: u32 = 42;
12323 const B: u32 = 42;
12324 const C: u32 = 42;
12325
12326
12327 fn main() {
12328 println!("hello");
12329
12330 println!("world");
12331 }
12332 "#
12333 .unindent();
12334 executor.run_until_parked();
12335 cx.set_state(
12336 &r#"
12337 use some::mod1;
12338 use some::mod2;
12339
12340 ˇconst B: u32 = 42;
12341 const C: u32 = 42;
12342
12343
12344 fn main() {
12345 println!("hello");
12346
12347 println!("world");
12348 }
12349 "#
12350 .unindent(),
12351 );
12352
12353 cx.set_diff_base(Some(&diff_base));
12354 executor.run_until_parked();
12355
12356 cx.update_editor(|editor, cx| {
12357 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12358 });
12359 executor.run_until_parked();
12360
12361 cx.assert_diff_hunks(
12362 r#"
12363 use some::mod1;
12364 use some::mod2;
12365
12366 - const A: u32 = 42;
12367 const B: u32 = 42;
12368 const C: u32 = 42;
12369
12370
12371 fn main() {
12372 println!("hello");
12373
12374 println!("world");
12375 }
12376 "#
12377 .unindent(),
12378 );
12379
12380 cx.update_editor(|editor, cx| {
12381 editor.delete_line(&DeleteLine, cx);
12382 });
12383 executor.run_until_parked();
12384 cx.assert_editor_state(
12385 &r#"
12386 use some::mod1;
12387 use some::mod2;
12388
12389 ˇconst C: u32 = 42;
12390
12391
12392 fn main() {
12393 println!("hello");
12394
12395 println!("world");
12396 }
12397 "#
12398 .unindent(),
12399 );
12400 cx.assert_diff_hunks(
12401 r#"
12402 use some::mod1;
12403 use some::mod2;
12404
12405 - const A: u32 = 42;
12406 - const B: u32 = 42;
12407 const C: u32 = 42;
12408
12409
12410 fn main() {
12411 println!("hello");
12412
12413 println!("world");
12414 }
12415 "#
12416 .unindent(),
12417 );
12418
12419 cx.update_editor(|editor, cx| {
12420 editor.delete_line(&DeleteLine, cx);
12421 });
12422 executor.run_until_parked();
12423 cx.assert_editor_state(
12424 &r#"
12425 use some::mod1;
12426 use some::mod2;
12427
12428 ˇ
12429
12430 fn main() {
12431 println!("hello");
12432
12433 println!("world");
12434 }
12435 "#
12436 .unindent(),
12437 );
12438 cx.assert_diff_hunks(
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 = 42;
12446
12447
12448 fn main() {
12449 println!("hello");
12450
12451 println!("world");
12452 }
12453 "#
12454 .unindent(),
12455 );
12456
12457 cx.update_editor(|editor, cx| {
12458 editor.handle_input("replacement", cx);
12459 });
12460 executor.run_until_parked();
12461 cx.assert_editor_state(
12462 &r#"
12463 use some::mod1;
12464 use some::mod2;
12465
12466 replacementˇ
12467
12468 fn main() {
12469 println!("hello");
12470
12471 println!("world");
12472 }
12473 "#
12474 .unindent(),
12475 );
12476 cx.assert_diff_hunks(
12477 r#"
12478 use some::mod1;
12479 use some::mod2;
12480
12481 - const A: u32 = 42;
12482 - const B: u32 = 42;
12483 - const C: u32 = 42;
12484 -
12485 + replacement
12486
12487 fn main() {
12488 println!("hello");
12489
12490 println!("world");
12491 }
12492 "#
12493 .unindent(),
12494 );
12495}
12496
12497#[gpui::test]
12498async fn test_edit_after_expanded_modification_hunk(
12499 executor: BackgroundExecutor,
12500 cx: &mut gpui::TestAppContext,
12501) {
12502 init_test(cx, |_| {});
12503
12504 let mut cx = EditorTestContext::new(cx).await;
12505
12506 let diff_base = r#"
12507 use some::mod1;
12508 use some::mod2;
12509
12510 const A: u32 = 42;
12511 const B: u32 = 42;
12512 const C: u32 = 42;
12513 const D: u32 = 42;
12514
12515
12516 fn main() {
12517 println!("hello");
12518
12519 println!("world");
12520 }"#
12521 .unindent();
12522
12523 cx.set_state(
12524 &r#"
12525 use some::mod1;
12526 use some::mod2;
12527
12528 const A: u32 = 42;
12529 const B: u32 = 42;
12530 const C: u32 = 43ˇ
12531 const D: u32 = 42;
12532
12533
12534 fn main() {
12535 println!("hello");
12536
12537 println!("world");
12538 }"#
12539 .unindent(),
12540 );
12541
12542 cx.set_diff_base(Some(&diff_base));
12543 executor.run_until_parked();
12544 cx.update_editor(|editor, cx| {
12545 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12546 });
12547 executor.run_until_parked();
12548
12549 cx.assert_diff_hunks(
12550 r#"
12551 use some::mod1;
12552 use some::mod2;
12553
12554 const A: u32 = 42;
12555 const B: u32 = 42;
12556 - const C: u32 = 42;
12557 + const C: u32 = 43
12558 const D: u32 = 42;
12559
12560
12561 fn main() {
12562 println!("hello");
12563
12564 println!("world");
12565 }"#
12566 .unindent(),
12567 );
12568
12569 cx.update_editor(|editor, cx| {
12570 editor.handle_input("\nnew_line\n", cx);
12571 });
12572 executor.run_until_parked();
12573
12574 cx.assert_diff_hunks(
12575 r#"
12576 use some::mod1;
12577 use some::mod2;
12578
12579 const A: u32 = 42;
12580 const B: u32 = 42;
12581 - const C: u32 = 42;
12582 + const C: u32 = 43
12583 + new_line
12584 +
12585 const D: u32 = 42;
12586
12587
12588 fn main() {
12589 println!("hello");
12590
12591 println!("world");
12592 }"#
12593 .unindent(),
12594 );
12595}
12596
12597async fn setup_indent_guides_editor(
12598 text: &str,
12599 cx: &mut gpui::TestAppContext,
12600) -> (BufferId, EditorTestContext) {
12601 init_test(cx, |_| {});
12602
12603 let mut cx = EditorTestContext::new(cx).await;
12604
12605 let buffer_id = cx.update_editor(|editor, cx| {
12606 editor.set_text(text, cx);
12607 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12608
12609 buffer_ids[0]
12610 });
12611
12612 (buffer_id, cx)
12613}
12614
12615fn assert_indent_guides(
12616 range: Range<u32>,
12617 expected: Vec<IndentGuide>,
12618 active_indices: Option<Vec<usize>>,
12619 cx: &mut EditorTestContext,
12620) {
12621 let indent_guides = cx.update_editor(|editor, cx| {
12622 let snapshot = editor.snapshot(cx).display_snapshot;
12623 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12624 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12625 true,
12626 &snapshot,
12627 cx,
12628 );
12629
12630 indent_guides.sort_by(|a, b| {
12631 a.depth.cmp(&b.depth).then(
12632 a.start_row
12633 .cmp(&b.start_row)
12634 .then(a.end_row.cmp(&b.end_row)),
12635 )
12636 });
12637 indent_guides
12638 });
12639
12640 if let Some(expected) = active_indices {
12641 let active_indices = cx.update_editor(|editor, cx| {
12642 let snapshot = editor.snapshot(cx).display_snapshot;
12643 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12644 });
12645
12646 assert_eq!(
12647 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12648 expected,
12649 "Active indent guide indices do not match"
12650 );
12651 }
12652
12653 let expected: Vec<_> = expected
12654 .into_iter()
12655 .map(|guide| MultiBufferIndentGuide {
12656 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12657 buffer: guide,
12658 })
12659 .collect();
12660
12661 assert_eq!(indent_guides, expected, "Indent guides do not match");
12662}
12663
12664fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12665 IndentGuide {
12666 buffer_id,
12667 start_row,
12668 end_row,
12669 depth,
12670 tab_size: 4,
12671 settings: IndentGuideSettings {
12672 enabled: true,
12673 line_width: 1,
12674 active_line_width: 1,
12675 ..Default::default()
12676 },
12677 }
12678}
12679
12680#[gpui::test]
12681async fn test_indent_guide_single_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 .unindent(),
12688 cx,
12689 )
12690 .await;
12691
12692 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12693}
12694
12695#[gpui::test]
12696async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12697 let (buffer_id, mut cx) = setup_indent_guides_editor(
12698 &"
12699 fn main() {
12700 let a = 1;
12701 let b = 2;
12702 }"
12703 .unindent(),
12704 cx,
12705 )
12706 .await;
12707
12708 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12709}
12710
12711#[gpui::test]
12712async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12713 let (buffer_id, mut cx) = setup_indent_guides_editor(
12714 &"
12715 fn main() {
12716 let a = 1;
12717 if a == 3 {
12718 let b = 2;
12719 } else {
12720 let c = 3;
12721 }
12722 }"
12723 .unindent(),
12724 cx,
12725 )
12726 .await;
12727
12728 assert_indent_guides(
12729 0..8,
12730 vec![
12731 indent_guide(buffer_id, 1, 6, 0),
12732 indent_guide(buffer_id, 3, 3, 1),
12733 indent_guide(buffer_id, 5, 5, 1),
12734 ],
12735 None,
12736 &mut cx,
12737 );
12738}
12739
12740#[gpui::test]
12741async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12742 let (buffer_id, mut cx) = setup_indent_guides_editor(
12743 &"
12744 fn main() {
12745 let a = 1;
12746 let b = 2;
12747 let c = 3;
12748 }"
12749 .unindent(),
12750 cx,
12751 )
12752 .await;
12753
12754 assert_indent_guides(
12755 0..5,
12756 vec![
12757 indent_guide(buffer_id, 1, 3, 0),
12758 indent_guide(buffer_id, 2, 2, 1),
12759 ],
12760 None,
12761 &mut cx,
12762 );
12763}
12764
12765#[gpui::test]
12766async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12767 let (buffer_id, mut cx) = setup_indent_guides_editor(
12768 &"
12769 fn main() {
12770 let a = 1;
12771
12772 let c = 3;
12773 }"
12774 .unindent(),
12775 cx,
12776 )
12777 .await;
12778
12779 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12780}
12781
12782#[gpui::test]
12783async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12784 let (buffer_id, mut cx) = setup_indent_guides_editor(
12785 &"
12786 fn main() {
12787 let a = 1;
12788
12789 let c = 3;
12790
12791 if a == 3 {
12792 let b = 2;
12793 } else {
12794 let c = 3;
12795 }
12796 }"
12797 .unindent(),
12798 cx,
12799 )
12800 .await;
12801
12802 assert_indent_guides(
12803 0..11,
12804 vec![
12805 indent_guide(buffer_id, 1, 9, 0),
12806 indent_guide(buffer_id, 6, 6, 1),
12807 indent_guide(buffer_id, 8, 8, 1),
12808 ],
12809 None,
12810 &mut cx,
12811 );
12812}
12813
12814#[gpui::test]
12815async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12816 let (buffer_id, mut cx) = setup_indent_guides_editor(
12817 &"
12818 fn main() {
12819 let a = 1;
12820
12821 let c = 3;
12822
12823 if a == 3 {
12824 let b = 2;
12825 } else {
12826 let c = 3;
12827 }
12828 }"
12829 .unindent(),
12830 cx,
12831 )
12832 .await;
12833
12834 assert_indent_guides(
12835 1..11,
12836 vec![
12837 indent_guide(buffer_id, 1, 9, 0),
12838 indent_guide(buffer_id, 6, 6, 1),
12839 indent_guide(buffer_id, 8, 8, 1),
12840 ],
12841 None,
12842 &mut cx,
12843 );
12844}
12845
12846#[gpui::test]
12847async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12848 let (buffer_id, mut cx) = setup_indent_guides_editor(
12849 &"
12850 fn main() {
12851 let a = 1;
12852
12853 let c = 3;
12854
12855 if a == 3 {
12856 let b = 2;
12857 } else {
12858 let c = 3;
12859 }
12860 }"
12861 .unindent(),
12862 cx,
12863 )
12864 .await;
12865
12866 assert_indent_guides(
12867 1..10,
12868 vec![
12869 indent_guide(buffer_id, 1, 9, 0),
12870 indent_guide(buffer_id, 6, 6, 1),
12871 indent_guide(buffer_id, 8, 8, 1),
12872 ],
12873 None,
12874 &mut cx,
12875 );
12876}
12877
12878#[gpui::test]
12879async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12880 let (buffer_id, mut cx) = setup_indent_guides_editor(
12881 &"
12882 block1
12883 block2
12884 block3
12885 block4
12886 block2
12887 block1
12888 block1"
12889 .unindent(),
12890 cx,
12891 )
12892 .await;
12893
12894 assert_indent_guides(
12895 1..10,
12896 vec![
12897 indent_guide(buffer_id, 1, 4, 0),
12898 indent_guide(buffer_id, 2, 3, 1),
12899 indent_guide(buffer_id, 3, 3, 2),
12900 ],
12901 None,
12902 &mut cx,
12903 );
12904}
12905
12906#[gpui::test]
12907async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12908 let (buffer_id, mut cx) = setup_indent_guides_editor(
12909 &"
12910 block1
12911 block2
12912 block3
12913
12914 block1
12915 block1"
12916 .unindent(),
12917 cx,
12918 )
12919 .await;
12920
12921 assert_indent_guides(
12922 0..6,
12923 vec![
12924 indent_guide(buffer_id, 1, 2, 0),
12925 indent_guide(buffer_id, 2, 2, 1),
12926 ],
12927 None,
12928 &mut cx,
12929 );
12930}
12931
12932#[gpui::test]
12933async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12934 let (buffer_id, mut cx) = setup_indent_guides_editor(
12935 &"
12936 block1
12937
12938
12939
12940 block2
12941 "
12942 .unindent(),
12943 cx,
12944 )
12945 .await;
12946
12947 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12948}
12949
12950#[gpui::test]
12951async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12952 let (buffer_id, mut cx) = setup_indent_guides_editor(
12953 &"
12954 def a:
12955 \tb = 3
12956 \tif True:
12957 \t\tc = 4
12958 \t\td = 5
12959 \tprint(b)
12960 "
12961 .unindent(),
12962 cx,
12963 )
12964 .await;
12965
12966 assert_indent_guides(
12967 0..6,
12968 vec![
12969 indent_guide(buffer_id, 1, 6, 0),
12970 indent_guide(buffer_id, 3, 4, 1),
12971 ],
12972 None,
12973 &mut cx,
12974 );
12975}
12976
12977#[gpui::test]
12978async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12979 let (buffer_id, mut cx) = setup_indent_guides_editor(
12980 &"
12981 fn main() {
12982 let a = 1;
12983 }"
12984 .unindent(),
12985 cx,
12986 )
12987 .await;
12988
12989 cx.update_editor(|editor, cx| {
12990 editor.change_selections(None, cx, |s| {
12991 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12992 });
12993 });
12994
12995 assert_indent_guides(
12996 0..3,
12997 vec![indent_guide(buffer_id, 1, 1, 0)],
12998 Some(vec![0]),
12999 &mut cx,
13000 );
13001}
13002
13003#[gpui::test]
13004async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13005 let (buffer_id, mut cx) = setup_indent_guides_editor(
13006 &"
13007 fn main() {
13008 if 1 == 2 {
13009 let a = 1;
13010 }
13011 }"
13012 .unindent(),
13013 cx,
13014 )
13015 .await;
13016
13017 cx.update_editor(|editor, cx| {
13018 editor.change_selections(None, cx, |s| {
13019 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13020 });
13021 });
13022
13023 assert_indent_guides(
13024 0..4,
13025 vec![
13026 indent_guide(buffer_id, 1, 3, 0),
13027 indent_guide(buffer_id, 2, 2, 1),
13028 ],
13029 Some(vec![1]),
13030 &mut cx,
13031 );
13032
13033 cx.update_editor(|editor, cx| {
13034 editor.change_selections(None, cx, |s| {
13035 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13036 });
13037 });
13038
13039 assert_indent_guides(
13040 0..4,
13041 vec![
13042 indent_guide(buffer_id, 1, 3, 0),
13043 indent_guide(buffer_id, 2, 2, 1),
13044 ],
13045 Some(vec![1]),
13046 &mut cx,
13047 );
13048
13049 cx.update_editor(|editor, cx| {
13050 editor.change_selections(None, cx, |s| {
13051 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13052 });
13053 });
13054
13055 assert_indent_guides(
13056 0..4,
13057 vec![
13058 indent_guide(buffer_id, 1, 3, 0),
13059 indent_guide(buffer_id, 2, 2, 1),
13060 ],
13061 Some(vec![0]),
13062 &mut cx,
13063 );
13064}
13065
13066#[gpui::test]
13067async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13068 let (buffer_id, mut cx) = setup_indent_guides_editor(
13069 &"
13070 fn main() {
13071 let a = 1;
13072
13073 let b = 2;
13074 }"
13075 .unindent(),
13076 cx,
13077 )
13078 .await;
13079
13080 cx.update_editor(|editor, cx| {
13081 editor.change_selections(None, cx, |s| {
13082 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13083 });
13084 });
13085
13086 assert_indent_guides(
13087 0..5,
13088 vec![indent_guide(buffer_id, 1, 3, 0)],
13089 Some(vec![0]),
13090 &mut cx,
13091 );
13092}
13093
13094#[gpui::test]
13095async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13096 let (buffer_id, mut cx) = setup_indent_guides_editor(
13097 &"
13098 def m:
13099 a = 1
13100 pass"
13101 .unindent(),
13102 cx,
13103 )
13104 .await;
13105
13106 cx.update_editor(|editor, cx| {
13107 editor.change_selections(None, cx, |s| {
13108 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13109 });
13110 });
13111
13112 assert_indent_guides(
13113 0..3,
13114 vec![indent_guide(buffer_id, 1, 2, 0)],
13115 Some(vec![0]),
13116 &mut cx,
13117 );
13118}
13119
13120#[gpui::test]
13121fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13122 init_test(cx, |_| {});
13123
13124 let editor = cx.add_window(|cx| {
13125 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13126 build_editor(buffer, cx)
13127 });
13128
13129 let render_args = Arc::new(Mutex::new(None));
13130 let snapshot = editor
13131 .update(cx, |editor, cx| {
13132 let snapshot = editor.buffer().read(cx).snapshot(cx);
13133 let range =
13134 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13135
13136 struct RenderArgs {
13137 row: MultiBufferRow,
13138 folded: bool,
13139 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13140 }
13141
13142 let crease = Crease::new(
13143 range,
13144 FoldPlaceholder::test(),
13145 {
13146 let toggle_callback = render_args.clone();
13147 move |row, folded, callback, _cx| {
13148 *toggle_callback.lock() = Some(RenderArgs {
13149 row,
13150 folded,
13151 callback,
13152 });
13153 div()
13154 }
13155 },
13156 |_row, _folded, _cx| div(),
13157 );
13158
13159 editor.insert_creases(Some(crease), cx);
13160 let snapshot = editor.snapshot(cx);
13161 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13162 snapshot
13163 })
13164 .unwrap();
13165
13166 let render_args = render_args.lock().take().unwrap();
13167 assert_eq!(render_args.row, MultiBufferRow(1));
13168 assert!(!render_args.folded);
13169 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13170
13171 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13172 .unwrap();
13173 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13174 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13175
13176 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13177 .unwrap();
13178 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13179 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13180}
13181
13182#[gpui::test]
13183async fn test_input_text(cx: &mut gpui::TestAppContext) {
13184 init_test(cx, |_| {});
13185 let mut cx = EditorTestContext::new(cx).await;
13186
13187 cx.set_state(
13188 &r#"ˇone
13189 two
13190
13191 three
13192 fourˇ
13193 five
13194
13195 siˇx"#
13196 .unindent(),
13197 );
13198
13199 cx.dispatch_action(HandleInput(String::new()));
13200 cx.assert_editor_state(
13201 &r#"ˇone
13202 two
13203
13204 three
13205 fourˇ
13206 five
13207
13208 siˇx"#
13209 .unindent(),
13210 );
13211
13212 cx.dispatch_action(HandleInput("AAAA".to_string()));
13213 cx.assert_editor_state(
13214 &r#"AAAAˇone
13215 two
13216
13217 three
13218 fourAAAAˇ
13219 five
13220
13221 siAAAAˇx"#
13222 .unindent(),
13223 );
13224}
13225
13226#[gpui::test]
13227async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13228 init_test(cx, |_| {});
13229
13230 let mut cx = EditorTestContext::new(cx).await;
13231 cx.set_state(
13232 r#"let foo = 1;
13233let foo = 2;
13234let foo = 3;
13235let fooˇ = 4;
13236let foo = 5;
13237let foo = 6;
13238let foo = 7;
13239let foo = 8;
13240let foo = 9;
13241let foo = 10;
13242let foo = 11;
13243let foo = 12;
13244let foo = 13;
13245let foo = 14;
13246let foo = 15;"#,
13247 );
13248
13249 cx.update_editor(|e, cx| {
13250 assert_eq!(
13251 e.next_scroll_position,
13252 NextScrollCursorCenterTopBottom::Center,
13253 "Default next scroll direction is center",
13254 );
13255
13256 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13257 assert_eq!(
13258 e.next_scroll_position,
13259 NextScrollCursorCenterTopBottom::Top,
13260 "After center, next scroll direction should be top",
13261 );
13262
13263 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13264 assert_eq!(
13265 e.next_scroll_position,
13266 NextScrollCursorCenterTopBottom::Bottom,
13267 "After top, next scroll direction should be bottom",
13268 );
13269
13270 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13271 assert_eq!(
13272 e.next_scroll_position,
13273 NextScrollCursorCenterTopBottom::Center,
13274 "After bottom, scrolling should start over",
13275 );
13276
13277 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13278 assert_eq!(
13279 e.next_scroll_position,
13280 NextScrollCursorCenterTopBottom::Top,
13281 "Scrolling continues if retriggered fast enough"
13282 );
13283 });
13284
13285 cx.executor()
13286 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13287 cx.executor().run_until_parked();
13288 cx.update_editor(|e, _| {
13289 assert_eq!(
13290 e.next_scroll_position,
13291 NextScrollCursorCenterTopBottom::Center,
13292 "If scrolling is not triggered fast enough, it should reset"
13293 );
13294 });
13295}
13296
13297#[gpui::test]
13298async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13299 init_test(cx, |_| {});
13300 let mut cx = EditorLspTestContext::new_rust(
13301 lsp::ServerCapabilities {
13302 definition_provider: Some(lsp::OneOf::Left(true)),
13303 references_provider: Some(lsp::OneOf::Left(true)),
13304 ..lsp::ServerCapabilities::default()
13305 },
13306 cx,
13307 )
13308 .await;
13309
13310 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13311 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13312 move |params, _| async move {
13313 if empty_go_to_definition {
13314 Ok(None)
13315 } else {
13316 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13317 uri: params.text_document_position_params.text_document.uri,
13318 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13319 })))
13320 }
13321 },
13322 );
13323 let references =
13324 cx.lsp
13325 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13326 Ok(Some(vec![lsp::Location {
13327 uri: params.text_document_position.text_document.uri,
13328 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13329 }]))
13330 });
13331 (go_to_definition, references)
13332 };
13333
13334 cx.set_state(
13335 &r#"fn one() {
13336 let mut a = ˇtwo();
13337 }
13338
13339 fn two() {}"#
13340 .unindent(),
13341 );
13342 set_up_lsp_handlers(false, &mut cx);
13343 let navigated = cx
13344 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13345 .await
13346 .expect("Failed to navigate to definition");
13347 assert_eq!(
13348 navigated,
13349 Navigated::Yes,
13350 "Should have navigated to definition from the GetDefinition response"
13351 );
13352 cx.assert_editor_state(
13353 &r#"fn one() {
13354 let mut a = two();
13355 }
13356
13357 fn «twoˇ»() {}"#
13358 .unindent(),
13359 );
13360
13361 let editors = cx.update_workspace(|workspace, cx| {
13362 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13363 });
13364 cx.update_editor(|_, test_editor_cx| {
13365 assert_eq!(
13366 editors.len(),
13367 1,
13368 "Initially, only one, test, editor should be open in the workspace"
13369 );
13370 assert_eq!(
13371 test_editor_cx.view(),
13372 editors.last().expect("Asserted len is 1")
13373 );
13374 });
13375
13376 set_up_lsp_handlers(true, &mut cx);
13377 let navigated = cx
13378 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13379 .await
13380 .expect("Failed to navigate to lookup references");
13381 assert_eq!(
13382 navigated,
13383 Navigated::Yes,
13384 "Should have navigated to references as a fallback after empty GoToDefinition response"
13385 );
13386 // We should not change the selections in the existing file,
13387 // if opening another milti buffer with the references
13388 cx.assert_editor_state(
13389 &r#"fn one() {
13390 let mut a = two();
13391 }
13392
13393 fn «twoˇ»() {}"#
13394 .unindent(),
13395 );
13396 let editors = cx.update_workspace(|workspace, cx| {
13397 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13398 });
13399 cx.update_editor(|_, test_editor_cx| {
13400 assert_eq!(
13401 editors.len(),
13402 2,
13403 "After falling back to references search, we open a new editor with the results"
13404 );
13405 let references_fallback_text = editors
13406 .into_iter()
13407 .find(|new_editor| new_editor != test_editor_cx.view())
13408 .expect("Should have one non-test editor now")
13409 .read(test_editor_cx)
13410 .text(test_editor_cx);
13411 assert_eq!(
13412 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13413 "Should use the range from the references response and not the GoToDefinition one"
13414 );
13415 });
13416}
13417
13418#[gpui::test]
13419async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13420 init_test(cx, |_| {});
13421
13422 let language = Arc::new(Language::new(
13423 LanguageConfig::default(),
13424 Some(tree_sitter_rust::LANGUAGE.into()),
13425 ));
13426
13427 let text = r#"
13428 #[cfg(test)]
13429 mod tests() {
13430 #[test]
13431 fn runnable_1() {
13432 let a = 1;
13433 }
13434
13435 #[test]
13436 fn runnable_2() {
13437 let a = 1;
13438 let b = 2;
13439 }
13440 }
13441 "#
13442 .unindent();
13443
13444 let fs = FakeFs::new(cx.executor());
13445 fs.insert_file("/file.rs", Default::default()).await;
13446
13447 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13448 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13449 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13450 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13451 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13452
13453 let editor = cx.new_view(|cx| {
13454 Editor::new(
13455 EditorMode::Full,
13456 multi_buffer,
13457 Some(project.clone()),
13458 true,
13459 cx,
13460 )
13461 });
13462
13463 editor.update(cx, |editor, cx| {
13464 editor.tasks.insert(
13465 (buffer.read(cx).remote_id(), 3),
13466 RunnableTasks {
13467 templates: vec![],
13468 offset: MultiBufferOffset(43),
13469 column: 0,
13470 extra_variables: HashMap::default(),
13471 context_range: BufferOffset(43)..BufferOffset(85),
13472 },
13473 );
13474 editor.tasks.insert(
13475 (buffer.read(cx).remote_id(), 8),
13476 RunnableTasks {
13477 templates: vec![],
13478 offset: MultiBufferOffset(86),
13479 column: 0,
13480 extra_variables: HashMap::default(),
13481 context_range: BufferOffset(86)..BufferOffset(191),
13482 },
13483 );
13484
13485 // Test finding task when cursor is inside function body
13486 editor.change_selections(None, cx, |s| {
13487 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13488 });
13489 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13490 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13491
13492 // Test finding task when cursor is on function name
13493 editor.change_selections(None, cx, |s| {
13494 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13495 });
13496 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13497 assert_eq!(row, 8, "Should find task when cursor is on function name");
13498 });
13499}
13500
13501fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13502 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13503 point..point
13504}
13505
13506fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13507 let (text, ranges) = marked_text_ranges(marked_text, true);
13508 assert_eq!(view.text(cx), text);
13509 assert_eq!(
13510 view.selections.ranges(cx),
13511 ranges,
13512 "Assert selections are {}",
13513 marked_text
13514 );
13515}
13516
13517pub fn handle_signature_help_request(
13518 cx: &mut EditorLspTestContext,
13519 mocked_response: lsp::SignatureHelp,
13520) -> impl Future<Output = ()> {
13521 let mut request =
13522 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13523 let mocked_response = mocked_response.clone();
13524 async move { Ok(Some(mocked_response)) }
13525 });
13526
13527 async move {
13528 request.next().await;
13529 }
13530}
13531
13532/// Handle completion request passing a marked string specifying where the completion
13533/// should be triggered from using '|' character, what range should be replaced, and what completions
13534/// should be returned using '<' and '>' to delimit the range
13535pub fn handle_completion_request(
13536 cx: &mut EditorLspTestContext,
13537 marked_string: &str,
13538 completions: Vec<&'static str>,
13539 counter: Arc<AtomicUsize>,
13540) -> impl Future<Output = ()> {
13541 let complete_from_marker: TextRangeMarker = '|'.into();
13542 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13543 let (_, mut marked_ranges) = marked_text_ranges_by(
13544 marked_string,
13545 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13546 );
13547
13548 let complete_from_position =
13549 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13550 let replace_range =
13551 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13552
13553 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13554 let completions = completions.clone();
13555 counter.fetch_add(1, atomic::Ordering::Release);
13556 async move {
13557 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13558 assert_eq!(
13559 params.text_document_position.position,
13560 complete_from_position
13561 );
13562 Ok(Some(lsp::CompletionResponse::Array(
13563 completions
13564 .iter()
13565 .map(|completion_text| lsp::CompletionItem {
13566 label: completion_text.to_string(),
13567 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13568 range: replace_range,
13569 new_text: completion_text.to_string(),
13570 })),
13571 ..Default::default()
13572 })
13573 .collect(),
13574 )))
13575 }
13576 });
13577
13578 async move {
13579 request.next().await;
13580 }
13581}
13582
13583fn handle_resolve_completion_request(
13584 cx: &mut EditorLspTestContext,
13585 edits: Option<Vec<(&'static str, &'static str)>>,
13586) -> impl Future<Output = ()> {
13587 let edits = edits.map(|edits| {
13588 edits
13589 .iter()
13590 .map(|(marked_string, new_text)| {
13591 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13592 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13593 lsp::TextEdit::new(replace_range, new_text.to_string())
13594 })
13595 .collect::<Vec<_>>()
13596 });
13597
13598 let mut request =
13599 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13600 let edits = edits.clone();
13601 async move {
13602 Ok(lsp::CompletionItem {
13603 additional_text_edits: edits,
13604 ..Default::default()
13605 })
13606 }
13607 });
13608
13609 async move {
13610 request.next().await;
13611 }
13612}
13613
13614pub(crate) fn update_test_language_settings(
13615 cx: &mut TestAppContext,
13616 f: impl Fn(&mut AllLanguageSettingsContent),
13617) {
13618 cx.update(|cx| {
13619 SettingsStore::update_global(cx, |store, cx| {
13620 store.update_user_settings::<AllLanguageSettings>(cx, f);
13621 });
13622 });
13623}
13624
13625pub(crate) fn update_test_project_settings(
13626 cx: &mut TestAppContext,
13627 f: impl Fn(&mut ProjectSettings),
13628) {
13629 cx.update(|cx| {
13630 SettingsStore::update_global(cx, |store, cx| {
13631 store.update_user_settings::<ProjectSettings>(cx, f);
13632 });
13633 });
13634}
13635
13636pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13637 cx.update(|cx| {
13638 assets::Assets.load_test_fonts(cx);
13639 let store = SettingsStore::test(cx);
13640 cx.set_global(store);
13641 theme::init(theme::LoadThemes::JustBase, cx);
13642 release_channel::init(SemanticVersion::default(), cx);
13643 client::init_settings(cx);
13644 language::init(cx);
13645 Project::init_settings(cx);
13646 workspace::init_settings(cx);
13647 crate::init(cx);
13648 });
13649
13650 update_test_language_settings(cx, f);
13651}
13652
13653pub(crate) fn rust_lang() -> Arc<Language> {
13654 Arc::new(Language::new(
13655 LanguageConfig {
13656 name: "Rust".into(),
13657 matcher: LanguageMatcher {
13658 path_suffixes: vec!["rs".to_string()],
13659 ..Default::default()
13660 },
13661 ..Default::default()
13662 },
13663 Some(tree_sitter_rust::LANGUAGE.into()),
13664 ))
13665}
13666
13667#[track_caller]
13668fn assert_hunk_revert(
13669 not_reverted_text_with_selections: &str,
13670 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13671 expected_reverted_text_with_selections: &str,
13672 base_text: &str,
13673 cx: &mut EditorLspTestContext,
13674) {
13675 cx.set_state(not_reverted_text_with_selections);
13676 cx.update_editor(|editor, cx| {
13677 editor
13678 .buffer()
13679 .read(cx)
13680 .as_singleton()
13681 .unwrap()
13682 .update(cx, |buffer, cx| {
13683 buffer.set_diff_base(Some(base_text.into()), cx);
13684 });
13685 });
13686 cx.executor().run_until_parked();
13687
13688 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13689 let snapshot = editor.buffer().read(cx).snapshot(cx);
13690 let reverted_hunk_statuses = snapshot
13691 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13692 .map(|hunk| hunk_status(&hunk))
13693 .collect::<Vec<_>>();
13694
13695 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13696 reverted_hunk_statuses
13697 });
13698 cx.executor().run_until_parked();
13699 cx.assert_editor_state(expected_reverted_text_with_selections);
13700 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13701}