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