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, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
13 WindowBounds, 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::{buffer_store::BufferChangeSet, 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::{self, AtomicBool, AtomicUsize};
35use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
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
1402 // moving above start of document should move selection to start of document,
1403 // but the next move down should still be at the original goal_x
1404 view.move_up(&MoveUp, cx);
1405 assert_eq!(
1406 view.selections.display_ranges(cx),
1407 &[empty_range(0, "".len())]
1408 );
1409
1410 view.move_down(&MoveDown, cx);
1411 assert_eq!(
1412 view.selections.display_ranges(cx),
1413 &[empty_range(1, "abcd".len())]
1414 );
1415
1416 view.move_down(&MoveDown, cx);
1417 assert_eq!(
1418 view.selections.display_ranges(cx),
1419 &[empty_range(2, "αβγ".len())]
1420 );
1421
1422 view.move_down(&MoveDown, cx);
1423 assert_eq!(
1424 view.selections.display_ranges(cx),
1425 &[empty_range(3, "abcd".len())]
1426 );
1427
1428 view.move_down(&MoveDown, cx);
1429 assert_eq!(
1430 view.selections.display_ranges(cx),
1431 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1432 );
1433
1434 // moving past end of document should not change goal_x
1435 view.move_down(&MoveDown, cx);
1436 assert_eq!(
1437 view.selections.display_ranges(cx),
1438 &[empty_range(5, "".len())]
1439 );
1440
1441 view.move_down(&MoveDown, cx);
1442 assert_eq!(
1443 view.selections.display_ranges(cx),
1444 &[empty_range(5, "".len())]
1445 );
1446
1447 view.move_up(&MoveUp, cx);
1448 assert_eq!(
1449 view.selections.display_ranges(cx),
1450 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1451 );
1452
1453 view.move_up(&MoveUp, cx);
1454 assert_eq!(
1455 view.selections.display_ranges(cx),
1456 &[empty_range(3, "abcd".len())]
1457 );
1458
1459 view.move_up(&MoveUp, cx);
1460 assert_eq!(
1461 view.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464 });
1465}
1466
1467#[gpui::test]
1468fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1469 init_test(cx, |_| {});
1470 let move_to_beg = MoveToBeginningOfLine {
1471 stop_at_soft_wraps: true,
1472 };
1473
1474 let move_to_end = MoveToEndOfLine {
1475 stop_at_soft_wraps: true,
1476 };
1477
1478 let view = cx.add_window(|cx| {
1479 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1480 build_editor(buffer, cx)
1481 });
1482 _ = view.update(cx, |view, cx| {
1483 view.change_selections(None, cx, |s| {
1484 s.select_display_ranges([
1485 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1486 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1487 ]);
1488 });
1489 });
1490
1491 _ = view.update(cx, |view, cx| {
1492 view.move_to_beginning_of_line(&move_to_beg, cx);
1493 assert_eq!(
1494 view.selections.display_ranges(cx),
1495 &[
1496 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1497 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1498 ]
1499 );
1500 });
1501
1502 _ = view.update(cx, |view, cx| {
1503 view.move_to_beginning_of_line(&move_to_beg, cx);
1504 assert_eq!(
1505 view.selections.display_ranges(cx),
1506 &[
1507 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1508 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1509 ]
1510 );
1511 });
1512
1513 _ = view.update(cx, |view, cx| {
1514 view.move_to_beginning_of_line(&move_to_beg, cx);
1515 assert_eq!(
1516 view.selections.display_ranges(cx),
1517 &[
1518 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1519 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1520 ]
1521 );
1522 });
1523
1524 _ = view.update(cx, |view, cx| {
1525 view.move_to_end_of_line(&move_to_end, cx);
1526 assert_eq!(
1527 view.selections.display_ranges(cx),
1528 &[
1529 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1530 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1531 ]
1532 );
1533 });
1534
1535 // Moving to the end of line again is a no-op.
1536 _ = view.update(cx, |view, cx| {
1537 view.move_to_end_of_line(&move_to_end, cx);
1538 assert_eq!(
1539 view.selections.display_ranges(cx),
1540 &[
1541 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1542 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1543 ]
1544 );
1545 });
1546
1547 _ = view.update(cx, |view, cx| {
1548 view.move_left(&MoveLeft, cx);
1549 view.select_to_beginning_of_line(
1550 &SelectToBeginningOfLine {
1551 stop_at_soft_wraps: true,
1552 },
1553 cx,
1554 );
1555 assert_eq!(
1556 view.selections.display_ranges(cx),
1557 &[
1558 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1559 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1560 ]
1561 );
1562 });
1563
1564 _ = view.update(cx, |view, cx| {
1565 view.select_to_beginning_of_line(
1566 &SelectToBeginningOfLine {
1567 stop_at_soft_wraps: true,
1568 },
1569 cx,
1570 );
1571 assert_eq!(
1572 view.selections.display_ranges(cx),
1573 &[
1574 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.select_to_beginning_of_line(
1582 &SelectToBeginningOfLine {
1583 stop_at_soft_wraps: true,
1584 },
1585 cx,
1586 );
1587 assert_eq!(
1588 view.selections.display_ranges(cx),
1589 &[
1590 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1591 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1592 ]
1593 );
1594 });
1595
1596 _ = view.update(cx, |view, cx| {
1597 view.select_to_end_of_line(
1598 &SelectToEndOfLine {
1599 stop_at_soft_wraps: true,
1600 },
1601 cx,
1602 );
1603 assert_eq!(
1604 view.selections.display_ranges(cx),
1605 &[
1606 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1607 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1608 ]
1609 );
1610 });
1611
1612 _ = view.update(cx, |view, cx| {
1613 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1614 assert_eq!(view.display_text(cx), "ab\n de");
1615 assert_eq!(
1616 view.selections.display_ranges(cx),
1617 &[
1618 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1619 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1620 ]
1621 );
1622 });
1623
1624 _ = view.update(cx, |view, cx| {
1625 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1626 assert_eq!(view.display_text(cx), "\n");
1627 assert_eq!(
1628 view.selections.display_ranges(cx),
1629 &[
1630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1631 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1632 ]
1633 );
1634 });
1635}
1636
1637#[gpui::test]
1638fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1639 init_test(cx, |_| {});
1640 let move_to_beg = MoveToBeginningOfLine {
1641 stop_at_soft_wraps: false,
1642 };
1643
1644 let move_to_end = MoveToEndOfLine {
1645 stop_at_soft_wraps: false,
1646 };
1647
1648 let view = cx.add_window(|cx| {
1649 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1650 build_editor(buffer, cx)
1651 });
1652
1653 _ = view.update(cx, |view, cx| {
1654 view.set_wrap_width(Some(140.0.into()), cx);
1655
1656 // We expect the following lines after wrapping
1657 // ```
1658 // thequickbrownfox
1659 // jumpedoverthelazydo
1660 // gs
1661 // ```
1662 // The final `gs` was soft-wrapped onto a new line.
1663 assert_eq!(
1664 "thequickbrownfox\njumpedoverthelaz\nydogs",
1665 view.display_text(cx),
1666 );
1667
1668 // First, let's assert behavior on the first line, that was not soft-wrapped.
1669 // Start the cursor at the `k` on the first line
1670 view.change_selections(None, cx, |s| {
1671 s.select_display_ranges([
1672 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1673 ]);
1674 });
1675
1676 // Moving to the beginning of the line should put us at the beginning of the line.
1677 view.move_to_beginning_of_line(&move_to_beg, cx);
1678 assert_eq!(
1679 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1680 view.selections.display_ranges(cx)
1681 );
1682
1683 // Moving to the end of the line should put us at the end of the line.
1684 view.move_to_end_of_line(&move_to_end, cx);
1685 assert_eq!(
1686 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1687 view.selections.display_ranges(cx)
1688 );
1689
1690 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1691 // Start the cursor at the last line (`y` that was wrapped to a new line)
1692 view.change_selections(None, cx, |s| {
1693 s.select_display_ranges([
1694 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1695 ]);
1696 });
1697
1698 // Moving to the beginning of the line should put us at the start of the second line of
1699 // display text, i.e., the `j`.
1700 view.move_to_beginning_of_line(&move_to_beg, cx);
1701 assert_eq!(
1702 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1703 view.selections.display_ranges(cx)
1704 );
1705
1706 // Moving to the beginning of the line again should be a no-op.
1707 view.move_to_beginning_of_line(&move_to_beg, cx);
1708 assert_eq!(
1709 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1710 view.selections.display_ranges(cx)
1711 );
1712
1713 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1714 // next display line.
1715 view.move_to_end_of_line(&move_to_end, cx);
1716 assert_eq!(
1717 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1718 view.selections.display_ranges(cx)
1719 );
1720
1721 // Moving to the end of the line again should be a no-op.
1722 view.move_to_end_of_line(&move_to_end, cx);
1723 assert_eq!(
1724 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1725 view.selections.display_ranges(cx)
1726 );
1727 });
1728}
1729
1730#[gpui::test]
1731fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1732 init_test(cx, |_| {});
1733
1734 let view = cx.add_window(|cx| {
1735 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1736 build_editor(buffer, cx)
1737 });
1738 _ = view.update(cx, |view, cx| {
1739 view.change_selections(None, cx, |s| {
1740 s.select_display_ranges([
1741 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1742 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1743 ])
1744 });
1745
1746 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1747 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1748
1749 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1750 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1751
1752 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1753 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1754
1755 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1756 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1757
1758 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1759 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1760
1761 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1762 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1763
1764 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1765 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1766
1767 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1768 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1769
1770 view.move_right(&MoveRight, cx);
1771 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1772 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1773
1774 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1775 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1776
1777 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1778 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1779 });
1780}
1781
1782#[gpui::test]
1783fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1784 init_test(cx, |_| {});
1785
1786 let view = cx.add_window(|cx| {
1787 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1788 build_editor(buffer, cx)
1789 });
1790
1791 _ = view.update(cx, |view, cx| {
1792 view.set_wrap_width(Some(140.0.into()), cx);
1793 assert_eq!(
1794 view.display_text(cx),
1795 "use one::{\n two::three::\n four::five\n};"
1796 );
1797
1798 view.change_selections(None, cx, |s| {
1799 s.select_display_ranges([
1800 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1801 ]);
1802 });
1803
1804 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1805 assert_eq!(
1806 view.selections.display_ranges(cx),
1807 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1808 );
1809
1810 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1811 assert_eq!(
1812 view.selections.display_ranges(cx),
1813 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1814 );
1815
1816 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1817 assert_eq!(
1818 view.selections.display_ranges(cx),
1819 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1820 );
1821
1822 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1823 assert_eq!(
1824 view.selections.display_ranges(cx),
1825 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1826 );
1827
1828 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1829 assert_eq!(
1830 view.selections.display_ranges(cx),
1831 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1832 );
1833
1834 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1835 assert_eq!(
1836 view.selections.display_ranges(cx),
1837 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1838 );
1839 });
1840}
1841
1842#[gpui::test]
1843async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1844 init_test(cx, |_| {});
1845 let mut cx = EditorTestContext::new(cx).await;
1846
1847 let line_height = cx.editor(|editor, cx| {
1848 editor
1849 .style()
1850 .unwrap()
1851 .text
1852 .line_height_in_pixels(cx.rem_size())
1853 });
1854 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1855
1856 cx.set_state(
1857 &r#"ˇone
1858 two
1859
1860 three
1861 fourˇ
1862 five
1863
1864 six"#
1865 .unindent(),
1866 );
1867
1868 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1869 cx.assert_editor_state(
1870 &r#"one
1871 two
1872 ˇ
1873 three
1874 four
1875 five
1876 ˇ
1877 six"#
1878 .unindent(),
1879 );
1880
1881 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1882 cx.assert_editor_state(
1883 &r#"one
1884 two
1885
1886 three
1887 four
1888 five
1889 ˇ
1890 sixˇ"#
1891 .unindent(),
1892 );
1893
1894 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1895 cx.assert_editor_state(
1896 &r#"one
1897 two
1898
1899 three
1900 four
1901 five
1902
1903 sixˇ"#
1904 .unindent(),
1905 );
1906
1907 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1908 cx.assert_editor_state(
1909 &r#"one
1910 two
1911
1912 three
1913 four
1914 five
1915 ˇ
1916 six"#
1917 .unindent(),
1918 );
1919
1920 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1921 cx.assert_editor_state(
1922 &r#"one
1923 two
1924 ˇ
1925 three
1926 four
1927 five
1928
1929 six"#
1930 .unindent(),
1931 );
1932
1933 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1934 cx.assert_editor_state(
1935 &r#"ˇone
1936 two
1937
1938 three
1939 four
1940 five
1941
1942 six"#
1943 .unindent(),
1944 );
1945}
1946
1947#[gpui::test]
1948async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1949 init_test(cx, |_| {});
1950 let mut cx = EditorTestContext::new(cx).await;
1951 let line_height = cx.editor(|editor, cx| {
1952 editor
1953 .style()
1954 .unwrap()
1955 .text
1956 .line_height_in_pixels(cx.rem_size())
1957 });
1958 let window = cx.window;
1959 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1960
1961 cx.set_state(
1962 r#"ˇone
1963 two
1964 three
1965 four
1966 five
1967 six
1968 seven
1969 eight
1970 nine
1971 ten
1972 "#,
1973 );
1974
1975 cx.update_editor(|editor, cx| {
1976 assert_eq!(
1977 editor.snapshot(cx).scroll_position(),
1978 gpui::Point::new(0., 0.)
1979 );
1980 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1981 assert_eq!(
1982 editor.snapshot(cx).scroll_position(),
1983 gpui::Point::new(0., 3.)
1984 );
1985 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1986 assert_eq!(
1987 editor.snapshot(cx).scroll_position(),
1988 gpui::Point::new(0., 6.)
1989 );
1990 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1991 assert_eq!(
1992 editor.snapshot(cx).scroll_position(),
1993 gpui::Point::new(0., 3.)
1994 );
1995
1996 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1997 assert_eq!(
1998 editor.snapshot(cx).scroll_position(),
1999 gpui::Point::new(0., 1.)
2000 );
2001 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2002 assert_eq!(
2003 editor.snapshot(cx).scroll_position(),
2004 gpui::Point::new(0., 3.)
2005 );
2006 });
2007}
2008
2009#[gpui::test]
2010async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2011 init_test(cx, |_| {});
2012 let mut cx = EditorTestContext::new(cx).await;
2013
2014 let line_height = cx.update_editor(|editor, cx| {
2015 editor.set_vertical_scroll_margin(2, cx);
2016 editor
2017 .style()
2018 .unwrap()
2019 .text
2020 .line_height_in_pixels(cx.rem_size())
2021 });
2022 let window = cx.window;
2023 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2024
2025 cx.set_state(
2026 r#"ˇone
2027 two
2028 three
2029 four
2030 five
2031 six
2032 seven
2033 eight
2034 nine
2035 ten
2036 "#,
2037 );
2038 cx.update_editor(|editor, cx| {
2039 assert_eq!(
2040 editor.snapshot(cx).scroll_position(),
2041 gpui::Point::new(0., 0.0)
2042 );
2043 });
2044
2045 // Add a cursor below the visible area. Since both cursors cannot fit
2046 // on screen, the editor autoscrolls to reveal the newest cursor, and
2047 // allows the vertical scroll margin below that cursor.
2048 cx.update_editor(|editor, cx| {
2049 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2050 selections.select_ranges([
2051 Point::new(0, 0)..Point::new(0, 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., 3.0)
2060 );
2061 });
2062
2063 // Move down. The editor cursor scrolls down to track the newest cursor.
2064 cx.update_editor(|editor, cx| {
2065 editor.move_down(&Default::default(), cx);
2066 });
2067 cx.update_editor(|editor, cx| {
2068 assert_eq!(
2069 editor.snapshot(cx).scroll_position(),
2070 gpui::Point::new(0., 4.0)
2071 );
2072 });
2073
2074 // Add a cursor above the visible area. Since both cursors fit on screen,
2075 // the editor scrolls to show both.
2076 cx.update_editor(|editor, cx| {
2077 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2078 selections.select_ranges([
2079 Point::new(1, 0)..Point::new(1, 0),
2080 Point::new(6, 0)..Point::new(6, 0),
2081 ]);
2082 })
2083 });
2084 cx.update_editor(|editor, cx| {
2085 assert_eq!(
2086 editor.snapshot(cx).scroll_position(),
2087 gpui::Point::new(0., 1.0)
2088 );
2089 });
2090}
2091
2092#[gpui::test]
2093async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2094 init_test(cx, |_| {});
2095 let mut cx = EditorTestContext::new(cx).await;
2096
2097 let line_height = cx.editor(|editor, cx| {
2098 editor
2099 .style()
2100 .unwrap()
2101 .text
2102 .line_height_in_pixels(cx.rem_size())
2103 });
2104 let window = cx.window;
2105 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2106 cx.set_state(
2107 &r#"
2108 ˇone
2109 two
2110 threeˇ
2111 four
2112 five
2113 six
2114 seven
2115 eight
2116 nine
2117 ten
2118 "#
2119 .unindent(),
2120 );
2121
2122 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2123 cx.assert_editor_state(
2124 &r#"
2125 one
2126 two
2127 three
2128 ˇfour
2129 five
2130 sixˇ
2131 seven
2132 eight
2133 nine
2134 ten
2135 "#
2136 .unindent(),
2137 );
2138
2139 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2140 cx.assert_editor_state(
2141 &r#"
2142 one
2143 two
2144 three
2145 four
2146 five
2147 six
2148 ˇseven
2149 eight
2150 nineˇ
2151 ten
2152 "#
2153 .unindent(),
2154 );
2155
2156 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2157 cx.assert_editor_state(
2158 &r#"
2159 one
2160 two
2161 three
2162 ˇfour
2163 five
2164 sixˇ
2165 seven
2166 eight
2167 nine
2168 ten
2169 "#
2170 .unindent(),
2171 );
2172
2173 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2174 cx.assert_editor_state(
2175 &r#"
2176 ˇone
2177 two
2178 threeˇ
2179 four
2180 five
2181 six
2182 seven
2183 eight
2184 nine
2185 ten
2186 "#
2187 .unindent(),
2188 );
2189
2190 // Test select collapsing
2191 cx.update_editor(|editor, cx| {
2192 editor.move_page_down(&MovePageDown::default(), cx);
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 });
2196 cx.assert_editor_state(
2197 &r#"
2198 one
2199 two
2200 three
2201 four
2202 five
2203 six
2204 seven
2205 eight
2206 nine
2207 ˇten
2208 ˇ"#
2209 .unindent(),
2210 );
2211}
2212
2213#[gpui::test]
2214async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2215 init_test(cx, |_| {});
2216 let mut cx = EditorTestContext::new(cx).await;
2217 cx.set_state("one «two threeˇ» four");
2218 cx.update_editor(|editor, cx| {
2219 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2220 assert_eq!(editor.text(cx), " four");
2221 });
2222}
2223
2224#[gpui::test]
2225fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2226 init_test(cx, |_| {});
2227
2228 let view = cx.add_window(|cx| {
2229 let buffer = MultiBuffer::build_simple("one two three four", cx);
2230 build_editor(buffer.clone(), cx)
2231 });
2232
2233 _ = view.update(cx, |view, cx| {
2234 view.change_selections(None, cx, |s| {
2235 s.select_display_ranges([
2236 // an empty selection - the preceding word fragment is deleted
2237 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2238 // characters selected - they are deleted
2239 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2240 ])
2241 });
2242 view.delete_to_previous_word_start(
2243 &DeleteToPreviousWordStart {
2244 ignore_newlines: false,
2245 },
2246 cx,
2247 );
2248 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2249 });
2250
2251 _ = view.update(cx, |view, cx| {
2252 view.change_selections(None, cx, |s| {
2253 s.select_display_ranges([
2254 // an empty selection - the following word fragment is deleted
2255 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2256 // characters selected - they are deleted
2257 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2258 ])
2259 });
2260 view.delete_to_next_word_end(
2261 &DeleteToNextWordEnd {
2262 ignore_newlines: false,
2263 },
2264 cx,
2265 );
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2267 });
2268}
2269
2270#[gpui::test]
2271fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2272 init_test(cx, |_| {});
2273
2274 let view = cx.add_window(|cx| {
2275 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2276 build_editor(buffer.clone(), cx)
2277 });
2278 let del_to_prev_word_start = DeleteToPreviousWordStart {
2279 ignore_newlines: false,
2280 };
2281 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2282 ignore_newlines: true,
2283 };
2284
2285 _ = view.update(cx, |view, cx| {
2286 view.change_selections(None, cx, |s| {
2287 s.select_display_ranges([
2288 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2289 ])
2290 });
2291 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2292 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2293 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2294 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2295 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2296 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2297 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2298 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2299 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2300 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2301 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2302 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2303 });
2304}
2305
2306#[gpui::test]
2307fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2308 init_test(cx, |_| {});
2309
2310 let view = cx.add_window(|cx| {
2311 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2312 build_editor(buffer.clone(), cx)
2313 });
2314 let del_to_next_word_end = DeleteToNextWordEnd {
2315 ignore_newlines: false,
2316 };
2317 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2318 ignore_newlines: true,
2319 };
2320
2321 _ = view.update(cx, |view, cx| {
2322 view.change_selections(None, cx, |s| {
2323 s.select_display_ranges([
2324 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2325 ])
2326 });
2327 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2328 assert_eq!(
2329 view.buffer.read(cx).read(cx).text(),
2330 "one\n two\nthree\n four"
2331 );
2332 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2333 assert_eq!(
2334 view.buffer.read(cx).read(cx).text(),
2335 "\n two\nthree\n four"
2336 );
2337 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2338 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2339 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2340 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2341 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2342 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2343 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2344 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2345 });
2346}
2347
2348#[gpui::test]
2349fn test_newline(cx: &mut TestAppContext) {
2350 init_test(cx, |_| {});
2351
2352 let view = cx.add_window(|cx| {
2353 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2354 build_editor(buffer.clone(), cx)
2355 });
2356
2357 _ = view.update(cx, |view, cx| {
2358 view.change_selections(None, cx, |s| {
2359 s.select_display_ranges([
2360 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2361 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2362 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2363 ])
2364 });
2365
2366 view.newline(&Newline, cx);
2367 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2368 });
2369}
2370
2371#[gpui::test]
2372fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2373 init_test(cx, |_| {});
2374
2375 let editor = cx.add_window(|cx| {
2376 let buffer = MultiBuffer::build_simple(
2377 "
2378 a
2379 b(
2380 X
2381 )
2382 c(
2383 X
2384 )
2385 "
2386 .unindent()
2387 .as_str(),
2388 cx,
2389 );
2390 let mut editor = build_editor(buffer.clone(), cx);
2391 editor.change_selections(None, cx, |s| {
2392 s.select_ranges([
2393 Point::new(2, 4)..Point::new(2, 5),
2394 Point::new(5, 4)..Point::new(5, 5),
2395 ])
2396 });
2397 editor
2398 });
2399
2400 _ = editor.update(cx, |editor, cx| {
2401 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2402 editor.buffer.update(cx, |buffer, cx| {
2403 buffer.edit(
2404 [
2405 (Point::new(1, 2)..Point::new(3, 0), ""),
2406 (Point::new(4, 2)..Point::new(6, 0), ""),
2407 ],
2408 None,
2409 cx,
2410 );
2411 assert_eq!(
2412 buffer.read(cx).text(),
2413 "
2414 a
2415 b()
2416 c()
2417 "
2418 .unindent()
2419 );
2420 });
2421 assert_eq!(
2422 editor.selections.ranges(cx),
2423 &[
2424 Point::new(1, 2)..Point::new(1, 2),
2425 Point::new(2, 2)..Point::new(2, 2),
2426 ],
2427 );
2428
2429 editor.newline(&Newline, cx);
2430 assert_eq!(
2431 editor.text(cx),
2432 "
2433 a
2434 b(
2435 )
2436 c(
2437 )
2438 "
2439 .unindent()
2440 );
2441
2442 // The selections are moved after the inserted newlines
2443 assert_eq!(
2444 editor.selections.ranges(cx),
2445 &[
2446 Point::new(2, 0)..Point::new(2, 0),
2447 Point::new(4, 0)..Point::new(4, 0),
2448 ],
2449 );
2450 });
2451}
2452
2453#[gpui::test]
2454async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2455 init_test(cx, |settings| {
2456 settings.defaults.tab_size = NonZeroU32::new(4)
2457 });
2458
2459 let language = Arc::new(
2460 Language::new(
2461 LanguageConfig::default(),
2462 Some(tree_sitter_rust::LANGUAGE.into()),
2463 )
2464 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2465 .unwrap(),
2466 );
2467
2468 let mut cx = EditorTestContext::new(cx).await;
2469 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2470 cx.set_state(indoc! {"
2471 const a: ˇA = (
2472 (ˇ
2473 «const_functionˇ»(ˇ),
2474 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2475 )ˇ
2476 ˇ);ˇ
2477 "});
2478
2479 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2480 cx.assert_editor_state(indoc! {"
2481 ˇ
2482 const a: A = (
2483 ˇ
2484 (
2485 ˇ
2486 ˇ
2487 const_function(),
2488 ˇ
2489 ˇ
2490 ˇ
2491 ˇ
2492 something_else,
2493 ˇ
2494 )
2495 ˇ
2496 ˇ
2497 );
2498 "});
2499}
2500
2501#[gpui::test]
2502async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2503 init_test(cx, |settings| {
2504 settings.defaults.tab_size = NonZeroU32::new(4)
2505 });
2506
2507 let language = Arc::new(
2508 Language::new(
2509 LanguageConfig::default(),
2510 Some(tree_sitter_rust::LANGUAGE.into()),
2511 )
2512 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2513 .unwrap(),
2514 );
2515
2516 let mut cx = EditorTestContext::new(cx).await;
2517 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2518 cx.set_state(indoc! {"
2519 const a: ˇA = (
2520 (ˇ
2521 «const_functionˇ»(ˇ),
2522 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2523 )ˇ
2524 ˇ);ˇ
2525 "});
2526
2527 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2528 cx.assert_editor_state(indoc! {"
2529 const a: A = (
2530 ˇ
2531 (
2532 ˇ
2533 const_function(),
2534 ˇ
2535 ˇ
2536 something_else,
2537 ˇ
2538 ˇ
2539 ˇ
2540 ˇ
2541 )
2542 ˇ
2543 );
2544 ˇ
2545 ˇ
2546 "});
2547}
2548
2549#[gpui::test]
2550async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2551 init_test(cx, |settings| {
2552 settings.defaults.tab_size = NonZeroU32::new(4)
2553 });
2554
2555 let language = Arc::new(Language::new(
2556 LanguageConfig {
2557 line_comments: vec!["//".into()],
2558 ..LanguageConfig::default()
2559 },
2560 None,
2561 ));
2562 {
2563 let mut cx = EditorTestContext::new(cx).await;
2564 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2565 cx.set_state(indoc! {"
2566 // Fooˇ
2567 "});
2568
2569 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2570 cx.assert_editor_state(indoc! {"
2571 // Foo
2572 //ˇ
2573 "});
2574 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2575 cx.set_state(indoc! {"
2576 ˇ// Foo
2577 "});
2578 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2579 cx.assert_editor_state(indoc! {"
2580
2581 ˇ// Foo
2582 "});
2583 }
2584 // Ensure that comment continuations can be disabled.
2585 update_test_language_settings(cx, |settings| {
2586 settings.defaults.extend_comment_on_newline = Some(false);
2587 });
2588 let mut cx = EditorTestContext::new(cx).await;
2589 cx.set_state(indoc! {"
2590 // Fooˇ
2591 "});
2592 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2593 cx.assert_editor_state(indoc! {"
2594 // Foo
2595 ˇ
2596 "});
2597}
2598
2599#[gpui::test]
2600fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2601 init_test(cx, |_| {});
2602
2603 let editor = cx.add_window(|cx| {
2604 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2605 let mut editor = build_editor(buffer.clone(), cx);
2606 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2607 editor
2608 });
2609
2610 _ = editor.update(cx, |editor, cx| {
2611 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2612 editor.buffer.update(cx, |buffer, cx| {
2613 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2614 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2615 });
2616 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2617
2618 editor.insert("Z", cx);
2619 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2620
2621 // The selections are moved after the inserted characters
2622 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2623 });
2624}
2625
2626#[gpui::test]
2627async fn test_tab(cx: &mut gpui::TestAppContext) {
2628 init_test(cx, |settings| {
2629 settings.defaults.tab_size = NonZeroU32::new(3)
2630 });
2631
2632 let mut cx = EditorTestContext::new(cx).await;
2633 cx.set_state(indoc! {"
2634 ˇabˇc
2635 ˇ🏀ˇ🏀ˇefg
2636 dˇ
2637 "});
2638 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2639 cx.assert_editor_state(indoc! {"
2640 ˇab ˇc
2641 ˇ🏀 ˇ🏀 ˇefg
2642 d ˇ
2643 "});
2644
2645 cx.set_state(indoc! {"
2646 a
2647 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2648 "});
2649 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2650 cx.assert_editor_state(indoc! {"
2651 a
2652 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2653 "});
2654}
2655
2656#[gpui::test]
2657async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2658 init_test(cx, |_| {});
2659
2660 let mut cx = EditorTestContext::new(cx).await;
2661 let language = Arc::new(
2662 Language::new(
2663 LanguageConfig::default(),
2664 Some(tree_sitter_rust::LANGUAGE.into()),
2665 )
2666 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2667 .unwrap(),
2668 );
2669 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2670
2671 // cursors that are already at the suggested indent level insert
2672 // a soft tab. cursors that are to the left of the suggested indent
2673 // auto-indent their line.
2674 cx.set_state(indoc! {"
2675 ˇ
2676 const a: B = (
2677 c(
2678 d(
2679 ˇ
2680 )
2681 ˇ
2682 ˇ )
2683 );
2684 "});
2685 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2686 cx.assert_editor_state(indoc! {"
2687 ˇ
2688 const a: B = (
2689 c(
2690 d(
2691 ˇ
2692 )
2693 ˇ
2694 ˇ)
2695 );
2696 "});
2697
2698 // handle auto-indent when there are multiple cursors on the same line
2699 cx.set_state(indoc! {"
2700 const a: B = (
2701 c(
2702 ˇ ˇ
2703 ˇ )
2704 );
2705 "});
2706 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2707 cx.assert_editor_state(indoc! {"
2708 const a: B = (
2709 c(
2710 ˇ
2711 ˇ)
2712 );
2713 "});
2714}
2715
2716#[gpui::test]
2717async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2718 init_test(cx, |settings| {
2719 settings.defaults.tab_size = NonZeroU32::new(4)
2720 });
2721
2722 let language = Arc::new(
2723 Language::new(
2724 LanguageConfig::default(),
2725 Some(tree_sitter_rust::LANGUAGE.into()),
2726 )
2727 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2728 .unwrap(),
2729 );
2730
2731 let mut cx = EditorTestContext::new(cx).await;
2732 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2733 cx.set_state(indoc! {"
2734 fn a() {
2735 if b {
2736 \t ˇc
2737 }
2738 }
2739 "});
2740
2741 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2742 cx.assert_editor_state(indoc! {"
2743 fn a() {
2744 if b {
2745 ˇc
2746 }
2747 }
2748 "});
2749}
2750
2751#[gpui::test]
2752async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2753 init_test(cx, |settings| {
2754 settings.defaults.tab_size = NonZeroU32::new(4);
2755 });
2756
2757 let mut cx = EditorTestContext::new(cx).await;
2758
2759 cx.set_state(indoc! {"
2760 «oneˇ» «twoˇ»
2761 three
2762 four
2763 "});
2764 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2765 cx.assert_editor_state(indoc! {"
2766 «oneˇ» «twoˇ»
2767 three
2768 four
2769 "});
2770
2771 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2772 cx.assert_editor_state(indoc! {"
2773 «oneˇ» «twoˇ»
2774 three
2775 four
2776 "});
2777
2778 // select across line ending
2779 cx.set_state(indoc! {"
2780 one two
2781 t«hree
2782 ˇ» four
2783 "});
2784 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 t«hree
2788 ˇ» four
2789 "});
2790
2791 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2792 cx.assert_editor_state(indoc! {"
2793 one two
2794 t«hree
2795 ˇ» four
2796 "});
2797
2798 // Ensure that indenting/outdenting works when the cursor is at column 0.
2799 cx.set_state(indoc! {"
2800 one two
2801 ˇthree
2802 four
2803 "});
2804 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2805 cx.assert_editor_state(indoc! {"
2806 one two
2807 ˇthree
2808 four
2809 "});
2810
2811 cx.set_state(indoc! {"
2812 one two
2813 ˇ three
2814 four
2815 "});
2816 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2817 cx.assert_editor_state(indoc! {"
2818 one two
2819 ˇthree
2820 four
2821 "});
2822}
2823
2824#[gpui::test]
2825async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.hard_tabs = Some(true);
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831
2832 // select two ranges on one line
2833 cx.set_state(indoc! {"
2834 «oneˇ» «twoˇ»
2835 three
2836 four
2837 "});
2838 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2839 cx.assert_editor_state(indoc! {"
2840 \t«oneˇ» «twoˇ»
2841 three
2842 four
2843 "});
2844 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2845 cx.assert_editor_state(indoc! {"
2846 \t\t«oneˇ» «twoˇ»
2847 three
2848 four
2849 "});
2850 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2851 cx.assert_editor_state(indoc! {"
2852 \t«oneˇ» «twoˇ»
2853 three
2854 four
2855 "});
2856 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2857 cx.assert_editor_state(indoc! {"
2858 «oneˇ» «twoˇ»
2859 three
2860 four
2861 "});
2862
2863 // select across a line ending
2864 cx.set_state(indoc! {"
2865 one two
2866 t«hree
2867 ˇ»four
2868 "});
2869 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2870 cx.assert_editor_state(indoc! {"
2871 one two
2872 \tt«hree
2873 ˇ»four
2874 "});
2875 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2876 cx.assert_editor_state(indoc! {"
2877 one two
2878 \t\tt«hree
2879 ˇ»four
2880 "});
2881 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2882 cx.assert_editor_state(indoc! {"
2883 one two
2884 \tt«hree
2885 ˇ»four
2886 "});
2887 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2888 cx.assert_editor_state(indoc! {"
2889 one two
2890 t«hree
2891 ˇ»four
2892 "});
2893
2894 // Ensure that indenting/outdenting works when the cursor is at column 0.
2895 cx.set_state(indoc! {"
2896 one two
2897 ˇthree
2898 four
2899 "});
2900 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2901 cx.assert_editor_state(indoc! {"
2902 one two
2903 ˇthree
2904 four
2905 "});
2906 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2907 cx.assert_editor_state(indoc! {"
2908 one two
2909 \tˇthree
2910 four
2911 "});
2912 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2913 cx.assert_editor_state(indoc! {"
2914 one two
2915 ˇthree
2916 four
2917 "});
2918}
2919
2920#[gpui::test]
2921fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2922 init_test(cx, |settings| {
2923 settings.languages.extend([
2924 (
2925 "TOML".into(),
2926 LanguageSettingsContent {
2927 tab_size: NonZeroU32::new(2),
2928 ..Default::default()
2929 },
2930 ),
2931 (
2932 "Rust".into(),
2933 LanguageSettingsContent {
2934 tab_size: NonZeroU32::new(4),
2935 ..Default::default()
2936 },
2937 ),
2938 ]);
2939 });
2940
2941 let toml_language = Arc::new(Language::new(
2942 LanguageConfig {
2943 name: "TOML".into(),
2944 ..Default::default()
2945 },
2946 None,
2947 ));
2948 let rust_language = Arc::new(Language::new(
2949 LanguageConfig {
2950 name: "Rust".into(),
2951 ..Default::default()
2952 },
2953 None,
2954 ));
2955
2956 let toml_buffer =
2957 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2958 let rust_buffer = cx.new_model(|cx| {
2959 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2960 });
2961 let multibuffer = cx.new_model(|cx| {
2962 let mut multibuffer = MultiBuffer::new(ReadWrite);
2963 multibuffer.push_excerpts(
2964 toml_buffer.clone(),
2965 [ExcerptRange {
2966 context: Point::new(0, 0)..Point::new(2, 0),
2967 primary: None,
2968 }],
2969 cx,
2970 );
2971 multibuffer.push_excerpts(
2972 rust_buffer.clone(),
2973 [ExcerptRange {
2974 context: Point::new(0, 0)..Point::new(1, 0),
2975 primary: None,
2976 }],
2977 cx,
2978 );
2979 multibuffer
2980 });
2981
2982 cx.add_window(|cx| {
2983 let mut editor = build_editor(multibuffer, cx);
2984
2985 assert_eq!(
2986 editor.text(cx),
2987 indoc! {"
2988 a = 1
2989 b = 2
2990
2991 const c: usize = 3;
2992 "}
2993 );
2994
2995 select_ranges(
2996 &mut editor,
2997 indoc! {"
2998 «aˇ» = 1
2999 b = 2
3000
3001 «const c:ˇ» usize = 3;
3002 "},
3003 cx,
3004 );
3005
3006 editor.tab(&Tab, cx);
3007 assert_text_with_selections(
3008 &mut editor,
3009 indoc! {"
3010 «aˇ» = 1
3011 b = 2
3012
3013 «const c:ˇ» usize = 3;
3014 "},
3015 cx,
3016 );
3017 editor.tab_prev(&TabPrev, cx);
3018 assert_text_with_selections(
3019 &mut editor,
3020 indoc! {"
3021 «aˇ» = 1
3022 b = 2
3023
3024 «const c:ˇ» usize = 3;
3025 "},
3026 cx,
3027 );
3028
3029 editor
3030 });
3031}
3032
3033#[gpui::test]
3034async fn test_backspace(cx: &mut gpui::TestAppContext) {
3035 init_test(cx, |_| {});
3036
3037 let mut cx = EditorTestContext::new(cx).await;
3038
3039 // Basic backspace
3040 cx.set_state(indoc! {"
3041 onˇe two three
3042 fou«rˇ» five six
3043 seven «ˇeight nine
3044 »ten
3045 "});
3046 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3047 cx.assert_editor_state(indoc! {"
3048 oˇe two three
3049 fouˇ five six
3050 seven ˇten
3051 "});
3052
3053 // Test backspace inside and around indents
3054 cx.set_state(indoc! {"
3055 zero
3056 ˇone
3057 ˇtwo
3058 ˇ ˇ ˇ three
3059 ˇ ˇ four
3060 "});
3061 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3062 cx.assert_editor_state(indoc! {"
3063 zero
3064 ˇone
3065 ˇtwo
3066 ˇ threeˇ four
3067 "});
3068
3069 // Test backspace with line_mode set to true
3070 cx.update_editor(|e, _| e.selections.line_mode = true);
3071 cx.set_state(indoc! {"
3072 The ˇquick ˇbrown
3073 fox jumps over
3074 the lazy dog
3075 ˇThe qu«ick bˇ»rown"});
3076 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3077 cx.assert_editor_state(indoc! {"
3078 ˇfox jumps over
3079 the lazy dogˇ"});
3080}
3081
3082#[gpui::test]
3083async fn test_delete(cx: &mut gpui::TestAppContext) {
3084 init_test(cx, |_| {});
3085
3086 let mut cx = EditorTestContext::new(cx).await;
3087 cx.set_state(indoc! {"
3088 onˇe two three
3089 fou«rˇ» five six
3090 seven «ˇeight nine
3091 »ten
3092 "});
3093 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3094 cx.assert_editor_state(indoc! {"
3095 onˇ two three
3096 fouˇ five six
3097 seven ˇten
3098 "});
3099
3100 // Test backspace with line_mode set to true
3101 cx.update_editor(|e, _| e.selections.line_mode = true);
3102 cx.set_state(indoc! {"
3103 The ˇquick ˇbrown
3104 fox «ˇjum»ps over
3105 the lazy dog
3106 ˇThe qu«ick bˇ»rown"});
3107 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3108 cx.assert_editor_state("ˇthe lazy dogˇ");
3109}
3110
3111#[gpui::test]
3112fn test_delete_line(cx: &mut TestAppContext) {
3113 init_test(cx, |_| {});
3114
3115 let view = cx.add_window(|cx| {
3116 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3117 build_editor(buffer, cx)
3118 });
3119 _ = view.update(cx, |view, cx| {
3120 view.change_selections(None, cx, |s| {
3121 s.select_display_ranges([
3122 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3123 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3124 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3125 ])
3126 });
3127 view.delete_line(&DeleteLine, cx);
3128 assert_eq!(view.display_text(cx), "ghi");
3129 assert_eq!(
3130 view.selections.display_ranges(cx),
3131 vec![
3132 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3133 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3134 ]
3135 );
3136 });
3137
3138 let view = cx.add_window(|cx| {
3139 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3140 build_editor(buffer, cx)
3141 });
3142 _ = view.update(cx, |view, cx| {
3143 view.change_selections(None, cx, |s| {
3144 s.select_display_ranges([
3145 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3146 ])
3147 });
3148 view.delete_line(&DeleteLine, cx);
3149 assert_eq!(view.display_text(cx), "ghi\n");
3150 assert_eq!(
3151 view.selections.display_ranges(cx),
3152 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3153 );
3154 });
3155}
3156
3157#[gpui::test]
3158fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3159 init_test(cx, |_| {});
3160
3161 cx.add_window(|cx| {
3162 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3163 let mut editor = build_editor(buffer.clone(), cx);
3164 let buffer = buffer.read(cx).as_singleton().unwrap();
3165
3166 assert_eq!(
3167 editor.selections.ranges::<Point>(cx),
3168 &[Point::new(0, 0)..Point::new(0, 0)]
3169 );
3170
3171 // When on single line, replace newline at end by space
3172 editor.join_lines(&JoinLines, cx);
3173 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3174 assert_eq!(
3175 editor.selections.ranges::<Point>(cx),
3176 &[Point::new(0, 3)..Point::new(0, 3)]
3177 );
3178
3179 // When multiple lines are selected, remove newlines that are spanned by the selection
3180 editor.change_selections(None, cx, |s| {
3181 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3182 });
3183 editor.join_lines(&JoinLines, cx);
3184 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3185 assert_eq!(
3186 editor.selections.ranges::<Point>(cx),
3187 &[Point::new(0, 11)..Point::new(0, 11)]
3188 );
3189
3190 // Undo should be transactional
3191 editor.undo(&Undo, cx);
3192 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3193 assert_eq!(
3194 editor.selections.ranges::<Point>(cx),
3195 &[Point::new(0, 5)..Point::new(2, 2)]
3196 );
3197
3198 // When joining an empty line don't insert a space
3199 editor.change_selections(None, cx, |s| {
3200 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3201 });
3202 editor.join_lines(&JoinLines, cx);
3203 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3204 assert_eq!(
3205 editor.selections.ranges::<Point>(cx),
3206 [Point::new(2, 3)..Point::new(2, 3)]
3207 );
3208
3209 // We can remove trailing newlines
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3212 assert_eq!(
3213 editor.selections.ranges::<Point>(cx),
3214 [Point::new(2, 3)..Point::new(2, 3)]
3215 );
3216
3217 // We don't blow up on the last line
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3220 assert_eq!(
3221 editor.selections.ranges::<Point>(cx),
3222 [Point::new(2, 3)..Point::new(2, 3)]
3223 );
3224
3225 // reset to test indentation
3226 editor.buffer.update(cx, |buffer, cx| {
3227 buffer.edit(
3228 [
3229 (Point::new(1, 0)..Point::new(1, 2), " "),
3230 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3231 ],
3232 None,
3233 cx,
3234 )
3235 });
3236
3237 // We remove any leading spaces
3238 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3239 editor.change_selections(None, cx, |s| {
3240 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3241 });
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3244
3245 // We don't insert a space for a line containing only spaces
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3248
3249 // We ignore any leading tabs
3250 editor.join_lines(&JoinLines, cx);
3251 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3252
3253 editor
3254 });
3255}
3256
3257#[gpui::test]
3258fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3259 init_test(cx, |_| {});
3260
3261 cx.add_window(|cx| {
3262 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3263 let mut editor = build_editor(buffer.clone(), cx);
3264 let buffer = buffer.read(cx).as_singleton().unwrap();
3265
3266 editor.change_selections(None, cx, |s| {
3267 s.select_ranges([
3268 Point::new(0, 2)..Point::new(1, 1),
3269 Point::new(1, 2)..Point::new(1, 2),
3270 Point::new(3, 1)..Point::new(3, 2),
3271 ])
3272 });
3273
3274 editor.join_lines(&JoinLines, cx);
3275 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3276
3277 assert_eq!(
3278 editor.selections.ranges::<Point>(cx),
3279 [
3280 Point::new(0, 7)..Point::new(0, 7),
3281 Point::new(1, 3)..Point::new(1, 3)
3282 ]
3283 );
3284 editor
3285 });
3286}
3287
3288#[gpui::test]
3289async fn test_join_lines_with_git_diff_base(
3290 executor: BackgroundExecutor,
3291 cx: &mut gpui::TestAppContext,
3292) {
3293 init_test(cx, |_| {});
3294
3295 let mut cx = EditorTestContext::new(cx).await;
3296
3297 let diff_base = r#"
3298 Line 0
3299 Line 1
3300 Line 2
3301 Line 3
3302 "#
3303 .unindent();
3304
3305 cx.set_state(
3306 &r#"
3307 ˇLine 0
3308 Line 1
3309 Line 2
3310 Line 3
3311 "#
3312 .unindent(),
3313 );
3314
3315 cx.set_diff_base(&diff_base);
3316 executor.run_until_parked();
3317
3318 // Join lines
3319 cx.update_editor(|editor, cx| {
3320 editor.join_lines(&JoinLines, cx);
3321 });
3322 executor.run_until_parked();
3323
3324 cx.assert_editor_state(
3325 &r#"
3326 Line 0ˇ Line 1
3327 Line 2
3328 Line 3
3329 "#
3330 .unindent(),
3331 );
3332 // Join again
3333 cx.update_editor(|editor, cx| {
3334 editor.join_lines(&JoinLines, cx);
3335 });
3336 executor.run_until_parked();
3337
3338 cx.assert_editor_state(
3339 &r#"
3340 Line 0 Line 1ˇ Line 2
3341 Line 3
3342 "#
3343 .unindent(),
3344 );
3345}
3346
3347#[gpui::test]
3348async fn test_custom_newlines_cause_no_false_positive_diffs(
3349 executor: BackgroundExecutor,
3350 cx: &mut gpui::TestAppContext,
3351) {
3352 init_test(cx, |_| {});
3353 let mut cx = EditorTestContext::new(cx).await;
3354 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3355 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3356 executor.run_until_parked();
3357
3358 cx.update_editor(|editor, cx| {
3359 let snapshot = editor.snapshot(cx);
3360 assert_eq!(
3361 snapshot
3362 .diff_map
3363 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3364 .collect::<Vec<_>>(),
3365 Vec::new(),
3366 "Should not have any diffs for files with custom newlines"
3367 );
3368 });
3369}
3370
3371#[gpui::test]
3372async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3373 init_test(cx, |_| {});
3374
3375 let mut cx = EditorTestContext::new(cx).await;
3376
3377 // Test sort_lines_case_insensitive()
3378 cx.set_state(indoc! {"
3379 «z
3380 y
3381 x
3382 Z
3383 Y
3384 Xˇ»
3385 "});
3386 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3387 cx.assert_editor_state(indoc! {"
3388 «x
3389 X
3390 y
3391 Y
3392 z
3393 Zˇ»
3394 "});
3395
3396 // Test reverse_lines()
3397 cx.set_state(indoc! {"
3398 «5
3399 4
3400 3
3401 2
3402 1ˇ»
3403 "});
3404 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3405 cx.assert_editor_state(indoc! {"
3406 «1
3407 2
3408 3
3409 4
3410 5ˇ»
3411 "});
3412
3413 // Skip testing shuffle_line()
3414
3415 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3416 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3417
3418 // Don't manipulate when cursor is on single line, but expand the selection
3419 cx.set_state(indoc! {"
3420 ddˇdd
3421 ccc
3422 bb
3423 a
3424 "});
3425 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3426 cx.assert_editor_state(indoc! {"
3427 «ddddˇ»
3428 ccc
3429 bb
3430 a
3431 "});
3432
3433 // Basic manipulate case
3434 // Start selection moves to column 0
3435 // End of selection shrinks to fit shorter line
3436 cx.set_state(indoc! {"
3437 dd«d
3438 ccc
3439 bb
3440 aaaaaˇ»
3441 "});
3442 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3443 cx.assert_editor_state(indoc! {"
3444 «aaaaa
3445 bb
3446 ccc
3447 dddˇ»
3448 "});
3449
3450 // Manipulate case with newlines
3451 cx.set_state(indoc! {"
3452 dd«d
3453 ccc
3454
3455 bb
3456 aaaaa
3457
3458 ˇ»
3459 "});
3460 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3461 cx.assert_editor_state(indoc! {"
3462 «
3463
3464 aaaaa
3465 bb
3466 ccc
3467 dddˇ»
3468
3469 "});
3470
3471 // Adding new line
3472 cx.set_state(indoc! {"
3473 aa«a
3474 bbˇ»b
3475 "});
3476 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3477 cx.assert_editor_state(indoc! {"
3478 «aaa
3479 bbb
3480 added_lineˇ»
3481 "});
3482
3483 // Removing line
3484 cx.set_state(indoc! {"
3485 aa«a
3486 bbbˇ»
3487 "});
3488 cx.update_editor(|e, cx| {
3489 e.manipulate_lines(cx, |lines| {
3490 lines.pop();
3491 })
3492 });
3493 cx.assert_editor_state(indoc! {"
3494 «aaaˇ»
3495 "});
3496
3497 // Removing all lines
3498 cx.set_state(indoc! {"
3499 aa«a
3500 bbbˇ»
3501 "});
3502 cx.update_editor(|e, cx| {
3503 e.manipulate_lines(cx, |lines| {
3504 lines.drain(..);
3505 })
3506 });
3507 cx.assert_editor_state(indoc! {"
3508 ˇ
3509 "});
3510}
3511
3512#[gpui::test]
3513async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3514 init_test(cx, |_| {});
3515
3516 let mut cx = EditorTestContext::new(cx).await;
3517
3518 // Consider continuous selection as single selection
3519 cx.set_state(indoc! {"
3520 Aaa«aa
3521 cˇ»c«c
3522 bb
3523 aaaˇ»aa
3524 "});
3525 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «Aaaaa
3528 ccc
3529 bb
3530 aaaaaˇ»
3531 "});
3532
3533 cx.set_state(indoc! {"
3534 Aaa«aa
3535 cˇ»c«c
3536 bb
3537 aaaˇ»aa
3538 "});
3539 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3540 cx.assert_editor_state(indoc! {"
3541 «Aaaaa
3542 ccc
3543 bbˇ»
3544 "});
3545
3546 // Consider non continuous selection as distinct dedup operations
3547 cx.set_state(indoc! {"
3548 «aaaaa
3549 bb
3550 aaaaa
3551 aaaaaˇ»
3552
3553 aaa«aaˇ»
3554 "});
3555 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaaa
3558 bbˇ»
3559
3560 «aaaaaˇ»
3561 "});
3562}
3563
3564#[gpui::test]
3565async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3566 init_test(cx, |_| {});
3567
3568 let mut cx = EditorTestContext::new(cx).await;
3569
3570 cx.set_state(indoc! {"
3571 «Aaa
3572 aAa
3573 Aaaˇ»
3574 "});
3575 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3576 cx.assert_editor_state(indoc! {"
3577 «Aaa
3578 aAaˇ»
3579 "});
3580
3581 cx.set_state(indoc! {"
3582 «Aaa
3583 aAa
3584 aaAˇ»
3585 "});
3586 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3587 cx.assert_editor_state(indoc! {"
3588 «Aaaˇ»
3589 "});
3590}
3591
3592#[gpui::test]
3593async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3594 init_test(cx, |_| {});
3595
3596 let mut cx = EditorTestContext::new(cx).await;
3597
3598 // Manipulate with multiple selections on a single line
3599 cx.set_state(indoc! {"
3600 dd«dd
3601 cˇ»c«c
3602 bb
3603 aaaˇ»aa
3604 "});
3605 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3606 cx.assert_editor_state(indoc! {"
3607 «aaaaa
3608 bb
3609 ccc
3610 ddddˇ»
3611 "});
3612
3613 // Manipulate with multiple disjoin selections
3614 cx.set_state(indoc! {"
3615 5«
3616 4
3617 3
3618 2
3619 1ˇ»
3620
3621 dd«dd
3622 ccc
3623 bb
3624 aaaˇ»aa
3625 "});
3626 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3627 cx.assert_editor_state(indoc! {"
3628 «1
3629 2
3630 3
3631 4
3632 5ˇ»
3633
3634 «aaaaa
3635 bb
3636 ccc
3637 ddddˇ»
3638 "});
3639
3640 // Adding lines on each selection
3641 cx.set_state(indoc! {"
3642 2«
3643 1ˇ»
3644
3645 bb«bb
3646 aaaˇ»aa
3647 "});
3648 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3649 cx.assert_editor_state(indoc! {"
3650 «2
3651 1
3652 added lineˇ»
3653
3654 «bbbb
3655 aaaaa
3656 added lineˇ»
3657 "});
3658
3659 // Removing lines on each selection
3660 cx.set_state(indoc! {"
3661 2«
3662 1ˇ»
3663
3664 bb«bb
3665 aaaˇ»aa
3666 "});
3667 cx.update_editor(|e, cx| {
3668 e.manipulate_lines(cx, |lines| {
3669 lines.pop();
3670 })
3671 });
3672 cx.assert_editor_state(indoc! {"
3673 «2ˇ»
3674
3675 «bbbbˇ»
3676 "});
3677}
3678
3679#[gpui::test]
3680async fn test_manipulate_text(cx: &mut TestAppContext) {
3681 init_test(cx, |_| {});
3682
3683 let mut cx = EditorTestContext::new(cx).await;
3684
3685 // Test convert_to_upper_case()
3686 cx.set_state(indoc! {"
3687 «hello worldˇ»
3688 "});
3689 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3690 cx.assert_editor_state(indoc! {"
3691 «HELLO WORLDˇ»
3692 "});
3693
3694 // Test convert_to_lower_case()
3695 cx.set_state(indoc! {"
3696 «HELLO WORLDˇ»
3697 "});
3698 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3699 cx.assert_editor_state(indoc! {"
3700 «hello worldˇ»
3701 "});
3702
3703 // Test multiple line, single selection case
3704 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3705 cx.set_state(indoc! {"
3706 «The quick brown
3707 fox jumps over
3708 the lazy dogˇ»
3709 "});
3710 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3711 cx.assert_editor_state(indoc! {"
3712 «The Quick Brown
3713 Fox Jumps Over
3714 The Lazy Dogˇ»
3715 "});
3716
3717 // Test multiple line, single selection case
3718 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3719 cx.set_state(indoc! {"
3720 «The quick brown
3721 fox jumps over
3722 the lazy dogˇ»
3723 "});
3724 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3725 cx.assert_editor_state(indoc! {"
3726 «TheQuickBrown
3727 FoxJumpsOver
3728 TheLazyDogˇ»
3729 "});
3730
3731 // From here on out, test more complex cases of manipulate_text()
3732
3733 // Test no selection case - should affect words cursors are in
3734 // Cursor at beginning, middle, and end of word
3735 cx.set_state(indoc! {"
3736 ˇhello big beauˇtiful worldˇ
3737 "});
3738 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3739 cx.assert_editor_state(indoc! {"
3740 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3741 "});
3742
3743 // Test multiple selections on a single line and across multiple lines
3744 cx.set_state(indoc! {"
3745 «Theˇ» quick «brown
3746 foxˇ» jumps «overˇ»
3747 the «lazyˇ» dog
3748 "});
3749 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3750 cx.assert_editor_state(indoc! {"
3751 «THEˇ» quick «BROWN
3752 FOXˇ» jumps «OVERˇ»
3753 the «LAZYˇ» dog
3754 "});
3755
3756 // Test case where text length grows
3757 cx.set_state(indoc! {"
3758 «tschüߡ»
3759 "});
3760 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3761 cx.assert_editor_state(indoc! {"
3762 «TSCHÜSSˇ»
3763 "});
3764
3765 // Test to make sure we don't crash when text shrinks
3766 cx.set_state(indoc! {"
3767 aaa_bbbˇ
3768 "});
3769 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3770 cx.assert_editor_state(indoc! {"
3771 «aaaBbbˇ»
3772 "});
3773
3774 // Test to make sure we all aware of the fact that each word can grow and shrink
3775 // Final selections should be aware of this fact
3776 cx.set_state(indoc! {"
3777 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3778 "});
3779 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3780 cx.assert_editor_state(indoc! {"
3781 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3782 "});
3783
3784 cx.set_state(indoc! {"
3785 «hElLo, WoRld!ˇ»
3786 "});
3787 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3788 cx.assert_editor_state(indoc! {"
3789 «HeLlO, wOrLD!ˇ»
3790 "});
3791}
3792
3793#[gpui::test]
3794fn test_duplicate_line(cx: &mut TestAppContext) {
3795 init_test(cx, |_| {});
3796
3797 let view = cx.add_window(|cx| {
3798 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3799 build_editor(buffer, cx)
3800 });
3801 _ = view.update(cx, |view, cx| {
3802 view.change_selections(None, cx, |s| {
3803 s.select_display_ranges([
3804 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3805 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3806 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3807 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3808 ])
3809 });
3810 view.duplicate_line_down(&DuplicateLineDown, cx);
3811 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3812 assert_eq!(
3813 view.selections.display_ranges(cx),
3814 vec![
3815 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3816 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3817 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3818 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3819 ]
3820 );
3821 });
3822
3823 let view = cx.add_window(|cx| {
3824 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3825 build_editor(buffer, cx)
3826 });
3827 _ = view.update(cx, |view, cx| {
3828 view.change_selections(None, cx, |s| {
3829 s.select_display_ranges([
3830 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3831 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3832 ])
3833 });
3834 view.duplicate_line_down(&DuplicateLineDown, cx);
3835 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3836 assert_eq!(
3837 view.selections.display_ranges(cx),
3838 vec![
3839 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3840 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3841 ]
3842 );
3843 });
3844
3845 // With `move_upwards` the selections stay in place, except for
3846 // the lines inserted above them
3847 let view = cx.add_window(|cx| {
3848 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3849 build_editor(buffer, cx)
3850 });
3851 _ = view.update(cx, |view, cx| {
3852 view.change_selections(None, cx, |s| {
3853 s.select_display_ranges([
3854 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3855 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3856 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3857 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3858 ])
3859 });
3860 view.duplicate_line_up(&DuplicateLineUp, cx);
3861 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3862 assert_eq!(
3863 view.selections.display_ranges(cx),
3864 vec![
3865 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3866 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3867 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3868 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3869 ]
3870 );
3871 });
3872
3873 let view = cx.add_window(|cx| {
3874 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3875 build_editor(buffer, cx)
3876 });
3877 _ = view.update(cx, |view, cx| {
3878 view.change_selections(None, cx, |s| {
3879 s.select_display_ranges([
3880 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3881 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3882 ])
3883 });
3884 view.duplicate_line_up(&DuplicateLineUp, cx);
3885 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3886 assert_eq!(
3887 view.selections.display_ranges(cx),
3888 vec![
3889 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3890 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3891 ]
3892 );
3893 });
3894
3895 let view = cx.add_window(|cx| {
3896 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3897 build_editor(buffer, cx)
3898 });
3899 _ = view.update(cx, |view, cx| {
3900 view.change_selections(None, cx, |s| {
3901 s.select_display_ranges([
3902 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3903 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3904 ])
3905 });
3906 view.duplicate_selection(&DuplicateSelection, cx);
3907 assert_eq!(view.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
3908 assert_eq!(
3909 view.selections.display_ranges(cx),
3910 vec![
3911 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3912 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
3913 ]
3914 );
3915 });
3916}
3917
3918#[gpui::test]
3919fn test_move_line_up_down(cx: &mut TestAppContext) {
3920 init_test(cx, |_| {});
3921
3922 let view = cx.add_window(|cx| {
3923 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3924 build_editor(buffer, cx)
3925 });
3926 _ = view.update(cx, |view, cx| {
3927 view.fold_creases(
3928 vec![
3929 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3930 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3931 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3932 ],
3933 true,
3934 cx,
3935 );
3936 view.change_selections(None, cx, |s| {
3937 s.select_display_ranges([
3938 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3939 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3940 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3941 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3942 ])
3943 });
3944 assert_eq!(
3945 view.display_text(cx),
3946 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3947 );
3948
3949 view.move_line_up(&MoveLineUp, cx);
3950 assert_eq!(
3951 view.display_text(cx),
3952 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3953 );
3954 assert_eq!(
3955 view.selections.display_ranges(cx),
3956 vec![
3957 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3958 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3959 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3960 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3961 ]
3962 );
3963 });
3964
3965 _ = view.update(cx, |view, cx| {
3966 view.move_line_down(&MoveLineDown, cx);
3967 assert_eq!(
3968 view.display_text(cx),
3969 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3970 );
3971 assert_eq!(
3972 view.selections.display_ranges(cx),
3973 vec![
3974 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3975 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3976 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3977 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3978 ]
3979 );
3980 });
3981
3982 _ = view.update(cx, |view, cx| {
3983 view.move_line_down(&MoveLineDown, cx);
3984 assert_eq!(
3985 view.display_text(cx),
3986 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3987 );
3988 assert_eq!(
3989 view.selections.display_ranges(cx),
3990 vec![
3991 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3992 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3993 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3994 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3995 ]
3996 );
3997 });
3998
3999 _ = view.update(cx, |view, cx| {
4000 view.move_line_up(&MoveLineUp, cx);
4001 assert_eq!(
4002 view.display_text(cx),
4003 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4004 );
4005 assert_eq!(
4006 view.selections.display_ranges(cx),
4007 vec![
4008 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4009 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4010 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4011 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4012 ]
4013 );
4014 });
4015}
4016
4017#[gpui::test]
4018fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4019 init_test(cx, |_| {});
4020
4021 let editor = cx.add_window(|cx| {
4022 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4023 build_editor(buffer, cx)
4024 });
4025 _ = editor.update(cx, |editor, cx| {
4026 let snapshot = editor.buffer.read(cx).snapshot(cx);
4027 editor.insert_blocks(
4028 [BlockProperties {
4029 style: BlockStyle::Fixed,
4030 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4031 height: 1,
4032 render: Arc::new(|_| div().into_any()),
4033 priority: 0,
4034 }],
4035 Some(Autoscroll::fit()),
4036 cx,
4037 );
4038 editor.change_selections(None, cx, |s| {
4039 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4040 });
4041 editor.move_line_down(&MoveLineDown, cx);
4042 });
4043}
4044
4045#[gpui::test]
4046async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4047 init_test(cx, |_| {});
4048
4049 let mut cx = EditorTestContext::new(cx).await;
4050 cx.set_state(
4051 &"
4052 ˇzero
4053 one
4054 two
4055 three
4056 four
4057 five
4058 "
4059 .unindent(),
4060 );
4061
4062 // Create a four-line block that replaces three lines of text.
4063 cx.update_editor(|editor, cx| {
4064 let snapshot = editor.snapshot(cx);
4065 let snapshot = &snapshot.buffer_snapshot;
4066 let placement = BlockPlacement::Replace(
4067 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4068 );
4069 editor.insert_blocks(
4070 [BlockProperties {
4071 placement,
4072 height: 4,
4073 style: BlockStyle::Sticky,
4074 render: Arc::new(|_| gpui::div().into_any_element()),
4075 priority: 0,
4076 }],
4077 None,
4078 cx,
4079 );
4080 });
4081
4082 // Move down so that the cursor touches the block.
4083 cx.update_editor(|editor, cx| {
4084 editor.move_down(&Default::default(), cx);
4085 });
4086 cx.assert_editor_state(
4087 &"
4088 zero
4089 «one
4090 two
4091 threeˇ»
4092 four
4093 five
4094 "
4095 .unindent(),
4096 );
4097
4098 // Move down past the block.
4099 cx.update_editor(|editor, cx| {
4100 editor.move_down(&Default::default(), cx);
4101 });
4102 cx.assert_editor_state(
4103 &"
4104 zero
4105 one
4106 two
4107 three
4108 ˇfour
4109 five
4110 "
4111 .unindent(),
4112 );
4113}
4114
4115#[gpui::test]
4116fn test_transpose(cx: &mut TestAppContext) {
4117 init_test(cx, |_| {});
4118
4119 _ = cx.add_window(|cx| {
4120 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4121 editor.set_style(EditorStyle::default(), cx);
4122 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4123 editor.transpose(&Default::default(), cx);
4124 assert_eq!(editor.text(cx), "bac");
4125 assert_eq!(editor.selections.ranges(cx), [2..2]);
4126
4127 editor.transpose(&Default::default(), cx);
4128 assert_eq!(editor.text(cx), "bca");
4129 assert_eq!(editor.selections.ranges(cx), [3..3]);
4130
4131 editor.transpose(&Default::default(), cx);
4132 assert_eq!(editor.text(cx), "bac");
4133 assert_eq!(editor.selections.ranges(cx), [3..3]);
4134
4135 editor
4136 });
4137
4138 _ = cx.add_window(|cx| {
4139 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4140 editor.set_style(EditorStyle::default(), cx);
4141 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4142 editor.transpose(&Default::default(), cx);
4143 assert_eq!(editor.text(cx), "acb\nde");
4144 assert_eq!(editor.selections.ranges(cx), [3..3]);
4145
4146 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4147 editor.transpose(&Default::default(), cx);
4148 assert_eq!(editor.text(cx), "acbd\ne");
4149 assert_eq!(editor.selections.ranges(cx), [5..5]);
4150
4151 editor.transpose(&Default::default(), cx);
4152 assert_eq!(editor.text(cx), "acbde\n");
4153 assert_eq!(editor.selections.ranges(cx), [6..6]);
4154
4155 editor.transpose(&Default::default(), cx);
4156 assert_eq!(editor.text(cx), "acbd\ne");
4157 assert_eq!(editor.selections.ranges(cx), [6..6]);
4158
4159 editor
4160 });
4161
4162 _ = cx.add_window(|cx| {
4163 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4164 editor.set_style(EditorStyle::default(), cx);
4165 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4166 editor.transpose(&Default::default(), cx);
4167 assert_eq!(editor.text(cx), "bacd\ne");
4168 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4169
4170 editor.transpose(&Default::default(), cx);
4171 assert_eq!(editor.text(cx), "bcade\n");
4172 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4173
4174 editor.transpose(&Default::default(), cx);
4175 assert_eq!(editor.text(cx), "bcda\ne");
4176 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4177
4178 editor.transpose(&Default::default(), cx);
4179 assert_eq!(editor.text(cx), "bcade\n");
4180 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4181
4182 editor.transpose(&Default::default(), cx);
4183 assert_eq!(editor.text(cx), "bcaed\n");
4184 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4185
4186 editor
4187 });
4188
4189 _ = cx.add_window(|cx| {
4190 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4191 editor.set_style(EditorStyle::default(), cx);
4192 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4193 editor.transpose(&Default::default(), cx);
4194 assert_eq!(editor.text(cx), "🏀🍐✋");
4195 assert_eq!(editor.selections.ranges(cx), [8..8]);
4196
4197 editor.transpose(&Default::default(), cx);
4198 assert_eq!(editor.text(cx), "🏀✋🍐");
4199 assert_eq!(editor.selections.ranges(cx), [11..11]);
4200
4201 editor.transpose(&Default::default(), cx);
4202 assert_eq!(editor.text(cx), "🏀🍐✋");
4203 assert_eq!(editor.selections.ranges(cx), [11..11]);
4204
4205 editor
4206 });
4207}
4208
4209#[gpui::test]
4210async fn test_rewrap(cx: &mut TestAppContext) {
4211 init_test(cx, |_| {});
4212
4213 let mut cx = EditorTestContext::new(cx).await;
4214
4215 let language_with_c_comments = Arc::new(Language::new(
4216 LanguageConfig {
4217 line_comments: vec!["// ".into()],
4218 ..LanguageConfig::default()
4219 },
4220 None,
4221 ));
4222 let language_with_pound_comments = Arc::new(Language::new(
4223 LanguageConfig {
4224 line_comments: vec!["# ".into()],
4225 ..LanguageConfig::default()
4226 },
4227 None,
4228 ));
4229 let markdown_language = Arc::new(Language::new(
4230 LanguageConfig {
4231 name: "Markdown".into(),
4232 ..LanguageConfig::default()
4233 },
4234 None,
4235 ));
4236 let language_with_doc_comments = Arc::new(Language::new(
4237 LanguageConfig {
4238 line_comments: vec!["// ".into(), "/// ".into()],
4239 ..LanguageConfig::default()
4240 },
4241 Some(tree_sitter_rust::LANGUAGE.into()),
4242 ));
4243
4244 let plaintext_language = Arc::new(Language::new(
4245 LanguageConfig {
4246 name: "Plain Text".into(),
4247 ..LanguageConfig::default()
4248 },
4249 None,
4250 ));
4251
4252 assert_rewrap(
4253 indoc! {"
4254 // ˇ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.
4255 "},
4256 indoc! {"
4257 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4258 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4259 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4260 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4261 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4262 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4263 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4264 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4265 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4266 // porttitor id. Aliquam id accumsan eros.
4267 "},
4268 language_with_c_comments.clone(),
4269 &mut cx,
4270 );
4271
4272 // Test that rewrapping works inside of a selection
4273 assert_rewrap(
4274 indoc! {"
4275 «// 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.ˇ»
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. Vivamus sit amet neque et quam
4281 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4282 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4283 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4284 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4285 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4286 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4287 // porttitor id. Aliquam id accumsan eros.ˇ»
4288 "},
4289 language_with_c_comments.clone(),
4290 &mut cx,
4291 );
4292
4293 // Test that cursors that expand to the same region are collapsed.
4294 assert_rewrap(
4295 indoc! {"
4296 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4297 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4298 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4299 // ˇ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.
4300 "},
4301 indoc! {"
4302 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4303 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4304 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4305 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4306 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4307 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4308 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4309 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4310 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4311 // porttitor id. Aliquam id accumsan eros.
4312 "},
4313 language_with_c_comments.clone(),
4314 &mut cx,
4315 );
4316
4317 // Test that non-contiguous selections are treated separately.
4318 assert_rewrap(
4319 indoc! {"
4320 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4321 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4322 //
4323 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4324 // ˇ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.
4325 "},
4326 indoc! {"
4327 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4328 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4329 // auctor, eu lacinia sapien scelerisque.
4330 //
4331 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4332 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4333 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4334 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4335 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4336 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4337 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4338 "},
4339 language_with_c_comments.clone(),
4340 &mut cx,
4341 );
4342
4343 // Test that different comment prefixes are supported.
4344 assert_rewrap(
4345 indoc! {"
4346 # ˇ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.
4347 "},
4348 indoc! {"
4349 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4350 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4351 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4352 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4353 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4354 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4355 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4356 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4357 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4358 # accumsan eros.
4359 "},
4360 language_with_pound_comments.clone(),
4361 &mut cx,
4362 );
4363
4364 // Test that rewrapping is ignored outside of comments in most languages.
4365 assert_rewrap(
4366 indoc! {"
4367 /// Adds two numbers.
4368 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4369 fn add(a: u32, b: u32) -> u32 {
4370 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ˇ
4371 }
4372 "},
4373 indoc! {"
4374 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4375 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4376 fn add(a: u32, b: u32) -> u32 {
4377 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ˇ
4378 }
4379 "},
4380 language_with_doc_comments.clone(),
4381 &mut cx,
4382 );
4383
4384 // Test that rewrapping works in Markdown and Plain Text languages.
4385 assert_rewrap(
4386 indoc! {"
4387 # Hello
4388
4389 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.
4390 "},
4391 indoc! {"
4392 # Hello
4393
4394 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4395 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4396 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4397 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4398 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4399 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4400 Integer sit amet scelerisque nisi.
4401 "},
4402 markdown_language,
4403 &mut cx,
4404 );
4405
4406 assert_rewrap(
4407 indoc! {"
4408 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.
4409 "},
4410 indoc! {"
4411 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4412 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4413 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4414 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4415 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4416 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4417 Integer sit amet scelerisque nisi.
4418 "},
4419 plaintext_language,
4420 &mut cx,
4421 );
4422
4423 // Test rewrapping unaligned comments in a selection.
4424 assert_rewrap(
4425 indoc! {"
4426 fn foo() {
4427 if true {
4428 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4429 // Praesent semper egestas tellus id dignissim.ˇ»
4430 do_something();
4431 } else {
4432 //
4433 }
4434 }
4435 "},
4436 indoc! {"
4437 fn foo() {
4438 if true {
4439 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4440 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4441 // egestas tellus id dignissim.ˇ»
4442 do_something();
4443 } else {
4444 //
4445 }
4446 }
4447 "},
4448 language_with_doc_comments.clone(),
4449 &mut cx,
4450 );
4451
4452 assert_rewrap(
4453 indoc! {"
4454 fn foo() {
4455 if true {
4456 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4457 // Praesent semper egestas tellus id dignissim.»
4458 do_something();
4459 } else {
4460 //
4461 }
4462
4463 }
4464 "},
4465 indoc! {"
4466 fn foo() {
4467 if true {
4468 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4469 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4470 // egestas tellus id dignissim.»
4471 do_something();
4472 } else {
4473 //
4474 }
4475
4476 }
4477 "},
4478 language_with_doc_comments.clone(),
4479 &mut cx,
4480 );
4481
4482 #[track_caller]
4483 fn assert_rewrap(
4484 unwrapped_text: &str,
4485 wrapped_text: &str,
4486 language: Arc<Language>,
4487 cx: &mut EditorTestContext,
4488 ) {
4489 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4490 cx.set_state(unwrapped_text);
4491 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4492 cx.assert_editor_state(wrapped_text);
4493 }
4494}
4495
4496#[gpui::test]
4497async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4498 init_test(cx, |_| {});
4499
4500 let mut cx = EditorTestContext::new(cx).await;
4501
4502 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4503 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4504 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4505
4506 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4507 cx.set_state("two ˇfour ˇsix ˇ");
4508 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4509 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4510
4511 // Paste again but with only two cursors. Since the number of cursors doesn't
4512 // match the number of slices in the clipboard, the entire clipboard text
4513 // is pasted at each cursor.
4514 cx.set_state("ˇtwo one✅ four three six five ˇ");
4515 cx.update_editor(|e, cx| {
4516 e.handle_input("( ", cx);
4517 e.paste(&Paste, cx);
4518 e.handle_input(") ", cx);
4519 });
4520 cx.assert_editor_state(
4521 &([
4522 "( one✅ ",
4523 "three ",
4524 "five ) ˇtwo one✅ four three six five ( one✅ ",
4525 "three ",
4526 "five ) ˇ",
4527 ]
4528 .join("\n")),
4529 );
4530
4531 // Cut with three selections, one of which is full-line.
4532 cx.set_state(indoc! {"
4533 1«2ˇ»3
4534 4ˇ567
4535 «8ˇ»9"});
4536 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4537 cx.assert_editor_state(indoc! {"
4538 1ˇ3
4539 ˇ9"});
4540
4541 // Paste with three selections, noticing how the copied selection that was full-line
4542 // gets inserted before the second cursor.
4543 cx.set_state(indoc! {"
4544 1ˇ3
4545 9ˇ
4546 «oˇ»ne"});
4547 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4548 cx.assert_editor_state(indoc! {"
4549 12ˇ3
4550 4567
4551 9ˇ
4552 8ˇne"});
4553
4554 // Copy with a single cursor only, which writes the whole line into the clipboard.
4555 cx.set_state(indoc! {"
4556 The quick brown
4557 fox juˇmps over
4558 the lazy dog"});
4559 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4560 assert_eq!(
4561 cx.read_from_clipboard()
4562 .and_then(|item| item.text().as_deref().map(str::to_string)),
4563 Some("fox jumps over\n".to_string())
4564 );
4565
4566 // Paste with three selections, noticing how the copied full-line selection is inserted
4567 // before the empty selections but replaces the selection that is non-empty.
4568 cx.set_state(indoc! {"
4569 Tˇhe quick brown
4570 «foˇ»x jumps over
4571 tˇhe lazy dog"});
4572 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4573 cx.assert_editor_state(indoc! {"
4574 fox jumps over
4575 Tˇhe quick brown
4576 fox jumps over
4577 ˇx jumps over
4578 fox jumps over
4579 tˇhe lazy dog"});
4580}
4581
4582#[gpui::test]
4583async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4584 init_test(cx, |_| {});
4585
4586 let mut cx = EditorTestContext::new(cx).await;
4587 let language = Arc::new(Language::new(
4588 LanguageConfig::default(),
4589 Some(tree_sitter_rust::LANGUAGE.into()),
4590 ));
4591 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4592
4593 // Cut an indented block, without the leading whitespace.
4594 cx.set_state(indoc! {"
4595 const a: B = (
4596 c(),
4597 «d(
4598 e,
4599 f
4600 )ˇ»
4601 );
4602 "});
4603 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4604 cx.assert_editor_state(indoc! {"
4605 const a: B = (
4606 c(),
4607 ˇ
4608 );
4609 "});
4610
4611 // Paste it at the same position.
4612 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4613 cx.assert_editor_state(indoc! {"
4614 const a: B = (
4615 c(),
4616 d(
4617 e,
4618 f
4619 )ˇ
4620 );
4621 "});
4622
4623 // Paste it at a line with a lower indent level.
4624 cx.set_state(indoc! {"
4625 ˇ
4626 const a: B = (
4627 c(),
4628 );
4629 "});
4630 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4631 cx.assert_editor_state(indoc! {"
4632 d(
4633 e,
4634 f
4635 )ˇ
4636 const a: B = (
4637 c(),
4638 );
4639 "});
4640
4641 // Cut an indented block, with the leading whitespace.
4642 cx.set_state(indoc! {"
4643 const a: B = (
4644 c(),
4645 « d(
4646 e,
4647 f
4648 )
4649 ˇ»);
4650 "});
4651 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4652 cx.assert_editor_state(indoc! {"
4653 const a: B = (
4654 c(),
4655 ˇ);
4656 "});
4657
4658 // Paste it at the same position.
4659 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4660 cx.assert_editor_state(indoc! {"
4661 const a: B = (
4662 c(),
4663 d(
4664 e,
4665 f
4666 )
4667 ˇ);
4668 "});
4669
4670 // Paste it at a line with a higher indent level.
4671 cx.set_state(indoc! {"
4672 const a: B = (
4673 c(),
4674 d(
4675 e,
4676 fˇ
4677 )
4678 );
4679 "});
4680 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4681 cx.assert_editor_state(indoc! {"
4682 const a: B = (
4683 c(),
4684 d(
4685 e,
4686 f d(
4687 e,
4688 f
4689 )
4690 ˇ
4691 )
4692 );
4693 "});
4694}
4695
4696#[gpui::test]
4697fn test_select_all(cx: &mut TestAppContext) {
4698 init_test(cx, |_| {});
4699
4700 let view = cx.add_window(|cx| {
4701 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4702 build_editor(buffer, cx)
4703 });
4704 _ = view.update(cx, |view, cx| {
4705 view.select_all(&SelectAll, cx);
4706 assert_eq!(
4707 view.selections.display_ranges(cx),
4708 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4709 );
4710 });
4711}
4712
4713#[gpui::test]
4714fn test_select_line(cx: &mut TestAppContext) {
4715 init_test(cx, |_| {});
4716
4717 let view = cx.add_window(|cx| {
4718 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4719 build_editor(buffer, cx)
4720 });
4721 _ = view.update(cx, |view, cx| {
4722 view.change_selections(None, cx, |s| {
4723 s.select_display_ranges([
4724 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4725 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4726 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4727 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4728 ])
4729 });
4730 view.select_line(&SelectLine, cx);
4731 assert_eq!(
4732 view.selections.display_ranges(cx),
4733 vec![
4734 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4735 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4736 ]
4737 );
4738 });
4739
4740 _ = view.update(cx, |view, cx| {
4741 view.select_line(&SelectLine, cx);
4742 assert_eq!(
4743 view.selections.display_ranges(cx),
4744 vec![
4745 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4746 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4747 ]
4748 );
4749 });
4750
4751 _ = view.update(cx, |view, cx| {
4752 view.select_line(&SelectLine, cx);
4753 assert_eq!(
4754 view.selections.display_ranges(cx),
4755 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4756 );
4757 });
4758}
4759
4760#[gpui::test]
4761fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4762 init_test(cx, |_| {});
4763
4764 let view = cx.add_window(|cx| {
4765 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4766 build_editor(buffer, cx)
4767 });
4768 _ = view.update(cx, |view, cx| {
4769 view.fold_creases(
4770 vec![
4771 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4772 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4773 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4774 ],
4775 true,
4776 cx,
4777 );
4778 view.change_selections(None, cx, |s| {
4779 s.select_display_ranges([
4780 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4781 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4782 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4783 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4784 ])
4785 });
4786 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4787 });
4788
4789 _ = view.update(cx, |view, cx| {
4790 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4791 assert_eq!(
4792 view.display_text(cx),
4793 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4794 );
4795 assert_eq!(
4796 view.selections.display_ranges(cx),
4797 [
4798 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4799 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4800 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4801 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4802 ]
4803 );
4804 });
4805
4806 _ = view.update(cx, |view, cx| {
4807 view.change_selections(None, cx, |s| {
4808 s.select_display_ranges([
4809 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4810 ])
4811 });
4812 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4813 assert_eq!(
4814 view.display_text(cx),
4815 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4816 );
4817 assert_eq!(
4818 view.selections.display_ranges(cx),
4819 [
4820 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4821 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4822 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4823 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4824 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4825 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4826 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4827 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4828 ]
4829 );
4830 });
4831}
4832
4833#[gpui::test]
4834async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4835 init_test(cx, |_| {});
4836
4837 let mut cx = EditorTestContext::new(cx).await;
4838
4839 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4840 cx.set_state(indoc!(
4841 r#"abc
4842 defˇghi
4843
4844 jk
4845 nlmo
4846 "#
4847 ));
4848
4849 cx.update_editor(|editor, cx| {
4850 editor.add_selection_above(&Default::default(), cx);
4851 });
4852
4853 cx.assert_editor_state(indoc!(
4854 r#"abcˇ
4855 defˇghi
4856
4857 jk
4858 nlmo
4859 "#
4860 ));
4861
4862 cx.update_editor(|editor, cx| {
4863 editor.add_selection_above(&Default::default(), cx);
4864 });
4865
4866 cx.assert_editor_state(indoc!(
4867 r#"abcˇ
4868 defˇghi
4869
4870 jk
4871 nlmo
4872 "#
4873 ));
4874
4875 cx.update_editor(|view, cx| {
4876 view.add_selection_below(&Default::default(), cx);
4877 });
4878
4879 cx.assert_editor_state(indoc!(
4880 r#"abc
4881 defˇghi
4882
4883 jk
4884 nlmo
4885 "#
4886 ));
4887
4888 cx.update_editor(|view, cx| {
4889 view.undo_selection(&Default::default(), cx);
4890 });
4891
4892 cx.assert_editor_state(indoc!(
4893 r#"abcˇ
4894 defˇghi
4895
4896 jk
4897 nlmo
4898 "#
4899 ));
4900
4901 cx.update_editor(|view, cx| {
4902 view.redo_selection(&Default::default(), cx);
4903 });
4904
4905 cx.assert_editor_state(indoc!(
4906 r#"abc
4907 defˇghi
4908
4909 jk
4910 nlmo
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ˇghi
4921
4922 jk
4923 nlmˇo
4924 "#
4925 ));
4926
4927 cx.update_editor(|view, cx| {
4928 view.add_selection_below(&Default::default(), cx);
4929 });
4930
4931 cx.assert_editor_state(indoc!(
4932 r#"abc
4933 defˇghi
4934
4935 jk
4936 nlmˇo
4937 "#
4938 ));
4939
4940 // change selections
4941 cx.set_state(indoc!(
4942 r#"abc
4943 def«ˇg»hi
4944
4945 jk
4946 nlmo
4947 "#
4948 ));
4949
4950 cx.update_editor(|view, cx| {
4951 view.add_selection_below(&Default::default(), cx);
4952 });
4953
4954 cx.assert_editor_state(indoc!(
4955 r#"abc
4956 def«ˇg»hi
4957
4958 jk
4959 nlm«ˇo»
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#"abc
4969 def«ˇg»hi
4970
4971 jk
4972 nlm«ˇo»
4973 "#
4974 ));
4975
4976 cx.update_editor(|view, cx| {
4977 view.add_selection_above(&Default::default(), cx);
4978 });
4979
4980 cx.assert_editor_state(indoc!(
4981 r#"abc
4982 def«ˇg»hi
4983
4984 jk
4985 nlmo
4986 "#
4987 ));
4988
4989 cx.update_editor(|view, cx| {
4990 view.add_selection_above(&Default::default(), cx);
4991 });
4992
4993 cx.assert_editor_state(indoc!(
4994 r#"abc
4995 def«ˇg»hi
4996
4997 jk
4998 nlmo
4999 "#
5000 ));
5001
5002 // Change selections again
5003 cx.set_state(indoc!(
5004 r#"a«bc
5005 defgˇ»hi
5006
5007 jk
5008 nlmo
5009 "#
5010 ));
5011
5012 cx.update_editor(|view, cx| {
5013 view.add_selection_below(&Default::default(), cx);
5014 });
5015
5016 cx.assert_editor_state(indoc!(
5017 r#"a«bcˇ»
5018 d«efgˇ»hi
5019
5020 j«kˇ»
5021 nlmo
5022 "#
5023 ));
5024
5025 cx.update_editor(|view, cx| {
5026 view.add_selection_below(&Default::default(), cx);
5027 });
5028 cx.assert_editor_state(indoc!(
5029 r#"a«bcˇ»
5030 d«efgˇ»hi
5031
5032 j«kˇ»
5033 n«lmoˇ»
5034 "#
5035 ));
5036 cx.update_editor(|view, cx| {
5037 view.add_selection_above(&Default::default(), cx);
5038 });
5039
5040 cx.assert_editor_state(indoc!(
5041 r#"a«bcˇ»
5042 d«efgˇ»hi
5043
5044 j«kˇ»
5045 nlmo
5046 "#
5047 ));
5048
5049 // Change selections again
5050 cx.set_state(indoc!(
5051 r#"abc
5052 d«ˇefghi
5053
5054 jk
5055 nlm»o
5056 "#
5057 ));
5058
5059 cx.update_editor(|view, cx| {
5060 view.add_selection_above(&Default::default(), cx);
5061 });
5062
5063 cx.assert_editor_state(indoc!(
5064 r#"a«ˇbc»
5065 d«ˇef»ghi
5066
5067 j«ˇk»
5068 n«ˇlm»o
5069 "#
5070 ));
5071
5072 cx.update_editor(|view, cx| {
5073 view.add_selection_below(&Default::default(), cx);
5074 });
5075
5076 cx.assert_editor_state(indoc!(
5077 r#"abc
5078 d«ˇef»ghi
5079
5080 j«ˇk»
5081 n«ˇlm»o
5082 "#
5083 ));
5084}
5085
5086#[gpui::test]
5087async fn test_select_next(cx: &mut gpui::TestAppContext) {
5088 init_test(cx, |_| {});
5089
5090 let mut cx = EditorTestContext::new(cx).await;
5091 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5092
5093 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5094 .unwrap();
5095 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5096
5097 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5098 .unwrap();
5099 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5100
5101 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5102 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5103
5104 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5105 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5106
5107 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5108 .unwrap();
5109 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5110
5111 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5112 .unwrap();
5113 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5114}
5115
5116#[gpui::test]
5117async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5118 init_test(cx, |_| {});
5119
5120 let mut cx = EditorTestContext::new(cx).await;
5121 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5122
5123 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5124 .unwrap();
5125 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5126}
5127
5128#[gpui::test]
5129async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5130 init_test(cx, |_| {});
5131
5132 let mut cx = EditorTestContext::new(cx).await;
5133 cx.set_state(
5134 r#"let foo = 2;
5135lˇet foo = 2;
5136let fooˇ = 2;
5137let foo = 2;
5138let foo = ˇ2;"#,
5139 );
5140
5141 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5142 .unwrap();
5143 cx.assert_editor_state(
5144 r#"let foo = 2;
5145«letˇ» foo = 2;
5146let «fooˇ» = 2;
5147let foo = 2;
5148let foo = «2ˇ»;"#,
5149 );
5150
5151 // noop for multiple selections with different contents
5152 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5153 .unwrap();
5154 cx.assert_editor_state(
5155 r#"let foo = 2;
5156«letˇ» foo = 2;
5157let «fooˇ» = 2;
5158let foo = 2;
5159let foo = «2ˇ»;"#,
5160 );
5161}
5162
5163#[gpui::test]
5164async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5165 init_test(cx, |_| {});
5166
5167 let mut cx =
5168 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5169
5170 cx.assert_editor_state(indoc! {"
5171 ˇbbb
5172 ccc
5173
5174 bbb
5175 ccc
5176 "});
5177 cx.dispatch_action(SelectPrevious::default());
5178 cx.assert_editor_state(indoc! {"
5179 «bbbˇ»
5180 ccc
5181
5182 bbb
5183 ccc
5184 "});
5185 cx.dispatch_action(SelectPrevious::default());
5186 cx.assert_editor_state(indoc! {"
5187 «bbbˇ»
5188 ccc
5189
5190 «bbbˇ»
5191 ccc
5192 "});
5193}
5194
5195#[gpui::test]
5196async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5197 init_test(cx, |_| {});
5198
5199 let mut cx = EditorTestContext::new(cx).await;
5200 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5201
5202 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5203 .unwrap();
5204 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5205
5206 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5207 .unwrap();
5208 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5209
5210 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5211 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5212
5213 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5214 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5215
5216 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5217 .unwrap();
5218 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5219
5220 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5221 .unwrap();
5222 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5223
5224 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5225 .unwrap();
5226 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5227}
5228
5229#[gpui::test]
5230async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5231 init_test(cx, |_| {});
5232
5233 let mut cx = EditorTestContext::new(cx).await;
5234 cx.set_state(
5235 r#"let foo = 2;
5236lˇet foo = 2;
5237let fooˇ = 2;
5238let foo = 2;
5239let foo = ˇ2;"#,
5240 );
5241
5242 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5243 .unwrap();
5244 cx.assert_editor_state(
5245 r#"let foo = 2;
5246«letˇ» foo = 2;
5247let «fooˇ» = 2;
5248let foo = 2;
5249let foo = «2ˇ»;"#,
5250 );
5251
5252 // noop for multiple selections with different contents
5253 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5254 .unwrap();
5255 cx.assert_editor_state(
5256 r#"let foo = 2;
5257«letˇ» foo = 2;
5258let «fooˇ» = 2;
5259let foo = 2;
5260let foo = «2ˇ»;"#,
5261 );
5262}
5263
5264#[gpui::test]
5265async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5266 init_test(cx, |_| {});
5267
5268 let mut cx = EditorTestContext::new(cx).await;
5269 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5270
5271 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5272 .unwrap();
5273 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5274
5275 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5276 .unwrap();
5277 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5278
5279 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5280 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5281
5282 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5283 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5284
5285 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5286 .unwrap();
5287 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5288
5289 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5290 .unwrap();
5291 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5292}
5293
5294#[gpui::test]
5295async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5296 init_test(cx, |_| {});
5297
5298 let language = Arc::new(Language::new(
5299 LanguageConfig::default(),
5300 Some(tree_sitter_rust::LANGUAGE.into()),
5301 ));
5302
5303 let text = r#"
5304 use mod1::mod2::{mod3, mod4};
5305
5306 fn fn_1(param1: bool, param2: &str) {
5307 let var1 = "text";
5308 }
5309 "#
5310 .unindent();
5311
5312 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5313 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5314 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5315
5316 editor
5317 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5318 .await;
5319
5320 editor.update(cx, |view, cx| {
5321 view.change_selections(None, cx, |s| {
5322 s.select_display_ranges([
5323 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5324 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5325 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5326 ]);
5327 });
5328 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5329 });
5330 editor.update(cx, |editor, cx| {
5331 assert_text_with_selections(
5332 editor,
5333 indoc! {r#"
5334 use mod1::mod2::{mod3, «mod4ˇ»};
5335
5336 fn fn_1«ˇ(param1: bool, param2: &str)» {
5337 let var1 = "«textˇ»";
5338 }
5339 "#},
5340 cx,
5341 );
5342 });
5343
5344 editor.update(cx, |view, cx| {
5345 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5346 });
5347 editor.update(cx, |editor, cx| {
5348 assert_text_with_selections(
5349 editor,
5350 indoc! {r#"
5351 use mod1::mod2::«{mod3, mod4}ˇ»;
5352
5353 «ˇfn fn_1(param1: bool, param2: &str) {
5354 let var1 = "text";
5355 }»
5356 "#},
5357 cx,
5358 );
5359 });
5360
5361 editor.update(cx, |view, cx| {
5362 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5363 });
5364 assert_eq!(
5365 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5366 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5367 );
5368
5369 // Trying to expand the selected syntax node one more time has no effect.
5370 editor.update(cx, |view, cx| {
5371 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5372 });
5373 assert_eq!(
5374 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5375 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5376 );
5377
5378 editor.update(cx, |view, cx| {
5379 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5380 });
5381 editor.update(cx, |editor, cx| {
5382 assert_text_with_selections(
5383 editor,
5384 indoc! {r#"
5385 use mod1::mod2::«{mod3, mod4}ˇ»;
5386
5387 «ˇfn fn_1(param1: bool, param2: &str) {
5388 let var1 = "text";
5389 }»
5390 "#},
5391 cx,
5392 );
5393 });
5394
5395 editor.update(cx, |view, cx| {
5396 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5397 });
5398 editor.update(cx, |editor, cx| {
5399 assert_text_with_selections(
5400 editor,
5401 indoc! {r#"
5402 use mod1::mod2::{mod3, «mod4ˇ»};
5403
5404 fn fn_1«ˇ(param1: bool, param2: &str)» {
5405 let var1 = "«textˇ»";
5406 }
5407 "#},
5408 cx,
5409 );
5410 });
5411
5412 editor.update(cx, |view, cx| {
5413 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5414 });
5415 editor.update(cx, |editor, cx| {
5416 assert_text_with_selections(
5417 editor,
5418 indoc! {r#"
5419 use mod1::mod2::{mod3, mo«ˇ»d4};
5420
5421 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5422 let var1 = "te«ˇ»xt";
5423 }
5424 "#},
5425 cx,
5426 );
5427 });
5428
5429 // Trying to shrink the selected syntax node one more time has no effect.
5430 editor.update(cx, |view, cx| {
5431 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5432 });
5433 editor.update(cx, |editor, cx| {
5434 assert_text_with_selections(
5435 editor,
5436 indoc! {r#"
5437 use mod1::mod2::{mod3, mo«ˇ»d4};
5438
5439 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5440 let var1 = "te«ˇ»xt";
5441 }
5442 "#},
5443 cx,
5444 );
5445 });
5446
5447 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5448 // a fold.
5449 editor.update(cx, |view, cx| {
5450 view.fold_creases(
5451 vec![
5452 Crease::simple(
5453 Point::new(0, 21)..Point::new(0, 24),
5454 FoldPlaceholder::test(),
5455 ),
5456 Crease::simple(
5457 Point::new(3, 20)..Point::new(3, 22),
5458 FoldPlaceholder::test(),
5459 ),
5460 ],
5461 true,
5462 cx,
5463 );
5464 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5465 });
5466 editor.update(cx, |editor, cx| {
5467 assert_text_with_selections(
5468 editor,
5469 indoc! {r#"
5470 use mod1::mod2::«{mod3, mod4}ˇ»;
5471
5472 fn fn_1«ˇ(param1: bool, param2: &str)» {
5473 «let var1 = "text";ˇ»
5474 }
5475 "#},
5476 cx,
5477 );
5478 });
5479}
5480
5481#[gpui::test]
5482async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5483 init_test(cx, |_| {});
5484
5485 let language = Arc::new(
5486 Language::new(
5487 LanguageConfig {
5488 brackets: BracketPairConfig {
5489 pairs: vec![
5490 BracketPair {
5491 start: "{".to_string(),
5492 end: "}".to_string(),
5493 close: false,
5494 surround: false,
5495 newline: true,
5496 },
5497 BracketPair {
5498 start: "(".to_string(),
5499 end: ")".to_string(),
5500 close: false,
5501 surround: false,
5502 newline: true,
5503 },
5504 ],
5505 ..Default::default()
5506 },
5507 ..Default::default()
5508 },
5509 Some(tree_sitter_rust::LANGUAGE.into()),
5510 )
5511 .with_indents_query(
5512 r#"
5513 (_ "(" ")" @end) @indent
5514 (_ "{" "}" @end) @indent
5515 "#,
5516 )
5517 .unwrap(),
5518 );
5519
5520 let text = "fn a() {}";
5521
5522 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5523 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5524 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5525 editor
5526 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5527 .await;
5528
5529 editor.update(cx, |editor, cx| {
5530 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5531 editor.newline(&Newline, cx);
5532 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5533 assert_eq!(
5534 editor.selections.ranges(cx),
5535 &[
5536 Point::new(1, 4)..Point::new(1, 4),
5537 Point::new(3, 4)..Point::new(3, 4),
5538 Point::new(5, 0)..Point::new(5, 0)
5539 ]
5540 );
5541 });
5542}
5543
5544#[gpui::test]
5545async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5546 init_test(cx, |_| {});
5547
5548 {
5549 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5550 cx.set_state(indoc! {"
5551 impl A {
5552
5553 fn b() {}
5554
5555 «fn c() {
5556
5557 }ˇ»
5558 }
5559 "});
5560
5561 cx.update_editor(|editor, cx| {
5562 editor.autoindent(&Default::default(), cx);
5563 });
5564
5565 cx.assert_editor_state(indoc! {"
5566 impl A {
5567
5568 fn b() {}
5569
5570 «fn c() {
5571
5572 }ˇ»
5573 }
5574 "});
5575 }
5576
5577 {
5578 let mut cx = EditorTestContext::new_multibuffer(
5579 cx,
5580 [indoc! { "
5581 impl A {
5582 «
5583 // a
5584 fn b(){}
5585 »
5586 «
5587 }
5588 fn c(){}
5589 »
5590 "}],
5591 );
5592
5593 let buffer = cx.update_editor(|editor, cx| {
5594 let buffer = editor.buffer().update(cx, |buffer, _| {
5595 buffer.all_buffers().iter().next().unwrap().clone()
5596 });
5597 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5598 buffer
5599 });
5600
5601 cx.run_until_parked();
5602 cx.update_editor(|editor, cx| {
5603 editor.select_all(&Default::default(), cx);
5604 editor.autoindent(&Default::default(), cx)
5605 });
5606 cx.run_until_parked();
5607
5608 cx.update(|cx| {
5609 pretty_assertions::assert_eq!(
5610 buffer.read(cx).text(),
5611 indoc! { "
5612 impl A {
5613
5614 // a
5615 fn b(){}
5616
5617
5618 }
5619 fn c(){}
5620
5621 " }
5622 )
5623 });
5624 }
5625}
5626
5627#[gpui::test]
5628async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5629 init_test(cx, |_| {});
5630
5631 let mut cx = EditorTestContext::new(cx).await;
5632
5633 let language = Arc::new(Language::new(
5634 LanguageConfig {
5635 brackets: BracketPairConfig {
5636 pairs: vec![
5637 BracketPair {
5638 start: "{".to_string(),
5639 end: "}".to_string(),
5640 close: true,
5641 surround: true,
5642 newline: true,
5643 },
5644 BracketPair {
5645 start: "(".to_string(),
5646 end: ")".to_string(),
5647 close: true,
5648 surround: true,
5649 newline: true,
5650 },
5651 BracketPair {
5652 start: "/*".to_string(),
5653 end: " */".to_string(),
5654 close: true,
5655 surround: true,
5656 newline: true,
5657 },
5658 BracketPair {
5659 start: "[".to_string(),
5660 end: "]".to_string(),
5661 close: false,
5662 surround: false,
5663 newline: true,
5664 },
5665 BracketPair {
5666 start: "\"".to_string(),
5667 end: "\"".to_string(),
5668 close: true,
5669 surround: true,
5670 newline: false,
5671 },
5672 BracketPair {
5673 start: "<".to_string(),
5674 end: ">".to_string(),
5675 close: false,
5676 surround: true,
5677 newline: true,
5678 },
5679 ],
5680 ..Default::default()
5681 },
5682 autoclose_before: "})]".to_string(),
5683 ..Default::default()
5684 },
5685 Some(tree_sitter_rust::LANGUAGE.into()),
5686 ));
5687
5688 cx.language_registry().add(language.clone());
5689 cx.update_buffer(|buffer, cx| {
5690 buffer.set_language(Some(language), cx);
5691 });
5692
5693 cx.set_state(
5694 &r#"
5695 🏀ˇ
5696 εˇ
5697 ❤️ˇ
5698 "#
5699 .unindent(),
5700 );
5701
5702 // autoclose multiple nested brackets at multiple cursors
5703 cx.update_editor(|view, cx| {
5704 view.handle_input("{", cx);
5705 view.handle_input("{", cx);
5706 view.handle_input("{", cx);
5707 });
5708 cx.assert_editor_state(
5709 &"
5710 🏀{{{ˇ}}}
5711 ε{{{ˇ}}}
5712 ❤️{{{ˇ}}}
5713 "
5714 .unindent(),
5715 );
5716
5717 // insert a different closing bracket
5718 cx.update_editor(|view, cx| {
5719 view.handle_input(")", cx);
5720 });
5721 cx.assert_editor_state(
5722 &"
5723 🏀{{{)ˇ}}}
5724 ε{{{)ˇ}}}
5725 ❤️{{{)ˇ}}}
5726 "
5727 .unindent(),
5728 );
5729
5730 // skip over the auto-closed brackets when typing a closing bracket
5731 cx.update_editor(|view, cx| {
5732 view.move_right(&MoveRight, cx);
5733 view.handle_input("}", cx);
5734 view.handle_input("}", cx);
5735 view.handle_input("}", cx);
5736 });
5737 cx.assert_editor_state(
5738 &"
5739 🏀{{{)}}}}ˇ
5740 ε{{{)}}}}ˇ
5741 ❤️{{{)}}}}ˇ
5742 "
5743 .unindent(),
5744 );
5745
5746 // autoclose multi-character pairs
5747 cx.set_state(
5748 &"
5749 ˇ
5750 ˇ
5751 "
5752 .unindent(),
5753 );
5754 cx.update_editor(|view, cx| {
5755 view.handle_input("/", cx);
5756 view.handle_input("*", cx);
5757 });
5758 cx.assert_editor_state(
5759 &"
5760 /*ˇ */
5761 /*ˇ */
5762 "
5763 .unindent(),
5764 );
5765
5766 // one cursor autocloses a multi-character pair, one cursor
5767 // does not autoclose.
5768 cx.set_state(
5769 &"
5770 /ˇ
5771 ˇ
5772 "
5773 .unindent(),
5774 );
5775 cx.update_editor(|view, cx| view.handle_input("*", cx));
5776 cx.assert_editor_state(
5777 &"
5778 /*ˇ */
5779 *ˇ
5780 "
5781 .unindent(),
5782 );
5783
5784 // Don't autoclose if the next character isn't whitespace and isn't
5785 // listed in the language's "autoclose_before" section.
5786 cx.set_state("ˇa b");
5787 cx.update_editor(|view, cx| view.handle_input("{", cx));
5788 cx.assert_editor_state("{ˇa b");
5789
5790 // Don't autoclose if `close` is false for the bracket pair
5791 cx.set_state("ˇ");
5792 cx.update_editor(|view, cx| view.handle_input("[", cx));
5793 cx.assert_editor_state("[ˇ");
5794
5795 // Surround with brackets if text is selected
5796 cx.set_state("«aˇ» b");
5797 cx.update_editor(|view, cx| view.handle_input("{", cx));
5798 cx.assert_editor_state("{«aˇ»} b");
5799
5800 // Autclose pair where the start and end characters are the same
5801 cx.set_state("aˇ");
5802 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5803 cx.assert_editor_state("a\"ˇ\"");
5804 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5805 cx.assert_editor_state("a\"\"ˇ");
5806
5807 // Don't autoclose pair if autoclose is disabled
5808 cx.set_state("ˇ");
5809 cx.update_editor(|view, cx| view.handle_input("<", cx));
5810 cx.assert_editor_state("<ˇ");
5811
5812 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5813 cx.set_state("«aˇ» b");
5814 cx.update_editor(|view, cx| view.handle_input("<", cx));
5815 cx.assert_editor_state("<«aˇ»> b");
5816}
5817
5818#[gpui::test]
5819async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5820 init_test(cx, |settings| {
5821 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5822 });
5823
5824 let mut cx = EditorTestContext::new(cx).await;
5825
5826 let language = Arc::new(Language::new(
5827 LanguageConfig {
5828 brackets: BracketPairConfig {
5829 pairs: vec![
5830 BracketPair {
5831 start: "{".to_string(),
5832 end: "}".to_string(),
5833 close: true,
5834 surround: true,
5835 newline: true,
5836 },
5837 BracketPair {
5838 start: "(".to_string(),
5839 end: ")".to_string(),
5840 close: true,
5841 surround: true,
5842 newline: true,
5843 },
5844 BracketPair {
5845 start: "[".to_string(),
5846 end: "]".to_string(),
5847 close: false,
5848 surround: false,
5849 newline: true,
5850 },
5851 ],
5852 ..Default::default()
5853 },
5854 autoclose_before: "})]".to_string(),
5855 ..Default::default()
5856 },
5857 Some(tree_sitter_rust::LANGUAGE.into()),
5858 ));
5859
5860 cx.language_registry().add(language.clone());
5861 cx.update_buffer(|buffer, cx| {
5862 buffer.set_language(Some(language), cx);
5863 });
5864
5865 cx.set_state(
5866 &"
5867 ˇ
5868 ˇ
5869 ˇ
5870 "
5871 .unindent(),
5872 );
5873
5874 // ensure only matching closing brackets are skipped over
5875 cx.update_editor(|view, cx| {
5876 view.handle_input("}", cx);
5877 view.move_left(&MoveLeft, cx);
5878 view.handle_input(")", cx);
5879 view.move_left(&MoveLeft, cx);
5880 });
5881 cx.assert_editor_state(
5882 &"
5883 ˇ)}
5884 ˇ)}
5885 ˇ)}
5886 "
5887 .unindent(),
5888 );
5889
5890 // skip-over closing brackets at multiple cursors
5891 cx.update_editor(|view, cx| {
5892 view.handle_input(")", cx);
5893 view.handle_input("}", cx);
5894 });
5895 cx.assert_editor_state(
5896 &"
5897 )}ˇ
5898 )}ˇ
5899 )}ˇ
5900 "
5901 .unindent(),
5902 );
5903
5904 // ignore non-close brackets
5905 cx.update_editor(|view, cx| {
5906 view.handle_input("]", cx);
5907 view.move_left(&MoveLeft, cx);
5908 view.handle_input("]", cx);
5909 });
5910 cx.assert_editor_state(
5911 &"
5912 )}]ˇ]
5913 )}]ˇ]
5914 )}]ˇ]
5915 "
5916 .unindent(),
5917 );
5918}
5919
5920#[gpui::test]
5921async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5922 init_test(cx, |_| {});
5923
5924 let mut cx = EditorTestContext::new(cx).await;
5925
5926 let html_language = Arc::new(
5927 Language::new(
5928 LanguageConfig {
5929 name: "HTML".into(),
5930 brackets: BracketPairConfig {
5931 pairs: vec![
5932 BracketPair {
5933 start: "<".into(),
5934 end: ">".into(),
5935 close: true,
5936 ..Default::default()
5937 },
5938 BracketPair {
5939 start: "{".into(),
5940 end: "}".into(),
5941 close: true,
5942 ..Default::default()
5943 },
5944 BracketPair {
5945 start: "(".into(),
5946 end: ")".into(),
5947 close: true,
5948 ..Default::default()
5949 },
5950 ],
5951 ..Default::default()
5952 },
5953 autoclose_before: "})]>".into(),
5954 ..Default::default()
5955 },
5956 Some(tree_sitter_html::language()),
5957 )
5958 .with_injection_query(
5959 r#"
5960 (script_element
5961 (raw_text) @content
5962 (#set! "language" "javascript"))
5963 "#,
5964 )
5965 .unwrap(),
5966 );
5967
5968 let javascript_language = Arc::new(Language::new(
5969 LanguageConfig {
5970 name: "JavaScript".into(),
5971 brackets: BracketPairConfig {
5972 pairs: vec![
5973 BracketPair {
5974 start: "/*".into(),
5975 end: " */".into(),
5976 close: true,
5977 ..Default::default()
5978 },
5979 BracketPair {
5980 start: "{".into(),
5981 end: "}".into(),
5982 close: true,
5983 ..Default::default()
5984 },
5985 BracketPair {
5986 start: "(".into(),
5987 end: ")".into(),
5988 close: true,
5989 ..Default::default()
5990 },
5991 ],
5992 ..Default::default()
5993 },
5994 autoclose_before: "})]>".into(),
5995 ..Default::default()
5996 },
5997 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5998 ));
5999
6000 cx.language_registry().add(html_language.clone());
6001 cx.language_registry().add(javascript_language.clone());
6002
6003 cx.update_buffer(|buffer, cx| {
6004 buffer.set_language(Some(html_language), cx);
6005 });
6006
6007 cx.set_state(
6008 &r#"
6009 <body>ˇ
6010 <script>
6011 var x = 1;ˇ
6012 </script>
6013 </body>ˇ
6014 "#
6015 .unindent(),
6016 );
6017
6018 // Precondition: different languages are active at different locations.
6019 cx.update_editor(|editor, cx| {
6020 let snapshot = editor.snapshot(cx);
6021 let cursors = editor.selections.ranges::<usize>(cx);
6022 let languages = cursors
6023 .iter()
6024 .map(|c| snapshot.language_at(c.start).unwrap().name())
6025 .collect::<Vec<_>>();
6026 assert_eq!(
6027 languages,
6028 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6029 );
6030 });
6031
6032 // Angle brackets autoclose in HTML, but not JavaScript.
6033 cx.update_editor(|editor, cx| {
6034 editor.handle_input("<", cx);
6035 editor.handle_input("a", cx);
6036 });
6037 cx.assert_editor_state(
6038 &r#"
6039 <body><aˇ>
6040 <script>
6041 var x = 1;<aˇ
6042 </script>
6043 </body><aˇ>
6044 "#
6045 .unindent(),
6046 );
6047
6048 // Curly braces and parens autoclose in both HTML and JavaScript.
6049 cx.update_editor(|editor, cx| {
6050 editor.handle_input(" b=", cx);
6051 editor.handle_input("{", cx);
6052 editor.handle_input("c", cx);
6053 editor.handle_input("(", cx);
6054 });
6055 cx.assert_editor_state(
6056 &r#"
6057 <body><a b={c(ˇ)}>
6058 <script>
6059 var x = 1;<a b={c(ˇ)}
6060 </script>
6061 </body><a b={c(ˇ)}>
6062 "#
6063 .unindent(),
6064 );
6065
6066 // Brackets that were already autoclosed are skipped.
6067 cx.update_editor(|editor, cx| {
6068 editor.handle_input(")", cx);
6069 editor.handle_input("d", cx);
6070 editor.handle_input("}", cx);
6071 });
6072 cx.assert_editor_state(
6073 &r#"
6074 <body><a b={c()d}ˇ>
6075 <script>
6076 var x = 1;<a b={c()d}ˇ
6077 </script>
6078 </body><a b={c()d}ˇ>
6079 "#
6080 .unindent(),
6081 );
6082 cx.update_editor(|editor, cx| {
6083 editor.handle_input(">", cx);
6084 });
6085 cx.assert_editor_state(
6086 &r#"
6087 <body><a b={c()d}>ˇ
6088 <script>
6089 var x = 1;<a b={c()d}>ˇ
6090 </script>
6091 </body><a b={c()d}>ˇ
6092 "#
6093 .unindent(),
6094 );
6095
6096 // Reset
6097 cx.set_state(
6098 &r#"
6099 <body>ˇ
6100 <script>
6101 var x = 1;ˇ
6102 </script>
6103 </body>ˇ
6104 "#
6105 .unindent(),
6106 );
6107
6108 cx.update_editor(|editor, cx| {
6109 editor.handle_input("<", cx);
6110 });
6111 cx.assert_editor_state(
6112 &r#"
6113 <body><ˇ>
6114 <script>
6115 var x = 1;<ˇ
6116 </script>
6117 </body><ˇ>
6118 "#
6119 .unindent(),
6120 );
6121
6122 // When backspacing, the closing angle brackets are removed.
6123 cx.update_editor(|editor, cx| {
6124 editor.backspace(&Backspace, cx);
6125 });
6126 cx.assert_editor_state(
6127 &r#"
6128 <body>ˇ
6129 <script>
6130 var x = 1;ˇ
6131 </script>
6132 </body>ˇ
6133 "#
6134 .unindent(),
6135 );
6136
6137 // Block comments autoclose in JavaScript, but not HTML.
6138 cx.update_editor(|editor, cx| {
6139 editor.handle_input("/", cx);
6140 editor.handle_input("*", cx);
6141 });
6142 cx.assert_editor_state(
6143 &r#"
6144 <body>/*ˇ
6145 <script>
6146 var x = 1;/*ˇ */
6147 </script>
6148 </body>/*ˇ
6149 "#
6150 .unindent(),
6151 );
6152}
6153
6154#[gpui::test]
6155async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6156 init_test(cx, |_| {});
6157
6158 let mut cx = EditorTestContext::new(cx).await;
6159
6160 let rust_language = Arc::new(
6161 Language::new(
6162 LanguageConfig {
6163 name: "Rust".into(),
6164 brackets: serde_json::from_value(json!([
6165 { "start": "{", "end": "}", "close": true, "newline": true },
6166 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6167 ]))
6168 .unwrap(),
6169 autoclose_before: "})]>".into(),
6170 ..Default::default()
6171 },
6172 Some(tree_sitter_rust::LANGUAGE.into()),
6173 )
6174 .with_override_query("(string_literal) @string")
6175 .unwrap(),
6176 );
6177
6178 cx.language_registry().add(rust_language.clone());
6179 cx.update_buffer(|buffer, cx| {
6180 buffer.set_language(Some(rust_language), cx);
6181 });
6182
6183 cx.set_state(
6184 &r#"
6185 let x = ˇ
6186 "#
6187 .unindent(),
6188 );
6189
6190 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6191 cx.update_editor(|editor, cx| {
6192 editor.handle_input("\"", cx);
6193 });
6194 cx.assert_editor_state(
6195 &r#"
6196 let x = "ˇ"
6197 "#
6198 .unindent(),
6199 );
6200
6201 // Inserting another quotation mark. The cursor moves across the existing
6202 // automatically-inserted quotation mark.
6203 cx.update_editor(|editor, cx| {
6204 editor.handle_input("\"", cx);
6205 });
6206 cx.assert_editor_state(
6207 &r#"
6208 let x = ""ˇ
6209 "#
6210 .unindent(),
6211 );
6212
6213 // Reset
6214 cx.set_state(
6215 &r#"
6216 let x = ˇ
6217 "#
6218 .unindent(),
6219 );
6220
6221 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6222 cx.update_editor(|editor, cx| {
6223 editor.handle_input("\"", cx);
6224 editor.handle_input(" ", cx);
6225 editor.move_left(&Default::default(), cx);
6226 editor.handle_input("\\", cx);
6227 editor.handle_input("\"", cx);
6228 });
6229 cx.assert_editor_state(
6230 &r#"
6231 let x = "\"ˇ "
6232 "#
6233 .unindent(),
6234 );
6235
6236 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6237 // mark. Nothing is inserted.
6238 cx.update_editor(|editor, cx| {
6239 editor.move_right(&Default::default(), cx);
6240 editor.handle_input("\"", cx);
6241 });
6242 cx.assert_editor_state(
6243 &r#"
6244 let x = "\" "ˇ
6245 "#
6246 .unindent(),
6247 );
6248}
6249
6250#[gpui::test]
6251async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6252 init_test(cx, |_| {});
6253
6254 let language = Arc::new(Language::new(
6255 LanguageConfig {
6256 brackets: BracketPairConfig {
6257 pairs: vec![
6258 BracketPair {
6259 start: "{".to_string(),
6260 end: "}".to_string(),
6261 close: true,
6262 surround: true,
6263 newline: true,
6264 },
6265 BracketPair {
6266 start: "/* ".to_string(),
6267 end: "*/".to_string(),
6268 close: true,
6269 surround: true,
6270 ..Default::default()
6271 },
6272 ],
6273 ..Default::default()
6274 },
6275 ..Default::default()
6276 },
6277 Some(tree_sitter_rust::LANGUAGE.into()),
6278 ));
6279
6280 let text = r#"
6281 a
6282 b
6283 c
6284 "#
6285 .unindent();
6286
6287 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6288 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6289 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6290 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6291 .await;
6292
6293 view.update(cx, |view, cx| {
6294 view.change_selections(None, cx, |s| {
6295 s.select_display_ranges([
6296 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6297 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6298 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6299 ])
6300 });
6301
6302 view.handle_input("{", cx);
6303 view.handle_input("{", cx);
6304 view.handle_input("{", cx);
6305 assert_eq!(
6306 view.text(cx),
6307 "
6308 {{{a}}}
6309 {{{b}}}
6310 {{{c}}}
6311 "
6312 .unindent()
6313 );
6314 assert_eq!(
6315 view.selections.display_ranges(cx),
6316 [
6317 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6318 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6319 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6320 ]
6321 );
6322
6323 view.undo(&Undo, cx);
6324 view.undo(&Undo, cx);
6325 view.undo(&Undo, cx);
6326 assert_eq!(
6327 view.text(cx),
6328 "
6329 a
6330 b
6331 c
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 view.selections.display_ranges(cx),
6337 [
6338 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6339 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6340 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6341 ]
6342 );
6343
6344 // Ensure inserting the first character of a multi-byte bracket pair
6345 // doesn't surround the selections with the bracket.
6346 view.handle_input("/", cx);
6347 assert_eq!(
6348 view.text(cx),
6349 "
6350 /
6351 /
6352 /
6353 "
6354 .unindent()
6355 );
6356 assert_eq!(
6357 view.selections.display_ranges(cx),
6358 [
6359 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6360 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6361 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6362 ]
6363 );
6364
6365 view.undo(&Undo, cx);
6366 assert_eq!(
6367 view.text(cx),
6368 "
6369 a
6370 b
6371 c
6372 "
6373 .unindent()
6374 );
6375 assert_eq!(
6376 view.selections.display_ranges(cx),
6377 [
6378 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6379 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6380 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6381 ]
6382 );
6383
6384 // Ensure inserting the last character of a multi-byte bracket pair
6385 // doesn't surround the selections with the bracket.
6386 view.handle_input("*", cx);
6387 assert_eq!(
6388 view.text(cx),
6389 "
6390 *
6391 *
6392 *
6393 "
6394 .unindent()
6395 );
6396 assert_eq!(
6397 view.selections.display_ranges(cx),
6398 [
6399 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6400 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6401 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6402 ]
6403 );
6404 });
6405}
6406
6407#[gpui::test]
6408async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6409 init_test(cx, |_| {});
6410
6411 let language = Arc::new(Language::new(
6412 LanguageConfig {
6413 brackets: BracketPairConfig {
6414 pairs: vec![BracketPair {
6415 start: "{".to_string(),
6416 end: "}".to_string(),
6417 close: true,
6418 surround: true,
6419 newline: true,
6420 }],
6421 ..Default::default()
6422 },
6423 autoclose_before: "}".to_string(),
6424 ..Default::default()
6425 },
6426 Some(tree_sitter_rust::LANGUAGE.into()),
6427 ));
6428
6429 let text = r#"
6430 a
6431 b
6432 c
6433 "#
6434 .unindent();
6435
6436 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6437 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6438 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6439 editor
6440 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6441 .await;
6442
6443 editor.update(cx, |editor, cx| {
6444 editor.change_selections(None, cx, |s| {
6445 s.select_ranges([
6446 Point::new(0, 1)..Point::new(0, 1),
6447 Point::new(1, 1)..Point::new(1, 1),
6448 Point::new(2, 1)..Point::new(2, 1),
6449 ])
6450 });
6451
6452 editor.handle_input("{", cx);
6453 editor.handle_input("{", cx);
6454 editor.handle_input("_", cx);
6455 assert_eq!(
6456 editor.text(cx),
6457 "
6458 a{{_}}
6459 b{{_}}
6460 c{{_}}
6461 "
6462 .unindent()
6463 );
6464 assert_eq!(
6465 editor.selections.ranges::<Point>(cx),
6466 [
6467 Point::new(0, 4)..Point::new(0, 4),
6468 Point::new(1, 4)..Point::new(1, 4),
6469 Point::new(2, 4)..Point::new(2, 4)
6470 ]
6471 );
6472
6473 editor.backspace(&Default::default(), cx);
6474 editor.backspace(&Default::default(), cx);
6475 assert_eq!(
6476 editor.text(cx),
6477 "
6478 a{}
6479 b{}
6480 c{}
6481 "
6482 .unindent()
6483 );
6484 assert_eq!(
6485 editor.selections.ranges::<Point>(cx),
6486 [
6487 Point::new(0, 2)..Point::new(0, 2),
6488 Point::new(1, 2)..Point::new(1, 2),
6489 Point::new(2, 2)..Point::new(2, 2)
6490 ]
6491 );
6492
6493 editor.delete_to_previous_word_start(&Default::default(), cx);
6494 assert_eq!(
6495 editor.text(cx),
6496 "
6497 a
6498 b
6499 c
6500 "
6501 .unindent()
6502 );
6503 assert_eq!(
6504 editor.selections.ranges::<Point>(cx),
6505 [
6506 Point::new(0, 1)..Point::new(0, 1),
6507 Point::new(1, 1)..Point::new(1, 1),
6508 Point::new(2, 1)..Point::new(2, 1)
6509 ]
6510 );
6511 });
6512}
6513
6514#[gpui::test]
6515async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6516 init_test(cx, |settings| {
6517 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6518 });
6519
6520 let mut cx = EditorTestContext::new(cx).await;
6521
6522 let language = Arc::new(Language::new(
6523 LanguageConfig {
6524 brackets: BracketPairConfig {
6525 pairs: vec![
6526 BracketPair {
6527 start: "{".to_string(),
6528 end: "}".to_string(),
6529 close: true,
6530 surround: true,
6531 newline: true,
6532 },
6533 BracketPair {
6534 start: "(".to_string(),
6535 end: ")".to_string(),
6536 close: true,
6537 surround: true,
6538 newline: true,
6539 },
6540 BracketPair {
6541 start: "[".to_string(),
6542 end: "]".to_string(),
6543 close: false,
6544 surround: true,
6545 newline: true,
6546 },
6547 ],
6548 ..Default::default()
6549 },
6550 autoclose_before: "})]".to_string(),
6551 ..Default::default()
6552 },
6553 Some(tree_sitter_rust::LANGUAGE.into()),
6554 ));
6555
6556 cx.language_registry().add(language.clone());
6557 cx.update_buffer(|buffer, cx| {
6558 buffer.set_language(Some(language), cx);
6559 });
6560
6561 cx.set_state(
6562 &"
6563 {(ˇ)}
6564 [[ˇ]]
6565 {(ˇ)}
6566 "
6567 .unindent(),
6568 );
6569
6570 cx.update_editor(|view, cx| {
6571 view.backspace(&Default::default(), cx);
6572 view.backspace(&Default::default(), cx);
6573 });
6574
6575 cx.assert_editor_state(
6576 &"
6577 ˇ
6578 ˇ]]
6579 ˇ
6580 "
6581 .unindent(),
6582 );
6583
6584 cx.update_editor(|view, cx| {
6585 view.handle_input("{", cx);
6586 view.handle_input("{", cx);
6587 view.move_right(&MoveRight, cx);
6588 view.move_right(&MoveRight, cx);
6589 view.move_left(&MoveLeft, cx);
6590 view.move_left(&MoveLeft, cx);
6591 view.backspace(&Default::default(), cx);
6592 });
6593
6594 cx.assert_editor_state(
6595 &"
6596 {ˇ}
6597 {ˇ}]]
6598 {ˇ}
6599 "
6600 .unindent(),
6601 );
6602
6603 cx.update_editor(|view, cx| {
6604 view.backspace(&Default::default(), cx);
6605 });
6606
6607 cx.assert_editor_state(
6608 &"
6609 ˇ
6610 ˇ]]
6611 ˇ
6612 "
6613 .unindent(),
6614 );
6615}
6616
6617#[gpui::test]
6618async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6619 init_test(cx, |_| {});
6620
6621 let language = Arc::new(Language::new(
6622 LanguageConfig::default(),
6623 Some(tree_sitter_rust::LANGUAGE.into()),
6624 ));
6625
6626 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6627 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6628 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6629 editor
6630 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6631 .await;
6632
6633 editor.update(cx, |editor, cx| {
6634 editor.set_auto_replace_emoji_shortcode(true);
6635
6636 editor.handle_input("Hello ", cx);
6637 editor.handle_input(":wave", cx);
6638 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6639
6640 editor.handle_input(":", cx);
6641 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6642
6643 editor.handle_input(" :smile", cx);
6644 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6645
6646 editor.handle_input(":", cx);
6647 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6648
6649 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6650 editor.handle_input(":wave", cx);
6651 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6652
6653 editor.handle_input(":", cx);
6654 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6655
6656 editor.handle_input(":1", cx);
6657 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6658
6659 editor.handle_input(":", cx);
6660 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6661
6662 // Ensure shortcode does not get replaced when it is part of a word
6663 editor.handle_input(" Test:wave", cx);
6664 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6665
6666 editor.handle_input(":", cx);
6667 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6668
6669 editor.set_auto_replace_emoji_shortcode(false);
6670
6671 // Ensure shortcode does not get replaced when auto replace is off
6672 editor.handle_input(" :wave", cx);
6673 assert_eq!(
6674 editor.text(cx),
6675 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6676 );
6677
6678 editor.handle_input(":", cx);
6679 assert_eq!(
6680 editor.text(cx),
6681 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6682 );
6683 });
6684}
6685
6686#[gpui::test]
6687async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6688 init_test(cx, |_| {});
6689
6690 let (text, insertion_ranges) = marked_text_ranges(
6691 indoc! {"
6692 ˇ
6693 "},
6694 false,
6695 );
6696
6697 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6698 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6699
6700 _ = editor.update(cx, |editor, cx| {
6701 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6702
6703 editor
6704 .insert_snippet(&insertion_ranges, snippet, cx)
6705 .unwrap();
6706
6707 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6708 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6709 assert_eq!(editor.text(cx), expected_text);
6710 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6711 }
6712
6713 assert(
6714 editor,
6715 cx,
6716 indoc! {"
6717 type «» =•
6718 "},
6719 );
6720
6721 assert!(editor.context_menu_visible(), "There should be a matches");
6722 });
6723}
6724
6725#[gpui::test]
6726async fn test_snippets(cx: &mut gpui::TestAppContext) {
6727 init_test(cx, |_| {});
6728
6729 let (text, insertion_ranges) = marked_text_ranges(
6730 indoc! {"
6731 a.ˇ b
6732 a.ˇ b
6733 a.ˇ b
6734 "},
6735 false,
6736 );
6737
6738 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6739 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6740
6741 editor.update(cx, |editor, cx| {
6742 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6743
6744 editor
6745 .insert_snippet(&insertion_ranges, snippet, cx)
6746 .unwrap();
6747
6748 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6749 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6750 assert_eq!(editor.text(cx), expected_text);
6751 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6752 }
6753
6754 assert(
6755 editor,
6756 cx,
6757 indoc! {"
6758 a.f(«one», two, «three») b
6759 a.f(«one», two, «three») b
6760 a.f(«one», two, «three») b
6761 "},
6762 );
6763
6764 // Can't move earlier than the first tab stop
6765 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6766 assert(
6767 editor,
6768 cx,
6769 indoc! {"
6770 a.f(«one», two, «three») b
6771 a.f(«one», two, «three») b
6772 a.f(«one», two, «three») b
6773 "},
6774 );
6775
6776 assert!(editor.move_to_next_snippet_tabstop(cx));
6777 assert(
6778 editor,
6779 cx,
6780 indoc! {"
6781 a.f(one, «two», three) b
6782 a.f(one, «two», three) b
6783 a.f(one, «two», three) b
6784 "},
6785 );
6786
6787 editor.move_to_prev_snippet_tabstop(cx);
6788 assert(
6789 editor,
6790 cx,
6791 indoc! {"
6792 a.f(«one», two, «three») b
6793 a.f(«one», two, «three») b
6794 a.f(«one», two, «three») b
6795 "},
6796 );
6797
6798 assert!(editor.move_to_next_snippet_tabstop(cx));
6799 assert(
6800 editor,
6801 cx,
6802 indoc! {"
6803 a.f(one, «two», three) b
6804 a.f(one, «two», three) b
6805 a.f(one, «two», three) b
6806 "},
6807 );
6808 assert!(editor.move_to_next_snippet_tabstop(cx));
6809 assert(
6810 editor,
6811 cx,
6812 indoc! {"
6813 a.f(one, two, three)ˇ b
6814 a.f(one, two, three)ˇ b
6815 a.f(one, two, three)ˇ b
6816 "},
6817 );
6818
6819 // As soon as the last tab stop is reached, snippet state is gone
6820 editor.move_to_prev_snippet_tabstop(cx);
6821 assert(
6822 editor,
6823 cx,
6824 indoc! {"
6825 a.f(one, two, three)ˇ b
6826 a.f(one, two, three)ˇ b
6827 a.f(one, two, three)ˇ b
6828 "},
6829 );
6830 });
6831}
6832
6833#[gpui::test]
6834async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6835 init_test(cx, |_| {});
6836
6837 let fs = FakeFs::new(cx.executor());
6838 fs.insert_file("/file.rs", Default::default()).await;
6839
6840 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6841
6842 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6843 language_registry.add(rust_lang());
6844 let mut fake_servers = language_registry.register_fake_lsp(
6845 "Rust",
6846 FakeLspAdapter {
6847 capabilities: lsp::ServerCapabilities {
6848 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6849 ..Default::default()
6850 },
6851 ..Default::default()
6852 },
6853 );
6854
6855 let buffer = project
6856 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6857 .await
6858 .unwrap();
6859
6860 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6861 let (editor, cx) =
6862 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6863 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6864 assert!(cx.read(|cx| editor.is_dirty(cx)));
6865
6866 cx.executor().start_waiting();
6867 let fake_server = fake_servers.next().await.unwrap();
6868
6869 let save = editor
6870 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6871 .unwrap();
6872 fake_server
6873 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6874 assert_eq!(
6875 params.text_document.uri,
6876 lsp::Url::from_file_path("/file.rs").unwrap()
6877 );
6878 assert_eq!(params.options.tab_size, 4);
6879 Ok(Some(vec![lsp::TextEdit::new(
6880 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6881 ", ".to_string(),
6882 )]))
6883 })
6884 .next()
6885 .await;
6886 cx.executor().start_waiting();
6887 save.await;
6888
6889 assert_eq!(
6890 editor.update(cx, |editor, cx| editor.text(cx)),
6891 "one, two\nthree\n"
6892 );
6893 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6894
6895 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6896 assert!(cx.read(|cx| editor.is_dirty(cx)));
6897
6898 // Ensure we can still save even if formatting hangs.
6899 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6900 assert_eq!(
6901 params.text_document.uri,
6902 lsp::Url::from_file_path("/file.rs").unwrap()
6903 );
6904 futures::future::pending::<()>().await;
6905 unreachable!()
6906 });
6907 let save = editor
6908 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6909 .unwrap();
6910 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6911 cx.executor().start_waiting();
6912 save.await;
6913 assert_eq!(
6914 editor.update(cx, |editor, cx| editor.text(cx)),
6915 "one\ntwo\nthree\n"
6916 );
6917 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6918
6919 // For non-dirty buffer, no formatting request should be sent
6920 let save = editor
6921 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6922 .unwrap();
6923 let _pending_format_request = fake_server
6924 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6925 panic!("Should not be invoked on non-dirty buffer");
6926 })
6927 .next();
6928 cx.executor().start_waiting();
6929 save.await;
6930
6931 // Set rust language override and assert overridden tabsize is sent to language server
6932 update_test_language_settings(cx, |settings| {
6933 settings.languages.insert(
6934 "Rust".into(),
6935 LanguageSettingsContent {
6936 tab_size: NonZeroU32::new(8),
6937 ..Default::default()
6938 },
6939 );
6940 });
6941
6942 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6943 assert!(cx.read(|cx| editor.is_dirty(cx)));
6944 let save = editor
6945 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6946 .unwrap();
6947 fake_server
6948 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6949 assert_eq!(
6950 params.text_document.uri,
6951 lsp::Url::from_file_path("/file.rs").unwrap()
6952 );
6953 assert_eq!(params.options.tab_size, 8);
6954 Ok(Some(vec![]))
6955 })
6956 .next()
6957 .await;
6958 cx.executor().start_waiting();
6959 save.await;
6960}
6961
6962#[gpui::test]
6963async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6964 init_test(cx, |_| {});
6965
6966 let cols = 4;
6967 let rows = 10;
6968 let sample_text_1 = sample_text(rows, cols, 'a');
6969 assert_eq!(
6970 sample_text_1,
6971 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6972 );
6973 let sample_text_2 = sample_text(rows, cols, 'l');
6974 assert_eq!(
6975 sample_text_2,
6976 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6977 );
6978 let sample_text_3 = sample_text(rows, cols, 'v');
6979 assert_eq!(
6980 sample_text_3,
6981 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6982 );
6983
6984 let fs = FakeFs::new(cx.executor());
6985 fs.insert_tree(
6986 "/a",
6987 json!({
6988 "main.rs": sample_text_1,
6989 "other.rs": sample_text_2,
6990 "lib.rs": sample_text_3,
6991 }),
6992 )
6993 .await;
6994
6995 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6996 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6997 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6998
6999 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7000 language_registry.add(rust_lang());
7001 let mut fake_servers = language_registry.register_fake_lsp(
7002 "Rust",
7003 FakeLspAdapter {
7004 capabilities: lsp::ServerCapabilities {
7005 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7006 ..Default::default()
7007 },
7008 ..Default::default()
7009 },
7010 );
7011
7012 let worktree = project.update(cx, |project, cx| {
7013 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7014 assert_eq!(worktrees.len(), 1);
7015 worktrees.pop().unwrap()
7016 });
7017 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7018
7019 let buffer_1 = project
7020 .update(cx, |project, cx| {
7021 project.open_buffer((worktree_id, "main.rs"), cx)
7022 })
7023 .await
7024 .unwrap();
7025 let buffer_2 = project
7026 .update(cx, |project, cx| {
7027 project.open_buffer((worktree_id, "other.rs"), cx)
7028 })
7029 .await
7030 .unwrap();
7031 let buffer_3 = project
7032 .update(cx, |project, cx| {
7033 project.open_buffer((worktree_id, "lib.rs"), cx)
7034 })
7035 .await
7036 .unwrap();
7037
7038 let multi_buffer = cx.new_model(|cx| {
7039 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7040 multi_buffer.push_excerpts(
7041 buffer_1.clone(),
7042 [
7043 ExcerptRange {
7044 context: Point::new(0, 0)..Point::new(3, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(5, 0)..Point::new(7, 0),
7049 primary: None,
7050 },
7051 ExcerptRange {
7052 context: Point::new(9, 0)..Point::new(10, 4),
7053 primary: None,
7054 },
7055 ],
7056 cx,
7057 );
7058 multi_buffer.push_excerpts(
7059 buffer_2.clone(),
7060 [
7061 ExcerptRange {
7062 context: Point::new(0, 0)..Point::new(3, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(5, 0)..Point::new(7, 0),
7067 primary: None,
7068 },
7069 ExcerptRange {
7070 context: Point::new(9, 0)..Point::new(10, 4),
7071 primary: None,
7072 },
7073 ],
7074 cx,
7075 );
7076 multi_buffer.push_excerpts(
7077 buffer_3.clone(),
7078 [
7079 ExcerptRange {
7080 context: Point::new(0, 0)..Point::new(3, 0),
7081 primary: None,
7082 },
7083 ExcerptRange {
7084 context: Point::new(5, 0)..Point::new(7, 0),
7085 primary: None,
7086 },
7087 ExcerptRange {
7088 context: Point::new(9, 0)..Point::new(10, 4),
7089 primary: None,
7090 },
7091 ],
7092 cx,
7093 );
7094 multi_buffer
7095 });
7096 let multi_buffer_editor = cx.new_view(|cx| {
7097 Editor::new(
7098 EditorMode::Full,
7099 multi_buffer,
7100 Some(project.clone()),
7101 true,
7102 cx,
7103 )
7104 });
7105
7106 multi_buffer_editor.update(cx, |editor, cx| {
7107 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7108 editor.insert("|one|two|three|", cx);
7109 });
7110 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7111 multi_buffer_editor.update(cx, |editor, cx| {
7112 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7113 s.select_ranges(Some(60..70))
7114 });
7115 editor.insert("|four|five|six|", cx);
7116 });
7117 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7118
7119 // First two buffers should be edited, but not the third one.
7120 assert_eq!(
7121 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7122 "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}",
7123 );
7124 buffer_1.update(cx, |buffer, _| {
7125 assert!(buffer.is_dirty());
7126 assert_eq!(
7127 buffer.text(),
7128 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7129 )
7130 });
7131 buffer_2.update(cx, |buffer, _| {
7132 assert!(buffer.is_dirty());
7133 assert_eq!(
7134 buffer.text(),
7135 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7136 )
7137 });
7138 buffer_3.update(cx, |buffer, _| {
7139 assert!(!buffer.is_dirty());
7140 assert_eq!(buffer.text(), sample_text_3,)
7141 });
7142 cx.executor().run_until_parked();
7143
7144 cx.executor().start_waiting();
7145 let save = multi_buffer_editor
7146 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7147 .unwrap();
7148
7149 let fake_server = fake_servers.next().await.unwrap();
7150 fake_server
7151 .server
7152 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7153 Ok(Some(vec![lsp::TextEdit::new(
7154 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7155 format!("[{} formatted]", params.text_document.uri),
7156 )]))
7157 })
7158 .detach();
7159 save.await;
7160
7161 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7162 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7163 assert_eq!(
7164 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7165 "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}",
7166 );
7167 buffer_1.update(cx, |buffer, _| {
7168 assert!(!buffer.is_dirty());
7169 assert_eq!(
7170 buffer.text(),
7171 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7172 )
7173 });
7174 buffer_2.update(cx, |buffer, _| {
7175 assert!(!buffer.is_dirty());
7176 assert_eq!(
7177 buffer.text(),
7178 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7179 )
7180 });
7181 buffer_3.update(cx, |buffer, _| {
7182 assert!(!buffer.is_dirty());
7183 assert_eq!(buffer.text(), sample_text_3,)
7184 });
7185}
7186
7187#[gpui::test]
7188async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7189 init_test(cx, |_| {});
7190
7191 let fs = FakeFs::new(cx.executor());
7192 fs.insert_file("/file.rs", Default::default()).await;
7193
7194 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7195
7196 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7197 language_registry.add(rust_lang());
7198 let mut fake_servers = language_registry.register_fake_lsp(
7199 "Rust",
7200 FakeLspAdapter {
7201 capabilities: lsp::ServerCapabilities {
7202 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7203 ..Default::default()
7204 },
7205 ..Default::default()
7206 },
7207 );
7208
7209 let buffer = project
7210 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7211 .await
7212 .unwrap();
7213
7214 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7215 let (editor, cx) =
7216 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7217 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7218 assert!(cx.read(|cx| editor.is_dirty(cx)));
7219
7220 cx.executor().start_waiting();
7221 let fake_server = fake_servers.next().await.unwrap();
7222
7223 let save = editor
7224 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7225 .unwrap();
7226 fake_server
7227 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7228 assert_eq!(
7229 params.text_document.uri,
7230 lsp::Url::from_file_path("/file.rs").unwrap()
7231 );
7232 assert_eq!(params.options.tab_size, 4);
7233 Ok(Some(vec![lsp::TextEdit::new(
7234 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7235 ", ".to_string(),
7236 )]))
7237 })
7238 .next()
7239 .await;
7240 cx.executor().start_waiting();
7241 save.await;
7242 assert_eq!(
7243 editor.update(cx, |editor, cx| editor.text(cx)),
7244 "one, two\nthree\n"
7245 );
7246 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7247
7248 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7249 assert!(cx.read(|cx| editor.is_dirty(cx)));
7250
7251 // Ensure we can still save even if formatting hangs.
7252 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7253 move |params, _| async move {
7254 assert_eq!(
7255 params.text_document.uri,
7256 lsp::Url::from_file_path("/file.rs").unwrap()
7257 );
7258 futures::future::pending::<()>().await;
7259 unreachable!()
7260 },
7261 );
7262 let save = editor
7263 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7264 .unwrap();
7265 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7266 cx.executor().start_waiting();
7267 save.await;
7268 assert_eq!(
7269 editor.update(cx, |editor, cx| editor.text(cx)),
7270 "one\ntwo\nthree\n"
7271 );
7272 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7273
7274 // For non-dirty buffer, no formatting request should be sent
7275 let save = editor
7276 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7277 .unwrap();
7278 let _pending_format_request = fake_server
7279 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7280 panic!("Should not be invoked on non-dirty buffer");
7281 })
7282 .next();
7283 cx.executor().start_waiting();
7284 save.await;
7285
7286 // Set Rust language override and assert overridden tabsize is sent to language server
7287 update_test_language_settings(cx, |settings| {
7288 settings.languages.insert(
7289 "Rust".into(),
7290 LanguageSettingsContent {
7291 tab_size: NonZeroU32::new(8),
7292 ..Default::default()
7293 },
7294 );
7295 });
7296
7297 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7298 assert!(cx.read(|cx| editor.is_dirty(cx)));
7299 let save = editor
7300 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7301 .unwrap();
7302 fake_server
7303 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7304 assert_eq!(
7305 params.text_document.uri,
7306 lsp::Url::from_file_path("/file.rs").unwrap()
7307 );
7308 assert_eq!(params.options.tab_size, 8);
7309 Ok(Some(vec![]))
7310 })
7311 .next()
7312 .await;
7313 cx.executor().start_waiting();
7314 save.await;
7315}
7316
7317#[gpui::test]
7318async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7319 init_test(cx, |settings| {
7320 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7321 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7322 ))
7323 });
7324
7325 let fs = FakeFs::new(cx.executor());
7326 fs.insert_file("/file.rs", Default::default()).await;
7327
7328 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7329
7330 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7331 language_registry.add(Arc::new(Language::new(
7332 LanguageConfig {
7333 name: "Rust".into(),
7334 matcher: LanguageMatcher {
7335 path_suffixes: vec!["rs".to_string()],
7336 ..Default::default()
7337 },
7338 ..LanguageConfig::default()
7339 },
7340 Some(tree_sitter_rust::LANGUAGE.into()),
7341 )));
7342 update_test_language_settings(cx, |settings| {
7343 // Enable Prettier formatting for the same buffer, and ensure
7344 // LSP is called instead of Prettier.
7345 settings.defaults.prettier = Some(PrettierSettings {
7346 allowed: true,
7347 ..PrettierSettings::default()
7348 });
7349 });
7350 let mut fake_servers = language_registry.register_fake_lsp(
7351 "Rust",
7352 FakeLspAdapter {
7353 capabilities: lsp::ServerCapabilities {
7354 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7355 ..Default::default()
7356 },
7357 ..Default::default()
7358 },
7359 );
7360
7361 let buffer = project
7362 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7363 .await
7364 .unwrap();
7365
7366 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7367 let (editor, cx) =
7368 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7369 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7370
7371 cx.executor().start_waiting();
7372 let fake_server = fake_servers.next().await.unwrap();
7373
7374 let format = editor
7375 .update(cx, |editor, cx| {
7376 editor.perform_format(
7377 project.clone(),
7378 FormatTrigger::Manual,
7379 FormatTarget::Buffer,
7380 cx,
7381 )
7382 })
7383 .unwrap();
7384 fake_server
7385 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7386 assert_eq!(
7387 params.text_document.uri,
7388 lsp::Url::from_file_path("/file.rs").unwrap()
7389 );
7390 assert_eq!(params.options.tab_size, 4);
7391 Ok(Some(vec![lsp::TextEdit::new(
7392 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7393 ", ".to_string(),
7394 )]))
7395 })
7396 .next()
7397 .await;
7398 cx.executor().start_waiting();
7399 format.await;
7400 assert_eq!(
7401 editor.update(cx, |editor, cx| editor.text(cx)),
7402 "one, two\nthree\n"
7403 );
7404
7405 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7406 // Ensure we don't lock if formatting hangs.
7407 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7408 assert_eq!(
7409 params.text_document.uri,
7410 lsp::Url::from_file_path("/file.rs").unwrap()
7411 );
7412 futures::future::pending::<()>().await;
7413 unreachable!()
7414 });
7415 let format = editor
7416 .update(cx, |editor, cx| {
7417 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7418 })
7419 .unwrap();
7420 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7421 cx.executor().start_waiting();
7422 format.await;
7423 assert_eq!(
7424 editor.update(cx, |editor, cx| editor.text(cx)),
7425 "one\ntwo\nthree\n"
7426 );
7427}
7428
7429#[gpui::test]
7430async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7431 init_test(cx, |_| {});
7432
7433 let mut cx = EditorLspTestContext::new_rust(
7434 lsp::ServerCapabilities {
7435 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7436 ..Default::default()
7437 },
7438 cx,
7439 )
7440 .await;
7441
7442 cx.set_state(indoc! {"
7443 one.twoˇ
7444 "});
7445
7446 // The format request takes a long time. When it completes, it inserts
7447 // a newline and an indent before the `.`
7448 cx.lsp
7449 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7450 let executor = cx.background_executor().clone();
7451 async move {
7452 executor.timer(Duration::from_millis(100)).await;
7453 Ok(Some(vec![lsp::TextEdit {
7454 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7455 new_text: "\n ".into(),
7456 }]))
7457 }
7458 });
7459
7460 // Submit a format request.
7461 let format_1 = cx
7462 .update_editor(|editor, cx| editor.format(&Format, cx))
7463 .unwrap();
7464 cx.executor().run_until_parked();
7465
7466 // Submit a second format request.
7467 let format_2 = cx
7468 .update_editor(|editor, cx| editor.format(&Format, cx))
7469 .unwrap();
7470 cx.executor().run_until_parked();
7471
7472 // Wait for both format requests to complete
7473 cx.executor().advance_clock(Duration::from_millis(200));
7474 cx.executor().start_waiting();
7475 format_1.await.unwrap();
7476 cx.executor().start_waiting();
7477 format_2.await.unwrap();
7478
7479 // The formatting edits only happens once.
7480 cx.assert_editor_state(indoc! {"
7481 one
7482 .twoˇ
7483 "});
7484}
7485
7486#[gpui::test]
7487async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7488 init_test(cx, |settings| {
7489 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7490 });
7491
7492 let mut cx = EditorLspTestContext::new_rust(
7493 lsp::ServerCapabilities {
7494 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7495 ..Default::default()
7496 },
7497 cx,
7498 )
7499 .await;
7500
7501 // Set up a buffer white some trailing whitespace and no trailing newline.
7502 cx.set_state(
7503 &[
7504 "one ", //
7505 "twoˇ", //
7506 "three ", //
7507 "four", //
7508 ]
7509 .join("\n"),
7510 );
7511
7512 // Submit a format request.
7513 let format = cx
7514 .update_editor(|editor, cx| editor.format(&Format, cx))
7515 .unwrap();
7516
7517 // Record which buffer changes have been sent to the language server
7518 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7519 cx.lsp
7520 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7521 let buffer_changes = buffer_changes.clone();
7522 move |params, _| {
7523 buffer_changes.lock().extend(
7524 params
7525 .content_changes
7526 .into_iter()
7527 .map(|e| (e.range.unwrap(), e.text)),
7528 );
7529 }
7530 });
7531
7532 // Handle formatting requests to the language server.
7533 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7534 let buffer_changes = buffer_changes.clone();
7535 move |_, _| {
7536 // When formatting is requested, trailing whitespace has already been stripped,
7537 // and the trailing newline has already been added.
7538 assert_eq!(
7539 &buffer_changes.lock()[1..],
7540 &[
7541 (
7542 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7543 "".into()
7544 ),
7545 (
7546 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7547 "".into()
7548 ),
7549 (
7550 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7551 "\n".into()
7552 ),
7553 ]
7554 );
7555
7556 // Insert blank lines between each line of the buffer.
7557 async move {
7558 Ok(Some(vec![
7559 lsp::TextEdit {
7560 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7561 new_text: "\n".into(),
7562 },
7563 lsp::TextEdit {
7564 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7565 new_text: "\n".into(),
7566 },
7567 ]))
7568 }
7569 }
7570 });
7571
7572 // After formatting the buffer, the trailing whitespace is stripped,
7573 // a newline is appended, and the edits provided by the language server
7574 // have been applied.
7575 format.await.unwrap();
7576 cx.assert_editor_state(
7577 &[
7578 "one", //
7579 "", //
7580 "twoˇ", //
7581 "", //
7582 "three", //
7583 "four", //
7584 "", //
7585 ]
7586 .join("\n"),
7587 );
7588
7589 // Undoing the formatting undoes the trailing whitespace removal, the
7590 // trailing newline, and the LSP edits.
7591 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7592 cx.assert_editor_state(
7593 &[
7594 "one ", //
7595 "twoˇ", //
7596 "three ", //
7597 "four", //
7598 ]
7599 .join("\n"),
7600 );
7601}
7602
7603#[gpui::test]
7604async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7605 cx: &mut gpui::TestAppContext,
7606) {
7607 init_test(cx, |_| {});
7608
7609 cx.update(|cx| {
7610 cx.update_global::<SettingsStore, _>(|settings, cx| {
7611 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7612 settings.auto_signature_help = Some(true);
7613 });
7614 });
7615 });
7616
7617 let mut cx = EditorLspTestContext::new_rust(
7618 lsp::ServerCapabilities {
7619 signature_help_provider: Some(lsp::SignatureHelpOptions {
7620 ..Default::default()
7621 }),
7622 ..Default::default()
7623 },
7624 cx,
7625 )
7626 .await;
7627
7628 let language = Language::new(
7629 LanguageConfig {
7630 name: "Rust".into(),
7631 brackets: BracketPairConfig {
7632 pairs: vec![
7633 BracketPair {
7634 start: "{".to_string(),
7635 end: "}".to_string(),
7636 close: true,
7637 surround: true,
7638 newline: true,
7639 },
7640 BracketPair {
7641 start: "(".to_string(),
7642 end: ")".to_string(),
7643 close: true,
7644 surround: true,
7645 newline: true,
7646 },
7647 BracketPair {
7648 start: "/*".to_string(),
7649 end: " */".to_string(),
7650 close: true,
7651 surround: true,
7652 newline: true,
7653 },
7654 BracketPair {
7655 start: "[".to_string(),
7656 end: "]".to_string(),
7657 close: false,
7658 surround: false,
7659 newline: true,
7660 },
7661 BracketPair {
7662 start: "\"".to_string(),
7663 end: "\"".to_string(),
7664 close: true,
7665 surround: true,
7666 newline: false,
7667 },
7668 BracketPair {
7669 start: "<".to_string(),
7670 end: ">".to_string(),
7671 close: false,
7672 surround: true,
7673 newline: true,
7674 },
7675 ],
7676 ..Default::default()
7677 },
7678 autoclose_before: "})]".to_string(),
7679 ..Default::default()
7680 },
7681 Some(tree_sitter_rust::LANGUAGE.into()),
7682 );
7683 let language = Arc::new(language);
7684
7685 cx.language_registry().add(language.clone());
7686 cx.update_buffer(|buffer, cx| {
7687 buffer.set_language(Some(language), cx);
7688 });
7689
7690 cx.set_state(
7691 &r#"
7692 fn main() {
7693 sampleˇ
7694 }
7695 "#
7696 .unindent(),
7697 );
7698
7699 cx.update_editor(|view, cx| {
7700 view.handle_input("(", cx);
7701 });
7702 cx.assert_editor_state(
7703 &"
7704 fn main() {
7705 sample(ˇ)
7706 }
7707 "
7708 .unindent(),
7709 );
7710
7711 let mocked_response = lsp::SignatureHelp {
7712 signatures: vec![lsp::SignatureInformation {
7713 label: "fn sample(param1: u8, param2: u8)".to_string(),
7714 documentation: None,
7715 parameters: Some(vec![
7716 lsp::ParameterInformation {
7717 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7718 documentation: None,
7719 },
7720 lsp::ParameterInformation {
7721 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7722 documentation: None,
7723 },
7724 ]),
7725 active_parameter: None,
7726 }],
7727 active_signature: Some(0),
7728 active_parameter: Some(0),
7729 };
7730 handle_signature_help_request(&mut cx, mocked_response).await;
7731
7732 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7733 .await;
7734
7735 cx.editor(|editor, _| {
7736 let signature_help_state = editor.signature_help_state.popover().cloned();
7737 assert!(signature_help_state.is_some());
7738 let ParsedMarkdown {
7739 text, highlights, ..
7740 } = signature_help_state.unwrap().parsed_content;
7741 assert_eq!(text, "param1: u8, param2: u8");
7742 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7743 });
7744}
7745
7746#[gpui::test]
7747async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7748 init_test(cx, |_| {});
7749
7750 cx.update(|cx| {
7751 cx.update_global::<SettingsStore, _>(|settings, cx| {
7752 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7753 settings.auto_signature_help = Some(false);
7754 settings.show_signature_help_after_edits = Some(false);
7755 });
7756 });
7757 });
7758
7759 let mut cx = EditorLspTestContext::new_rust(
7760 lsp::ServerCapabilities {
7761 signature_help_provider: Some(lsp::SignatureHelpOptions {
7762 ..Default::default()
7763 }),
7764 ..Default::default()
7765 },
7766 cx,
7767 )
7768 .await;
7769
7770 let language = Language::new(
7771 LanguageConfig {
7772 name: "Rust".into(),
7773 brackets: BracketPairConfig {
7774 pairs: vec![
7775 BracketPair {
7776 start: "{".to_string(),
7777 end: "}".to_string(),
7778 close: true,
7779 surround: true,
7780 newline: true,
7781 },
7782 BracketPair {
7783 start: "(".to_string(),
7784 end: ")".to_string(),
7785 close: true,
7786 surround: true,
7787 newline: true,
7788 },
7789 BracketPair {
7790 start: "/*".to_string(),
7791 end: " */".to_string(),
7792 close: true,
7793 surround: true,
7794 newline: true,
7795 },
7796 BracketPair {
7797 start: "[".to_string(),
7798 end: "]".to_string(),
7799 close: false,
7800 surround: false,
7801 newline: true,
7802 },
7803 BracketPair {
7804 start: "\"".to_string(),
7805 end: "\"".to_string(),
7806 close: true,
7807 surround: true,
7808 newline: false,
7809 },
7810 BracketPair {
7811 start: "<".to_string(),
7812 end: ">".to_string(),
7813 close: false,
7814 surround: true,
7815 newline: true,
7816 },
7817 ],
7818 ..Default::default()
7819 },
7820 autoclose_before: "})]".to_string(),
7821 ..Default::default()
7822 },
7823 Some(tree_sitter_rust::LANGUAGE.into()),
7824 );
7825 let language = Arc::new(language);
7826
7827 cx.language_registry().add(language.clone());
7828 cx.update_buffer(|buffer, cx| {
7829 buffer.set_language(Some(language), cx);
7830 });
7831
7832 // Ensure that signature_help is not called when no signature help is enabled.
7833 cx.set_state(
7834 &r#"
7835 fn main() {
7836 sampleˇ
7837 }
7838 "#
7839 .unindent(),
7840 );
7841 cx.update_editor(|view, cx| {
7842 view.handle_input("(", cx);
7843 });
7844 cx.assert_editor_state(
7845 &"
7846 fn main() {
7847 sample(ˇ)
7848 }
7849 "
7850 .unindent(),
7851 );
7852 cx.editor(|editor, _| {
7853 assert!(editor.signature_help_state.task().is_none());
7854 });
7855
7856 let mocked_response = lsp::SignatureHelp {
7857 signatures: vec![lsp::SignatureInformation {
7858 label: "fn sample(param1: u8, param2: u8)".to_string(),
7859 documentation: None,
7860 parameters: Some(vec![
7861 lsp::ParameterInformation {
7862 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7863 documentation: None,
7864 },
7865 lsp::ParameterInformation {
7866 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7867 documentation: None,
7868 },
7869 ]),
7870 active_parameter: None,
7871 }],
7872 active_signature: Some(0),
7873 active_parameter: Some(0),
7874 };
7875
7876 // Ensure that signature_help is called when enabled afte edits
7877 cx.update(|cx| {
7878 cx.update_global::<SettingsStore, _>(|settings, cx| {
7879 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7880 settings.auto_signature_help = Some(false);
7881 settings.show_signature_help_after_edits = Some(true);
7882 });
7883 });
7884 });
7885 cx.set_state(
7886 &r#"
7887 fn main() {
7888 sampleˇ
7889 }
7890 "#
7891 .unindent(),
7892 );
7893 cx.update_editor(|view, cx| {
7894 view.handle_input("(", cx);
7895 });
7896 cx.assert_editor_state(
7897 &"
7898 fn main() {
7899 sample(ˇ)
7900 }
7901 "
7902 .unindent(),
7903 );
7904 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7905 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7906 .await;
7907 cx.update_editor(|editor, _| {
7908 let signature_help_state = editor.signature_help_state.popover().cloned();
7909 assert!(signature_help_state.is_some());
7910 let ParsedMarkdown {
7911 text, highlights, ..
7912 } = signature_help_state.unwrap().parsed_content;
7913 assert_eq!(text, "param1: u8, param2: u8");
7914 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7915 editor.signature_help_state = SignatureHelpState::default();
7916 });
7917
7918 // Ensure that signature_help is called when auto signature help override is enabled
7919 cx.update(|cx| {
7920 cx.update_global::<SettingsStore, _>(|settings, cx| {
7921 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7922 settings.auto_signature_help = Some(true);
7923 settings.show_signature_help_after_edits = Some(false);
7924 });
7925 });
7926 });
7927 cx.set_state(
7928 &r#"
7929 fn main() {
7930 sampleˇ
7931 }
7932 "#
7933 .unindent(),
7934 );
7935 cx.update_editor(|view, cx| {
7936 view.handle_input("(", cx);
7937 });
7938 cx.assert_editor_state(
7939 &"
7940 fn main() {
7941 sample(ˇ)
7942 }
7943 "
7944 .unindent(),
7945 );
7946 handle_signature_help_request(&mut cx, mocked_response).await;
7947 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7948 .await;
7949 cx.editor(|editor, _| {
7950 let signature_help_state = editor.signature_help_state.popover().cloned();
7951 assert!(signature_help_state.is_some());
7952 let ParsedMarkdown {
7953 text, highlights, ..
7954 } = signature_help_state.unwrap().parsed_content;
7955 assert_eq!(text, "param1: u8, param2: u8");
7956 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7957 });
7958}
7959
7960#[gpui::test]
7961async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7962 init_test(cx, |_| {});
7963 cx.update(|cx| {
7964 cx.update_global::<SettingsStore, _>(|settings, cx| {
7965 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7966 settings.auto_signature_help = Some(true);
7967 });
7968 });
7969 });
7970
7971 let mut cx = EditorLspTestContext::new_rust(
7972 lsp::ServerCapabilities {
7973 signature_help_provider: Some(lsp::SignatureHelpOptions {
7974 ..Default::default()
7975 }),
7976 ..Default::default()
7977 },
7978 cx,
7979 )
7980 .await;
7981
7982 // A test that directly calls `show_signature_help`
7983 cx.update_editor(|editor, cx| {
7984 editor.show_signature_help(&ShowSignatureHelp, cx);
7985 });
7986
7987 let mocked_response = lsp::SignatureHelp {
7988 signatures: vec![lsp::SignatureInformation {
7989 label: "fn sample(param1: u8, param2: u8)".to_string(),
7990 documentation: None,
7991 parameters: Some(vec![
7992 lsp::ParameterInformation {
7993 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7994 documentation: None,
7995 },
7996 lsp::ParameterInformation {
7997 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7998 documentation: None,
7999 },
8000 ]),
8001 active_parameter: None,
8002 }],
8003 active_signature: Some(0),
8004 active_parameter: Some(0),
8005 };
8006 handle_signature_help_request(&mut cx, mocked_response).await;
8007
8008 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8009 .await;
8010
8011 cx.editor(|editor, _| {
8012 let signature_help_state = editor.signature_help_state.popover().cloned();
8013 assert!(signature_help_state.is_some());
8014 let ParsedMarkdown {
8015 text, highlights, ..
8016 } = signature_help_state.unwrap().parsed_content;
8017 assert_eq!(text, "param1: u8, param2: u8");
8018 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8019 });
8020
8021 // When exiting outside from inside the brackets, `signature_help` is closed.
8022 cx.set_state(indoc! {"
8023 fn main() {
8024 sample(ˇ);
8025 }
8026
8027 fn sample(param1: u8, param2: u8) {}
8028 "});
8029
8030 cx.update_editor(|editor, cx| {
8031 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8032 });
8033
8034 let mocked_response = lsp::SignatureHelp {
8035 signatures: Vec::new(),
8036 active_signature: None,
8037 active_parameter: None,
8038 };
8039 handle_signature_help_request(&mut cx, mocked_response).await;
8040
8041 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8042 .await;
8043
8044 cx.editor(|editor, _| {
8045 assert!(!editor.signature_help_state.is_shown());
8046 });
8047
8048 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8049 cx.set_state(indoc! {"
8050 fn main() {
8051 sample(ˇ);
8052 }
8053
8054 fn sample(param1: u8, param2: u8) {}
8055 "});
8056
8057 let mocked_response = lsp::SignatureHelp {
8058 signatures: vec![lsp::SignatureInformation {
8059 label: "fn sample(param1: u8, param2: u8)".to_string(),
8060 documentation: None,
8061 parameters: Some(vec![
8062 lsp::ParameterInformation {
8063 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8064 documentation: None,
8065 },
8066 lsp::ParameterInformation {
8067 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8068 documentation: None,
8069 },
8070 ]),
8071 active_parameter: None,
8072 }],
8073 active_signature: Some(0),
8074 active_parameter: Some(0),
8075 };
8076 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8077 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8078 .await;
8079 cx.editor(|editor, _| {
8080 assert!(editor.signature_help_state.is_shown());
8081 });
8082
8083 // Restore the popover with more parameter input
8084 cx.set_state(indoc! {"
8085 fn main() {
8086 sample(param1, param2ˇ);
8087 }
8088
8089 fn sample(param1: u8, param2: u8) {}
8090 "});
8091
8092 let mocked_response = lsp::SignatureHelp {
8093 signatures: vec![lsp::SignatureInformation {
8094 label: "fn sample(param1: u8, param2: u8)".to_string(),
8095 documentation: None,
8096 parameters: Some(vec![
8097 lsp::ParameterInformation {
8098 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8099 documentation: None,
8100 },
8101 lsp::ParameterInformation {
8102 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8103 documentation: None,
8104 },
8105 ]),
8106 active_parameter: None,
8107 }],
8108 active_signature: Some(0),
8109 active_parameter: Some(1),
8110 };
8111 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8112 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8113 .await;
8114
8115 // When selecting a range, the popover is gone.
8116 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8117 cx.update_editor(|editor, cx| {
8118 editor.change_selections(None, cx, |s| {
8119 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8120 })
8121 });
8122 cx.assert_editor_state(indoc! {"
8123 fn main() {
8124 sample(param1, «ˇparam2»);
8125 }
8126
8127 fn sample(param1: u8, param2: u8) {}
8128 "});
8129 cx.editor(|editor, _| {
8130 assert!(!editor.signature_help_state.is_shown());
8131 });
8132
8133 // When unselecting again, the popover is back if within the brackets.
8134 cx.update_editor(|editor, cx| {
8135 editor.change_selections(None, cx, |s| {
8136 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8137 })
8138 });
8139 cx.assert_editor_state(indoc! {"
8140 fn main() {
8141 sample(param1, ˇparam2);
8142 }
8143
8144 fn sample(param1: u8, param2: u8) {}
8145 "});
8146 handle_signature_help_request(&mut cx, mocked_response).await;
8147 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8148 .await;
8149 cx.editor(|editor, _| {
8150 assert!(editor.signature_help_state.is_shown());
8151 });
8152
8153 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8154 cx.update_editor(|editor, cx| {
8155 editor.change_selections(None, cx, |s| {
8156 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8157 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8158 })
8159 });
8160 cx.assert_editor_state(indoc! {"
8161 fn main() {
8162 sample(param1, ˇparam2);
8163 }
8164
8165 fn sample(param1: u8, param2: u8) {}
8166 "});
8167
8168 let mocked_response = lsp::SignatureHelp {
8169 signatures: vec![lsp::SignatureInformation {
8170 label: "fn sample(param1: u8, param2: u8)".to_string(),
8171 documentation: None,
8172 parameters: Some(vec![
8173 lsp::ParameterInformation {
8174 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8175 documentation: None,
8176 },
8177 lsp::ParameterInformation {
8178 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8179 documentation: None,
8180 },
8181 ]),
8182 active_parameter: None,
8183 }],
8184 active_signature: Some(0),
8185 active_parameter: Some(1),
8186 };
8187 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8188 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8189 .await;
8190 cx.update_editor(|editor, cx| {
8191 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8192 });
8193 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8194 .await;
8195 cx.update_editor(|editor, cx| {
8196 editor.change_selections(None, cx, |s| {
8197 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8198 })
8199 });
8200 cx.assert_editor_state(indoc! {"
8201 fn main() {
8202 sample(param1, «ˇparam2»);
8203 }
8204
8205 fn sample(param1: u8, param2: u8) {}
8206 "});
8207 cx.update_editor(|editor, cx| {
8208 editor.change_selections(None, cx, |s| {
8209 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8210 })
8211 });
8212 cx.assert_editor_state(indoc! {"
8213 fn main() {
8214 sample(param1, ˇparam2);
8215 }
8216
8217 fn sample(param1: u8, param2: u8) {}
8218 "});
8219 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8220 .await;
8221}
8222
8223#[gpui::test]
8224async fn test_completion(cx: &mut gpui::TestAppContext) {
8225 init_test(cx, |_| {});
8226
8227 let mut cx = EditorLspTestContext::new_rust(
8228 lsp::ServerCapabilities {
8229 completion_provider: Some(lsp::CompletionOptions {
8230 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8231 resolve_provider: Some(true),
8232 ..Default::default()
8233 }),
8234 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8235 ..Default::default()
8236 },
8237 cx,
8238 )
8239 .await;
8240 let counter = Arc::new(AtomicUsize::new(0));
8241
8242 cx.set_state(indoc! {"
8243 oneˇ
8244 two
8245 three
8246 "});
8247 cx.simulate_keystroke(".");
8248 handle_completion_request(
8249 &mut cx,
8250 indoc! {"
8251 one.|<>
8252 two
8253 three
8254 "},
8255 vec!["first_completion", "second_completion"],
8256 counter.clone(),
8257 )
8258 .await;
8259 cx.condition(|editor, _| editor.context_menu_visible())
8260 .await;
8261 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8262
8263 let _handler = handle_signature_help_request(
8264 &mut cx,
8265 lsp::SignatureHelp {
8266 signatures: vec![lsp::SignatureInformation {
8267 label: "test signature".to_string(),
8268 documentation: None,
8269 parameters: Some(vec![lsp::ParameterInformation {
8270 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8271 documentation: None,
8272 }]),
8273 active_parameter: None,
8274 }],
8275 active_signature: None,
8276 active_parameter: None,
8277 },
8278 );
8279 cx.update_editor(|editor, cx| {
8280 assert!(
8281 !editor.signature_help_state.is_shown(),
8282 "No signature help was called for"
8283 );
8284 editor.show_signature_help(&ShowSignatureHelp, cx);
8285 });
8286 cx.run_until_parked();
8287 cx.update_editor(|editor, _| {
8288 assert!(
8289 !editor.signature_help_state.is_shown(),
8290 "No signature help should be shown when completions menu is open"
8291 );
8292 });
8293
8294 let apply_additional_edits = cx.update_editor(|editor, cx| {
8295 editor.context_menu_next(&Default::default(), cx);
8296 editor
8297 .confirm_completion(&ConfirmCompletion::default(), cx)
8298 .unwrap()
8299 });
8300 cx.assert_editor_state(indoc! {"
8301 one.second_completionˇ
8302 two
8303 three
8304 "});
8305
8306 handle_resolve_completion_request(
8307 &mut cx,
8308 Some(vec![
8309 (
8310 //This overlaps with the primary completion edit which is
8311 //misbehavior from the LSP spec, test that we filter it out
8312 indoc! {"
8313 one.second_ˇcompletion
8314 two
8315 threeˇ
8316 "},
8317 "overlapping additional edit",
8318 ),
8319 (
8320 indoc! {"
8321 one.second_completion
8322 two
8323 threeˇ
8324 "},
8325 "\nadditional edit",
8326 ),
8327 ]),
8328 )
8329 .await;
8330 apply_additional_edits.await.unwrap();
8331 cx.assert_editor_state(indoc! {"
8332 one.second_completionˇ
8333 two
8334 three
8335 additional edit
8336 "});
8337
8338 cx.set_state(indoc! {"
8339 one.second_completion
8340 twoˇ
8341 threeˇ
8342 additional edit
8343 "});
8344 cx.simulate_keystroke(" ");
8345 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8346 cx.simulate_keystroke("s");
8347 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8348
8349 cx.assert_editor_state(indoc! {"
8350 one.second_completion
8351 two sˇ
8352 three sˇ
8353 additional edit
8354 "});
8355 handle_completion_request(
8356 &mut cx,
8357 indoc! {"
8358 one.second_completion
8359 two s
8360 three <s|>
8361 additional edit
8362 "},
8363 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8364 counter.clone(),
8365 )
8366 .await;
8367 cx.condition(|editor, _| editor.context_menu_visible())
8368 .await;
8369 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8370
8371 cx.simulate_keystroke("i");
8372
8373 handle_completion_request(
8374 &mut cx,
8375 indoc! {"
8376 one.second_completion
8377 two si
8378 three <si|>
8379 additional edit
8380 "},
8381 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8382 counter.clone(),
8383 )
8384 .await;
8385 cx.condition(|editor, _| editor.context_menu_visible())
8386 .await;
8387 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8388
8389 let apply_additional_edits = cx.update_editor(|editor, cx| {
8390 editor
8391 .confirm_completion(&ConfirmCompletion::default(), cx)
8392 .unwrap()
8393 });
8394 cx.assert_editor_state(indoc! {"
8395 one.second_completion
8396 two sixth_completionˇ
8397 three sixth_completionˇ
8398 additional edit
8399 "});
8400
8401 handle_resolve_completion_request(&mut cx, None).await;
8402 apply_additional_edits.await.unwrap();
8403
8404 update_test_language_settings(&mut cx, |settings| {
8405 settings.defaults.show_completions_on_input = Some(false);
8406 });
8407 cx.set_state("editorˇ");
8408 cx.simulate_keystroke(".");
8409 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8410 cx.simulate_keystroke("c");
8411 cx.simulate_keystroke("l");
8412 cx.simulate_keystroke("o");
8413 cx.assert_editor_state("editor.cloˇ");
8414 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8415 cx.update_editor(|editor, cx| {
8416 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8417 });
8418 handle_completion_request(
8419 &mut cx,
8420 "editor.<clo|>",
8421 vec!["close", "clobber"],
8422 counter.clone(),
8423 )
8424 .await;
8425 cx.condition(|editor, _| editor.context_menu_visible())
8426 .await;
8427 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8428
8429 let apply_additional_edits = cx.update_editor(|editor, cx| {
8430 editor
8431 .confirm_completion(&ConfirmCompletion::default(), cx)
8432 .unwrap()
8433 });
8434 cx.assert_editor_state("editor.closeˇ");
8435 handle_resolve_completion_request(&mut cx, None).await;
8436 apply_additional_edits.await.unwrap();
8437}
8438
8439#[gpui::test]
8440async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8441 init_test(cx, |_| {});
8442 let mut cx = EditorLspTestContext::new_rust(
8443 lsp::ServerCapabilities {
8444 completion_provider: Some(lsp::CompletionOptions {
8445 trigger_characters: Some(vec![".".to_string()]),
8446 ..Default::default()
8447 }),
8448 ..Default::default()
8449 },
8450 cx,
8451 )
8452 .await;
8453 cx.lsp
8454 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8455 Ok(Some(lsp::CompletionResponse::Array(vec![
8456 lsp::CompletionItem {
8457 label: "first".into(),
8458 ..Default::default()
8459 },
8460 lsp::CompletionItem {
8461 label: "last".into(),
8462 ..Default::default()
8463 },
8464 ])))
8465 });
8466 cx.set_state("variableˇ");
8467 cx.simulate_keystroke(".");
8468 cx.executor().run_until_parked();
8469
8470 cx.update_editor(|editor, _| {
8471 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8472 {
8473 assert_eq!(completion_menu_entries(&menu.entries), &["first", "last"]);
8474 } else {
8475 panic!("expected completion menu to be open");
8476 }
8477 });
8478
8479 cx.update_editor(|editor, cx| {
8480 editor.move_page_down(&MovePageDown::default(), cx);
8481 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8482 {
8483 assert!(
8484 menu.selected_item == 1,
8485 "expected PageDown to select the last item from the context menu"
8486 );
8487 } else {
8488 panic!("expected completion menu to stay open after PageDown");
8489 }
8490 });
8491
8492 cx.update_editor(|editor, cx| {
8493 editor.move_page_up(&MovePageUp::default(), cx);
8494 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8495 {
8496 assert!(
8497 menu.selected_item == 0,
8498 "expected PageUp to select the first item from the context menu"
8499 );
8500 } else {
8501 panic!("expected completion menu to stay open after PageUp");
8502 }
8503 });
8504}
8505
8506#[gpui::test]
8507async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8508 init_test(cx, |_| {});
8509 let mut cx = EditorLspTestContext::new_rust(
8510 lsp::ServerCapabilities {
8511 completion_provider: Some(lsp::CompletionOptions {
8512 trigger_characters: Some(vec![".".to_string()]),
8513 ..Default::default()
8514 }),
8515 ..Default::default()
8516 },
8517 cx,
8518 )
8519 .await;
8520 cx.lsp
8521 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8522 Ok(Some(lsp::CompletionResponse::Array(vec![
8523 lsp::CompletionItem {
8524 label: "Range".into(),
8525 sort_text: Some("a".into()),
8526 ..Default::default()
8527 },
8528 lsp::CompletionItem {
8529 label: "r".into(),
8530 sort_text: Some("b".into()),
8531 ..Default::default()
8532 },
8533 lsp::CompletionItem {
8534 label: "ret".into(),
8535 sort_text: Some("c".into()),
8536 ..Default::default()
8537 },
8538 lsp::CompletionItem {
8539 label: "return".into(),
8540 sort_text: Some("d".into()),
8541 ..Default::default()
8542 },
8543 lsp::CompletionItem {
8544 label: "slice".into(),
8545 sort_text: Some("d".into()),
8546 ..Default::default()
8547 },
8548 ])))
8549 });
8550 cx.set_state("rˇ");
8551 cx.executor().run_until_parked();
8552 cx.update_editor(|editor, cx| {
8553 editor.show_completions(
8554 &ShowCompletions {
8555 trigger: Some("r".into()),
8556 },
8557 cx,
8558 );
8559 });
8560 cx.executor().run_until_parked();
8561
8562 cx.update_editor(|editor, _| {
8563 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8564 {
8565 assert_eq!(
8566 completion_menu_entries(&menu.entries),
8567 &["r", "ret", "Range", "return"]
8568 );
8569 } else {
8570 panic!("expected completion menu to be open");
8571 }
8572 });
8573}
8574
8575#[gpui::test]
8576async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8577 init_test(cx, |_| {});
8578
8579 let mut cx = EditorLspTestContext::new_rust(
8580 lsp::ServerCapabilities {
8581 completion_provider: Some(lsp::CompletionOptions {
8582 trigger_characters: Some(vec![".".to_string()]),
8583 resolve_provider: Some(true),
8584 ..Default::default()
8585 }),
8586 ..Default::default()
8587 },
8588 cx,
8589 )
8590 .await;
8591
8592 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8593 cx.simulate_keystroke(".");
8594 let completion_item = lsp::CompletionItem {
8595 label: "Some".into(),
8596 kind: Some(lsp::CompletionItemKind::SNIPPET),
8597 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8598 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8599 kind: lsp::MarkupKind::Markdown,
8600 value: "```rust\nSome(2)\n```".to_string(),
8601 })),
8602 deprecated: Some(false),
8603 sort_text: Some("Some".to_string()),
8604 filter_text: Some("Some".to_string()),
8605 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8606 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8607 range: lsp::Range {
8608 start: lsp::Position {
8609 line: 0,
8610 character: 22,
8611 },
8612 end: lsp::Position {
8613 line: 0,
8614 character: 22,
8615 },
8616 },
8617 new_text: "Some(2)".to_string(),
8618 })),
8619 additional_text_edits: Some(vec![lsp::TextEdit {
8620 range: lsp::Range {
8621 start: lsp::Position {
8622 line: 0,
8623 character: 20,
8624 },
8625 end: lsp::Position {
8626 line: 0,
8627 character: 22,
8628 },
8629 },
8630 new_text: "".to_string(),
8631 }]),
8632 ..Default::default()
8633 };
8634
8635 let closure_completion_item = completion_item.clone();
8636 let counter = Arc::new(AtomicUsize::new(0));
8637 let counter_clone = counter.clone();
8638 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8639 let task_completion_item = closure_completion_item.clone();
8640 counter_clone.fetch_add(1, atomic::Ordering::Release);
8641 async move {
8642 Ok(Some(lsp::CompletionResponse::Array(vec![
8643 task_completion_item,
8644 ])))
8645 }
8646 });
8647
8648 cx.condition(|editor, _| editor.context_menu_visible())
8649 .await;
8650 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8651 assert!(request.next().await.is_some());
8652 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8653
8654 cx.simulate_keystroke("S");
8655 cx.simulate_keystroke("o");
8656 cx.simulate_keystroke("m");
8657 cx.condition(|editor, _| editor.context_menu_visible())
8658 .await;
8659 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8660 assert!(request.next().await.is_some());
8661 assert!(request.next().await.is_some());
8662 assert!(request.next().await.is_some());
8663 request.close();
8664 assert!(request.next().await.is_none());
8665 assert_eq!(
8666 counter.load(atomic::Ordering::Acquire),
8667 4,
8668 "With the completions menu open, only one LSP request should happen per input"
8669 );
8670}
8671
8672#[gpui::test]
8673async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8674 init_test(cx, |_| {});
8675 let mut cx = EditorTestContext::new(cx).await;
8676 let language = Arc::new(Language::new(
8677 LanguageConfig {
8678 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8679 ..Default::default()
8680 },
8681 Some(tree_sitter_rust::LANGUAGE.into()),
8682 ));
8683 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8684
8685 // If multiple selections intersect a line, the line is only toggled once.
8686 cx.set_state(indoc! {"
8687 fn a() {
8688 «//b();
8689 ˇ»// «c();
8690 //ˇ» d();
8691 }
8692 "});
8693
8694 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8695
8696 cx.assert_editor_state(indoc! {"
8697 fn a() {
8698 «b();
8699 c();
8700 ˇ» d();
8701 }
8702 "});
8703
8704 // The comment prefix is inserted at the same column for every line in a
8705 // selection.
8706 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8707
8708 cx.assert_editor_state(indoc! {"
8709 fn a() {
8710 // «b();
8711 // c();
8712 ˇ»// d();
8713 }
8714 "});
8715
8716 // If a selection ends at the beginning of a line, that line is not toggled.
8717 cx.set_selections_state(indoc! {"
8718 fn a() {
8719 // b();
8720 «// c();
8721 ˇ» // d();
8722 }
8723 "});
8724
8725 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8726
8727 cx.assert_editor_state(indoc! {"
8728 fn a() {
8729 // b();
8730 «c();
8731 ˇ» // d();
8732 }
8733 "});
8734
8735 // If a selection span a single line and is empty, the line is toggled.
8736 cx.set_state(indoc! {"
8737 fn a() {
8738 a();
8739 b();
8740 ˇ
8741 }
8742 "});
8743
8744 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8745
8746 cx.assert_editor_state(indoc! {"
8747 fn a() {
8748 a();
8749 b();
8750 //•ˇ
8751 }
8752 "});
8753
8754 // If a selection span multiple lines, empty lines are not toggled.
8755 cx.set_state(indoc! {"
8756 fn a() {
8757 «a();
8758
8759 c();ˇ»
8760 }
8761 "});
8762
8763 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8764
8765 cx.assert_editor_state(indoc! {"
8766 fn a() {
8767 // «a();
8768
8769 // c();ˇ»
8770 }
8771 "});
8772
8773 // If a selection includes multiple comment prefixes, all lines are uncommented.
8774 cx.set_state(indoc! {"
8775 fn a() {
8776 «// a();
8777 /// b();
8778 //! c();ˇ»
8779 }
8780 "});
8781
8782 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8783
8784 cx.assert_editor_state(indoc! {"
8785 fn a() {
8786 «a();
8787 b();
8788 c();ˇ»
8789 }
8790 "});
8791}
8792
8793#[gpui::test]
8794async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8795 init_test(cx, |_| {});
8796 let mut cx = EditorTestContext::new(cx).await;
8797 let language = Arc::new(Language::new(
8798 LanguageConfig {
8799 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8800 ..Default::default()
8801 },
8802 Some(tree_sitter_rust::LANGUAGE.into()),
8803 ));
8804 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8805
8806 let toggle_comments = &ToggleComments {
8807 advance_downwards: false,
8808 ignore_indent: true,
8809 };
8810
8811 // If multiple selections intersect a line, the line is only toggled once.
8812 cx.set_state(indoc! {"
8813 fn a() {
8814 // «b();
8815 // c();
8816 // ˇ» d();
8817 }
8818 "});
8819
8820 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8821
8822 cx.assert_editor_state(indoc! {"
8823 fn a() {
8824 «b();
8825 c();
8826 ˇ» d();
8827 }
8828 "});
8829
8830 // The comment prefix is inserted at the beginning of each line
8831 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8832
8833 cx.assert_editor_state(indoc! {"
8834 fn a() {
8835 // «b();
8836 // c();
8837 // ˇ» d();
8838 }
8839 "});
8840
8841 // If a selection ends at the beginning of a line, that line is not toggled.
8842 cx.set_selections_state(indoc! {"
8843 fn a() {
8844 // b();
8845 // «c();
8846 ˇ»// d();
8847 }
8848 "});
8849
8850 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8851
8852 cx.assert_editor_state(indoc! {"
8853 fn a() {
8854 // b();
8855 «c();
8856 ˇ»// d();
8857 }
8858 "});
8859
8860 // If a selection span a single line and is empty, the line is toggled.
8861 cx.set_state(indoc! {"
8862 fn a() {
8863 a();
8864 b();
8865 ˇ
8866 }
8867 "});
8868
8869 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8870
8871 cx.assert_editor_state(indoc! {"
8872 fn a() {
8873 a();
8874 b();
8875 //ˇ
8876 }
8877 "});
8878
8879 // If a selection span multiple lines, empty lines are not toggled.
8880 cx.set_state(indoc! {"
8881 fn a() {
8882 «a();
8883
8884 c();ˇ»
8885 }
8886 "});
8887
8888 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8889
8890 cx.assert_editor_state(indoc! {"
8891 fn a() {
8892 // «a();
8893
8894 // c();ˇ»
8895 }
8896 "});
8897
8898 // If a selection includes multiple comment prefixes, all lines are uncommented.
8899 cx.set_state(indoc! {"
8900 fn a() {
8901 // «a();
8902 /// b();
8903 //! c();ˇ»
8904 }
8905 "});
8906
8907 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8908
8909 cx.assert_editor_state(indoc! {"
8910 fn a() {
8911 «a();
8912 b();
8913 c();ˇ»
8914 }
8915 "});
8916}
8917
8918#[gpui::test]
8919async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8920 init_test(cx, |_| {});
8921
8922 let language = Arc::new(Language::new(
8923 LanguageConfig {
8924 line_comments: vec!["// ".into()],
8925 ..Default::default()
8926 },
8927 Some(tree_sitter_rust::LANGUAGE.into()),
8928 ));
8929
8930 let mut cx = EditorTestContext::new(cx).await;
8931
8932 cx.language_registry().add(language.clone());
8933 cx.update_buffer(|buffer, cx| {
8934 buffer.set_language(Some(language), cx);
8935 });
8936
8937 let toggle_comments = &ToggleComments {
8938 advance_downwards: true,
8939 ignore_indent: false,
8940 };
8941
8942 // Single cursor on one line -> advance
8943 // Cursor moves horizontally 3 characters as well on non-blank line
8944 cx.set_state(indoc!(
8945 "fn a() {
8946 ˇdog();
8947 cat();
8948 }"
8949 ));
8950 cx.update_editor(|editor, cx| {
8951 editor.toggle_comments(toggle_comments, cx);
8952 });
8953 cx.assert_editor_state(indoc!(
8954 "fn a() {
8955 // dog();
8956 catˇ();
8957 }"
8958 ));
8959
8960 // Single selection on one line -> don't advance
8961 cx.set_state(indoc!(
8962 "fn a() {
8963 «dog()ˇ»;
8964 cat();
8965 }"
8966 ));
8967 cx.update_editor(|editor, cx| {
8968 editor.toggle_comments(toggle_comments, cx);
8969 });
8970 cx.assert_editor_state(indoc!(
8971 "fn a() {
8972 // «dog()ˇ»;
8973 cat();
8974 }"
8975 ));
8976
8977 // Multiple cursors on one line -> advance
8978 cx.set_state(indoc!(
8979 "fn a() {
8980 ˇdˇog();
8981 cat();
8982 }"
8983 ));
8984 cx.update_editor(|editor, cx| {
8985 editor.toggle_comments(toggle_comments, cx);
8986 });
8987 cx.assert_editor_state(indoc!(
8988 "fn a() {
8989 // dog();
8990 catˇ(ˇ);
8991 }"
8992 ));
8993
8994 // Multiple cursors on one line, with selection -> don't advance
8995 cx.set_state(indoc!(
8996 "fn a() {
8997 ˇdˇog«()ˇ»;
8998 cat();
8999 }"
9000 ));
9001 cx.update_editor(|editor, cx| {
9002 editor.toggle_comments(toggle_comments, cx);
9003 });
9004 cx.assert_editor_state(indoc!(
9005 "fn a() {
9006 // ˇdˇog«()ˇ»;
9007 cat();
9008 }"
9009 ));
9010
9011 // Single cursor on one line -> advance
9012 // Cursor moves to column 0 on blank line
9013 cx.set_state(indoc!(
9014 "fn a() {
9015 ˇdog();
9016
9017 cat();
9018 }"
9019 ));
9020 cx.update_editor(|editor, cx| {
9021 editor.toggle_comments(toggle_comments, cx);
9022 });
9023 cx.assert_editor_state(indoc!(
9024 "fn a() {
9025 // dog();
9026 ˇ
9027 cat();
9028 }"
9029 ));
9030
9031 // Single cursor on one line -> advance
9032 // Cursor starts and ends at column 0
9033 cx.set_state(indoc!(
9034 "fn a() {
9035 ˇ dog();
9036 cat();
9037 }"
9038 ));
9039 cx.update_editor(|editor, cx| {
9040 editor.toggle_comments(toggle_comments, cx);
9041 });
9042 cx.assert_editor_state(indoc!(
9043 "fn a() {
9044 // dog();
9045 ˇ cat();
9046 }"
9047 ));
9048}
9049
9050#[gpui::test]
9051async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9052 init_test(cx, |_| {});
9053
9054 let mut cx = EditorTestContext::new(cx).await;
9055
9056 let html_language = Arc::new(
9057 Language::new(
9058 LanguageConfig {
9059 name: "HTML".into(),
9060 block_comment: Some(("<!-- ".into(), " -->".into())),
9061 ..Default::default()
9062 },
9063 Some(tree_sitter_html::language()),
9064 )
9065 .with_injection_query(
9066 r#"
9067 (script_element
9068 (raw_text) @content
9069 (#set! "language" "javascript"))
9070 "#,
9071 )
9072 .unwrap(),
9073 );
9074
9075 let javascript_language = Arc::new(Language::new(
9076 LanguageConfig {
9077 name: "JavaScript".into(),
9078 line_comments: vec!["// ".into()],
9079 ..Default::default()
9080 },
9081 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9082 ));
9083
9084 cx.language_registry().add(html_language.clone());
9085 cx.language_registry().add(javascript_language.clone());
9086 cx.update_buffer(|buffer, cx| {
9087 buffer.set_language(Some(html_language), cx);
9088 });
9089
9090 // Toggle comments for empty selections
9091 cx.set_state(
9092 &r#"
9093 <p>A</p>ˇ
9094 <p>B</p>ˇ
9095 <p>C</p>ˇ
9096 "#
9097 .unindent(),
9098 );
9099 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9100 cx.assert_editor_state(
9101 &r#"
9102 <!-- <p>A</p>ˇ -->
9103 <!-- <p>B</p>ˇ -->
9104 <!-- <p>C</p>ˇ -->
9105 "#
9106 .unindent(),
9107 );
9108 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9109 cx.assert_editor_state(
9110 &r#"
9111 <p>A</p>ˇ
9112 <p>B</p>ˇ
9113 <p>C</p>ˇ
9114 "#
9115 .unindent(),
9116 );
9117
9118 // Toggle comments for mixture of empty and non-empty selections, where
9119 // multiple selections occupy a given line.
9120 cx.set_state(
9121 &r#"
9122 <p>A«</p>
9123 <p>ˇ»B</p>ˇ
9124 <p>C«</p>
9125 <p>ˇ»D</p>ˇ
9126 "#
9127 .unindent(),
9128 );
9129
9130 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9131 cx.assert_editor_state(
9132 &r#"
9133 <!-- <p>A«</p>
9134 <p>ˇ»B</p>ˇ -->
9135 <!-- <p>C«</p>
9136 <p>ˇ»D</p>ˇ -->
9137 "#
9138 .unindent(),
9139 );
9140 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9141 cx.assert_editor_state(
9142 &r#"
9143 <p>A«</p>
9144 <p>ˇ»B</p>ˇ
9145 <p>C«</p>
9146 <p>ˇ»D</p>ˇ
9147 "#
9148 .unindent(),
9149 );
9150
9151 // Toggle comments when different languages are active for different
9152 // selections.
9153 cx.set_state(
9154 &r#"
9155 ˇ<script>
9156 ˇvar x = new Y();
9157 ˇ</script>
9158 "#
9159 .unindent(),
9160 );
9161 cx.executor().run_until_parked();
9162 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9163 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9164 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9165 cx.assert_editor_state(
9166 &r#"
9167 <!-- ˇ<script> -->
9168 // ˇvar x = new Y();
9169 // ˇ</script>
9170 "#
9171 .unindent(),
9172 );
9173}
9174
9175#[gpui::test]
9176fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9177 init_test(cx, |_| {});
9178
9179 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9180 let multibuffer = cx.new_model(|cx| {
9181 let mut multibuffer = MultiBuffer::new(ReadWrite);
9182 multibuffer.push_excerpts(
9183 buffer.clone(),
9184 [
9185 ExcerptRange {
9186 context: Point::new(0, 0)..Point::new(0, 4),
9187 primary: None,
9188 },
9189 ExcerptRange {
9190 context: Point::new(1, 0)..Point::new(1, 4),
9191 primary: None,
9192 },
9193 ],
9194 cx,
9195 );
9196 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9197 multibuffer
9198 });
9199
9200 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9201 view.update(cx, |view, cx| {
9202 assert_eq!(view.text(cx), "aaaa\nbbbb");
9203 view.change_selections(None, cx, |s| {
9204 s.select_ranges([
9205 Point::new(0, 0)..Point::new(0, 0),
9206 Point::new(1, 0)..Point::new(1, 0),
9207 ])
9208 });
9209
9210 view.handle_input("X", cx);
9211 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9212 assert_eq!(
9213 view.selections.ranges(cx),
9214 [
9215 Point::new(0, 1)..Point::new(0, 1),
9216 Point::new(1, 1)..Point::new(1, 1),
9217 ]
9218 );
9219
9220 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9221 view.change_selections(None, cx, |s| {
9222 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9223 });
9224 view.backspace(&Default::default(), cx);
9225 assert_eq!(view.text(cx), "Xa\nbbb");
9226 assert_eq!(
9227 view.selections.ranges(cx),
9228 [Point::new(1, 0)..Point::new(1, 0)]
9229 );
9230
9231 view.change_selections(None, cx, |s| {
9232 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9233 });
9234 view.backspace(&Default::default(), cx);
9235 assert_eq!(view.text(cx), "X\nbb");
9236 assert_eq!(
9237 view.selections.ranges(cx),
9238 [Point::new(0, 1)..Point::new(0, 1)]
9239 );
9240 });
9241}
9242
9243#[gpui::test]
9244fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9245 init_test(cx, |_| {});
9246
9247 let markers = vec![('[', ']').into(), ('(', ')').into()];
9248 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9249 indoc! {"
9250 [aaaa
9251 (bbbb]
9252 cccc)",
9253 },
9254 markers.clone(),
9255 );
9256 let excerpt_ranges = markers.into_iter().map(|marker| {
9257 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9258 ExcerptRange {
9259 context,
9260 primary: None,
9261 }
9262 });
9263 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9264 let multibuffer = cx.new_model(|cx| {
9265 let mut multibuffer = MultiBuffer::new(ReadWrite);
9266 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9267 multibuffer
9268 });
9269
9270 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9271 view.update(cx, |view, cx| {
9272 let (expected_text, selection_ranges) = marked_text_ranges(
9273 indoc! {"
9274 aaaa
9275 bˇbbb
9276 bˇbbˇb
9277 cccc"
9278 },
9279 true,
9280 );
9281 assert_eq!(view.text(cx), expected_text);
9282 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9283
9284 view.handle_input("X", cx);
9285
9286 let (expected_text, expected_selections) = marked_text_ranges(
9287 indoc! {"
9288 aaaa
9289 bXˇbbXb
9290 bXˇbbXˇb
9291 cccc"
9292 },
9293 false,
9294 );
9295 assert_eq!(view.text(cx), expected_text);
9296 assert_eq!(view.selections.ranges(cx), expected_selections);
9297
9298 view.newline(&Newline, cx);
9299 let (expected_text, expected_selections) = marked_text_ranges(
9300 indoc! {"
9301 aaaa
9302 bX
9303 ˇbbX
9304 b
9305 bX
9306 ˇbbX
9307 ˇb
9308 cccc"
9309 },
9310 false,
9311 );
9312 assert_eq!(view.text(cx), expected_text);
9313 assert_eq!(view.selections.ranges(cx), expected_selections);
9314 });
9315}
9316
9317#[gpui::test]
9318fn test_refresh_selections(cx: &mut TestAppContext) {
9319 init_test(cx, |_| {});
9320
9321 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9322 let mut excerpt1_id = None;
9323 let multibuffer = cx.new_model(|cx| {
9324 let mut multibuffer = MultiBuffer::new(ReadWrite);
9325 excerpt1_id = multibuffer
9326 .push_excerpts(
9327 buffer.clone(),
9328 [
9329 ExcerptRange {
9330 context: Point::new(0, 0)..Point::new(1, 4),
9331 primary: None,
9332 },
9333 ExcerptRange {
9334 context: Point::new(1, 0)..Point::new(2, 4),
9335 primary: None,
9336 },
9337 ],
9338 cx,
9339 )
9340 .into_iter()
9341 .next();
9342 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9343 multibuffer
9344 });
9345
9346 let editor = cx.add_window(|cx| {
9347 let mut editor = build_editor(multibuffer.clone(), cx);
9348 let snapshot = editor.snapshot(cx);
9349 editor.change_selections(None, cx, |s| {
9350 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9351 });
9352 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9353 assert_eq!(
9354 editor.selections.ranges(cx),
9355 [
9356 Point::new(1, 3)..Point::new(1, 3),
9357 Point::new(2, 1)..Point::new(2, 1),
9358 ]
9359 );
9360 editor
9361 });
9362
9363 // Refreshing selections is a no-op when excerpts haven't changed.
9364 _ = editor.update(cx, |editor, cx| {
9365 editor.change_selections(None, cx, |s| s.refresh());
9366 assert_eq!(
9367 editor.selections.ranges(cx),
9368 [
9369 Point::new(1, 3)..Point::new(1, 3),
9370 Point::new(2, 1)..Point::new(2, 1),
9371 ]
9372 );
9373 });
9374
9375 multibuffer.update(cx, |multibuffer, cx| {
9376 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9377 });
9378 _ = editor.update(cx, |editor, cx| {
9379 // Removing an excerpt causes the first selection to become degenerate.
9380 assert_eq!(
9381 editor.selections.ranges(cx),
9382 [
9383 Point::new(0, 0)..Point::new(0, 0),
9384 Point::new(0, 1)..Point::new(0, 1)
9385 ]
9386 );
9387
9388 // Refreshing selections will relocate the first selection to the original buffer
9389 // location.
9390 editor.change_selections(None, cx, |s| s.refresh());
9391 assert_eq!(
9392 editor.selections.ranges(cx),
9393 [
9394 Point::new(0, 1)..Point::new(0, 1),
9395 Point::new(0, 3)..Point::new(0, 3)
9396 ]
9397 );
9398 assert!(editor.selections.pending_anchor().is_some());
9399 });
9400}
9401
9402#[gpui::test]
9403fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9404 init_test(cx, |_| {});
9405
9406 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9407 let mut excerpt1_id = None;
9408 let multibuffer = cx.new_model(|cx| {
9409 let mut multibuffer = MultiBuffer::new(ReadWrite);
9410 excerpt1_id = multibuffer
9411 .push_excerpts(
9412 buffer.clone(),
9413 [
9414 ExcerptRange {
9415 context: Point::new(0, 0)..Point::new(1, 4),
9416 primary: None,
9417 },
9418 ExcerptRange {
9419 context: Point::new(1, 0)..Point::new(2, 4),
9420 primary: None,
9421 },
9422 ],
9423 cx,
9424 )
9425 .into_iter()
9426 .next();
9427 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9428 multibuffer
9429 });
9430
9431 let editor = cx.add_window(|cx| {
9432 let mut editor = build_editor(multibuffer.clone(), cx);
9433 let snapshot = editor.snapshot(cx);
9434 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9435 assert_eq!(
9436 editor.selections.ranges(cx),
9437 [Point::new(1, 3)..Point::new(1, 3)]
9438 );
9439 editor
9440 });
9441
9442 multibuffer.update(cx, |multibuffer, cx| {
9443 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9444 });
9445 _ = editor.update(cx, |editor, cx| {
9446 assert_eq!(
9447 editor.selections.ranges(cx),
9448 [Point::new(0, 0)..Point::new(0, 0)]
9449 );
9450
9451 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9452 editor.change_selections(None, cx, |s| s.refresh());
9453 assert_eq!(
9454 editor.selections.ranges(cx),
9455 [Point::new(0, 3)..Point::new(0, 3)]
9456 );
9457 assert!(editor.selections.pending_anchor().is_some());
9458 });
9459}
9460
9461#[gpui::test]
9462async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9463 init_test(cx, |_| {});
9464
9465 let language = Arc::new(
9466 Language::new(
9467 LanguageConfig {
9468 brackets: BracketPairConfig {
9469 pairs: vec![
9470 BracketPair {
9471 start: "{".to_string(),
9472 end: "}".to_string(),
9473 close: true,
9474 surround: true,
9475 newline: true,
9476 },
9477 BracketPair {
9478 start: "/* ".to_string(),
9479 end: " */".to_string(),
9480 close: true,
9481 surround: true,
9482 newline: true,
9483 },
9484 ],
9485 ..Default::default()
9486 },
9487 ..Default::default()
9488 },
9489 Some(tree_sitter_rust::LANGUAGE.into()),
9490 )
9491 .with_indents_query("")
9492 .unwrap(),
9493 );
9494
9495 let text = concat!(
9496 "{ }\n", //
9497 " x\n", //
9498 " /* */\n", //
9499 "x\n", //
9500 "{{} }\n", //
9501 );
9502
9503 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9504 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9505 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9506 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9507 .await;
9508
9509 view.update(cx, |view, cx| {
9510 view.change_selections(None, cx, |s| {
9511 s.select_display_ranges([
9512 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9513 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9514 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9515 ])
9516 });
9517 view.newline(&Newline, cx);
9518
9519 assert_eq!(
9520 view.buffer().read(cx).read(cx).text(),
9521 concat!(
9522 "{ \n", // Suppress rustfmt
9523 "\n", //
9524 "}\n", //
9525 " x\n", //
9526 " /* \n", //
9527 " \n", //
9528 " */\n", //
9529 "x\n", //
9530 "{{} \n", //
9531 "}\n", //
9532 )
9533 );
9534 });
9535}
9536
9537#[gpui::test]
9538fn test_highlighted_ranges(cx: &mut TestAppContext) {
9539 init_test(cx, |_| {});
9540
9541 let editor = cx.add_window(|cx| {
9542 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9543 build_editor(buffer.clone(), cx)
9544 });
9545
9546 _ = editor.update(cx, |editor, cx| {
9547 struct Type1;
9548 struct Type2;
9549
9550 let buffer = editor.buffer.read(cx).snapshot(cx);
9551
9552 let anchor_range =
9553 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9554
9555 editor.highlight_background::<Type1>(
9556 &[
9557 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9558 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9559 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9560 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9561 ],
9562 |_| Hsla::red(),
9563 cx,
9564 );
9565 editor.highlight_background::<Type2>(
9566 &[
9567 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9568 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9569 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9570 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9571 ],
9572 |_| Hsla::green(),
9573 cx,
9574 );
9575
9576 let snapshot = editor.snapshot(cx);
9577 let mut highlighted_ranges = editor.background_highlights_in_range(
9578 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9579 &snapshot,
9580 cx.theme().colors(),
9581 );
9582 // Enforce a consistent ordering based on color without relying on the ordering of the
9583 // highlight's `TypeId` which is non-executor.
9584 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9585 assert_eq!(
9586 highlighted_ranges,
9587 &[
9588 (
9589 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9590 Hsla::red(),
9591 ),
9592 (
9593 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9594 Hsla::red(),
9595 ),
9596 (
9597 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9598 Hsla::green(),
9599 ),
9600 (
9601 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9602 Hsla::green(),
9603 ),
9604 ]
9605 );
9606 assert_eq!(
9607 editor.background_highlights_in_range(
9608 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9609 &snapshot,
9610 cx.theme().colors(),
9611 ),
9612 &[(
9613 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9614 Hsla::red(),
9615 )]
9616 );
9617 });
9618}
9619
9620#[gpui::test]
9621async fn test_following(cx: &mut gpui::TestAppContext) {
9622 init_test(cx, |_| {});
9623
9624 let fs = FakeFs::new(cx.executor());
9625 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9626
9627 let buffer = project.update(cx, |project, cx| {
9628 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9629 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9630 });
9631 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9632 let follower = cx.update(|cx| {
9633 cx.open_window(
9634 WindowOptions {
9635 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9636 gpui::Point::new(px(0.), px(0.)),
9637 gpui::Point::new(px(10.), px(80.)),
9638 ))),
9639 ..Default::default()
9640 },
9641 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9642 )
9643 .unwrap()
9644 });
9645
9646 let is_still_following = Rc::new(RefCell::new(true));
9647 let follower_edit_event_count = Rc::new(RefCell::new(0));
9648 let pending_update = Rc::new(RefCell::new(None));
9649 _ = follower.update(cx, {
9650 let update = pending_update.clone();
9651 let is_still_following = is_still_following.clone();
9652 let follower_edit_event_count = follower_edit_event_count.clone();
9653 |_, cx| {
9654 cx.subscribe(
9655 &leader.root_view(cx).unwrap(),
9656 move |_, leader, event, cx| {
9657 leader
9658 .read(cx)
9659 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9660 },
9661 )
9662 .detach();
9663
9664 cx.subscribe(
9665 &follower.root_view(cx).unwrap(),
9666 move |_, _, event: &EditorEvent, _cx| {
9667 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9668 *is_still_following.borrow_mut() = false;
9669 }
9670
9671 if let EditorEvent::BufferEdited = event {
9672 *follower_edit_event_count.borrow_mut() += 1;
9673 }
9674 },
9675 )
9676 .detach();
9677 }
9678 });
9679
9680 // Update the selections only
9681 _ = leader.update(cx, |leader, cx| {
9682 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9683 });
9684 follower
9685 .update(cx, |follower, cx| {
9686 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9687 })
9688 .unwrap()
9689 .await
9690 .unwrap();
9691 _ = follower.update(cx, |follower, cx| {
9692 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9693 });
9694 assert!(*is_still_following.borrow());
9695 assert_eq!(*follower_edit_event_count.borrow(), 0);
9696
9697 // Update the scroll position only
9698 _ = leader.update(cx, |leader, cx| {
9699 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9700 });
9701 follower
9702 .update(cx, |follower, cx| {
9703 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9704 })
9705 .unwrap()
9706 .await
9707 .unwrap();
9708 assert_eq!(
9709 follower
9710 .update(cx, |follower, cx| follower.scroll_position(cx))
9711 .unwrap(),
9712 gpui::Point::new(1.5, 3.5)
9713 );
9714 assert!(*is_still_following.borrow());
9715 assert_eq!(*follower_edit_event_count.borrow(), 0);
9716
9717 // Update the selections and scroll position. The follower's scroll position is updated
9718 // via autoscroll, not via the leader's exact scroll position.
9719 _ = leader.update(cx, |leader, cx| {
9720 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9721 leader.request_autoscroll(Autoscroll::newest(), cx);
9722 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9723 });
9724 follower
9725 .update(cx, |follower, cx| {
9726 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9727 })
9728 .unwrap()
9729 .await
9730 .unwrap();
9731 _ = follower.update(cx, |follower, cx| {
9732 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9733 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9734 });
9735 assert!(*is_still_following.borrow());
9736
9737 // Creating a pending selection that precedes another selection
9738 _ = leader.update(cx, |leader, cx| {
9739 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9740 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9741 });
9742 follower
9743 .update(cx, |follower, cx| {
9744 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9745 })
9746 .unwrap()
9747 .await
9748 .unwrap();
9749 _ = follower.update(cx, |follower, cx| {
9750 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9751 });
9752 assert!(*is_still_following.borrow());
9753
9754 // Extend the pending selection so that it surrounds another selection
9755 _ = leader.update(cx, |leader, cx| {
9756 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9757 });
9758 follower
9759 .update(cx, |follower, cx| {
9760 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9761 })
9762 .unwrap()
9763 .await
9764 .unwrap();
9765 _ = follower.update(cx, |follower, cx| {
9766 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9767 });
9768
9769 // Scrolling locally breaks the follow
9770 _ = follower.update(cx, |follower, cx| {
9771 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9772 follower.set_scroll_anchor(
9773 ScrollAnchor {
9774 anchor: top_anchor,
9775 offset: gpui::Point::new(0.0, 0.5),
9776 },
9777 cx,
9778 );
9779 });
9780 assert!(!(*is_still_following.borrow()));
9781}
9782
9783#[gpui::test]
9784async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9785 init_test(cx, |_| {});
9786
9787 let fs = FakeFs::new(cx.executor());
9788 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9789 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9790 let pane = workspace
9791 .update(cx, |workspace, _| workspace.active_pane().clone())
9792 .unwrap();
9793
9794 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9795
9796 let leader = pane.update(cx, |_, cx| {
9797 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9798 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9799 });
9800
9801 // Start following the editor when it has no excerpts.
9802 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9803 let follower_1 = cx
9804 .update_window(*workspace.deref(), |_, cx| {
9805 Editor::from_state_proto(
9806 workspace.root_view(cx).unwrap(),
9807 ViewId {
9808 creator: Default::default(),
9809 id: 0,
9810 },
9811 &mut state_message,
9812 cx,
9813 )
9814 })
9815 .unwrap()
9816 .unwrap()
9817 .await
9818 .unwrap();
9819
9820 let update_message = Rc::new(RefCell::new(None));
9821 follower_1.update(cx, {
9822 let update = update_message.clone();
9823 |_, cx| {
9824 cx.subscribe(&leader, move |_, leader, event, cx| {
9825 leader
9826 .read(cx)
9827 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9828 })
9829 .detach();
9830 }
9831 });
9832
9833 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9834 (
9835 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9836 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9837 )
9838 });
9839
9840 // Insert some excerpts.
9841 leader.update(cx, |leader, cx| {
9842 leader.buffer.update(cx, |multibuffer, cx| {
9843 let excerpt_ids = multibuffer.push_excerpts(
9844 buffer_1.clone(),
9845 [
9846 ExcerptRange {
9847 context: 1..6,
9848 primary: None,
9849 },
9850 ExcerptRange {
9851 context: 12..15,
9852 primary: None,
9853 },
9854 ExcerptRange {
9855 context: 0..3,
9856 primary: None,
9857 },
9858 ],
9859 cx,
9860 );
9861 multibuffer.insert_excerpts_after(
9862 excerpt_ids[0],
9863 buffer_2.clone(),
9864 [
9865 ExcerptRange {
9866 context: 8..12,
9867 primary: None,
9868 },
9869 ExcerptRange {
9870 context: 0..6,
9871 primary: None,
9872 },
9873 ],
9874 cx,
9875 );
9876 });
9877 });
9878
9879 // Apply the update of adding the excerpts.
9880 follower_1
9881 .update(cx, |follower, cx| {
9882 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9883 })
9884 .await
9885 .unwrap();
9886 assert_eq!(
9887 follower_1.update(cx, |editor, cx| editor.text(cx)),
9888 leader.update(cx, |editor, cx| editor.text(cx))
9889 );
9890 update_message.borrow_mut().take();
9891
9892 // Start following separately after it already has excerpts.
9893 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9894 let follower_2 = cx
9895 .update_window(*workspace.deref(), |_, cx| {
9896 Editor::from_state_proto(
9897 workspace.root_view(cx).unwrap().clone(),
9898 ViewId {
9899 creator: Default::default(),
9900 id: 0,
9901 },
9902 &mut state_message,
9903 cx,
9904 )
9905 })
9906 .unwrap()
9907 .unwrap()
9908 .await
9909 .unwrap();
9910 assert_eq!(
9911 follower_2.update(cx, |editor, cx| editor.text(cx)),
9912 leader.update(cx, |editor, cx| editor.text(cx))
9913 );
9914
9915 // Remove some excerpts.
9916 leader.update(cx, |leader, cx| {
9917 leader.buffer.update(cx, |multibuffer, cx| {
9918 let excerpt_ids = multibuffer.excerpt_ids();
9919 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9920 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9921 });
9922 });
9923
9924 // Apply the update of removing the excerpts.
9925 follower_1
9926 .update(cx, |follower, cx| {
9927 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9928 })
9929 .await
9930 .unwrap();
9931 follower_2
9932 .update(cx, |follower, cx| {
9933 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9934 })
9935 .await
9936 .unwrap();
9937 update_message.borrow_mut().take();
9938 assert_eq!(
9939 follower_1.update(cx, |editor, cx| editor.text(cx)),
9940 leader.update(cx, |editor, cx| editor.text(cx))
9941 );
9942}
9943
9944#[gpui::test]
9945async fn go_to_prev_overlapping_diagnostic(
9946 executor: BackgroundExecutor,
9947 cx: &mut gpui::TestAppContext,
9948) {
9949 init_test(cx, |_| {});
9950
9951 let mut cx = EditorTestContext::new(cx).await;
9952 let lsp_store =
9953 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9954
9955 cx.set_state(indoc! {"
9956 ˇfn func(abc def: i32) -> u32 {
9957 }
9958 "});
9959
9960 cx.update(|cx| {
9961 lsp_store.update(cx, |lsp_store, cx| {
9962 lsp_store
9963 .update_diagnostics(
9964 LanguageServerId(0),
9965 lsp::PublishDiagnosticsParams {
9966 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9967 version: None,
9968 diagnostics: vec![
9969 lsp::Diagnostic {
9970 range: lsp::Range::new(
9971 lsp::Position::new(0, 11),
9972 lsp::Position::new(0, 12),
9973 ),
9974 severity: Some(lsp::DiagnosticSeverity::ERROR),
9975 ..Default::default()
9976 },
9977 lsp::Diagnostic {
9978 range: lsp::Range::new(
9979 lsp::Position::new(0, 12),
9980 lsp::Position::new(0, 15),
9981 ),
9982 severity: Some(lsp::DiagnosticSeverity::ERROR),
9983 ..Default::default()
9984 },
9985 lsp::Diagnostic {
9986 range: lsp::Range::new(
9987 lsp::Position::new(0, 25),
9988 lsp::Position::new(0, 28),
9989 ),
9990 severity: Some(lsp::DiagnosticSeverity::ERROR),
9991 ..Default::default()
9992 },
9993 ],
9994 },
9995 &[],
9996 cx,
9997 )
9998 .unwrap()
9999 });
10000 });
10001
10002 executor.run_until_parked();
10003
10004 cx.update_editor(|editor, cx| {
10005 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10006 });
10007
10008 cx.assert_editor_state(indoc! {"
10009 fn func(abc def: i32) -> ˇu32 {
10010 }
10011 "});
10012
10013 cx.update_editor(|editor, cx| {
10014 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10015 });
10016
10017 cx.assert_editor_state(indoc! {"
10018 fn func(abc ˇdef: i32) -> u32 {
10019 }
10020 "});
10021
10022 cx.update_editor(|editor, cx| {
10023 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10024 });
10025
10026 cx.assert_editor_state(indoc! {"
10027 fn func(abcˇ def: i32) -> u32 {
10028 }
10029 "});
10030
10031 cx.update_editor(|editor, cx| {
10032 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10033 });
10034
10035 cx.assert_editor_state(indoc! {"
10036 fn func(abc def: i32) -> ˇu32 {
10037 }
10038 "});
10039}
10040
10041#[gpui::test]
10042async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10043 init_test(cx, |_| {});
10044
10045 let mut cx = EditorTestContext::new(cx).await;
10046
10047 cx.set_state(indoc! {"
10048 fn func(abˇc def: i32) -> u32 {
10049 }
10050 "});
10051 let lsp_store =
10052 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10053
10054 cx.update(|cx| {
10055 lsp_store.update(cx, |lsp_store, cx| {
10056 lsp_store.update_diagnostics(
10057 LanguageServerId(0),
10058 lsp::PublishDiagnosticsParams {
10059 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10060 version: None,
10061 diagnostics: vec![lsp::Diagnostic {
10062 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10063 severity: Some(lsp::DiagnosticSeverity::ERROR),
10064 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10065 ..Default::default()
10066 }],
10067 },
10068 &[],
10069 cx,
10070 )
10071 })
10072 }).unwrap();
10073 cx.run_until_parked();
10074 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10075 cx.run_until_parked();
10076 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10077}
10078
10079#[gpui::test]
10080async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10081 init_test(cx, |_| {});
10082
10083 let mut cx = EditorTestContext::new(cx).await;
10084
10085 let diff_base = r#"
10086 use some::mod;
10087
10088 const A: u32 = 42;
10089
10090 fn main() {
10091 println!("hello");
10092
10093 println!("world");
10094 }
10095 "#
10096 .unindent();
10097
10098 // Edits are modified, removed, modified, added
10099 cx.set_state(
10100 &r#"
10101 use some::modified;
10102
10103 ˇ
10104 fn main() {
10105 println!("hello there");
10106
10107 println!("around the");
10108 println!("world");
10109 }
10110 "#
10111 .unindent(),
10112 );
10113
10114 cx.set_diff_base(&diff_base);
10115 executor.run_until_parked();
10116
10117 cx.update_editor(|editor, cx| {
10118 //Wrap around the bottom of the buffer
10119 for _ in 0..3 {
10120 editor.go_to_next_hunk(&GoToHunk, cx);
10121 }
10122 });
10123
10124 cx.assert_editor_state(
10125 &r#"
10126 ˇuse some::modified;
10127
10128
10129 fn main() {
10130 println!("hello there");
10131
10132 println!("around the");
10133 println!("world");
10134 }
10135 "#
10136 .unindent(),
10137 );
10138
10139 cx.update_editor(|editor, cx| {
10140 //Wrap around the top of the buffer
10141 for _ in 0..2 {
10142 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10143 }
10144 });
10145
10146 cx.assert_editor_state(
10147 &r#"
10148 use some::modified;
10149
10150
10151 fn main() {
10152 ˇ println!("hello there");
10153
10154 println!("around the");
10155 println!("world");
10156 }
10157 "#
10158 .unindent(),
10159 );
10160
10161 cx.update_editor(|editor, cx| {
10162 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10163 });
10164
10165 cx.assert_editor_state(
10166 &r#"
10167 use some::modified;
10168
10169 ˇ
10170 fn main() {
10171 println!("hello there");
10172
10173 println!("around the");
10174 println!("world");
10175 }
10176 "#
10177 .unindent(),
10178 );
10179
10180 cx.update_editor(|editor, cx| {
10181 for _ in 0..3 {
10182 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10183 }
10184 });
10185
10186 cx.assert_editor_state(
10187 &r#"
10188 use some::modified;
10189
10190
10191 fn main() {
10192 ˇ println!("hello there");
10193
10194 println!("around the");
10195 println!("world");
10196 }
10197 "#
10198 .unindent(),
10199 );
10200
10201 cx.update_editor(|editor, cx| {
10202 editor.fold(&Fold, cx);
10203
10204 //Make sure that the fold only gets one hunk
10205 for _ in 0..4 {
10206 editor.go_to_next_hunk(&GoToHunk, cx);
10207 }
10208 });
10209
10210 cx.assert_editor_state(
10211 &r#"
10212 ˇuse some::modified;
10213
10214
10215 fn main() {
10216 println!("hello there");
10217
10218 println!("around the");
10219 println!("world");
10220 }
10221 "#
10222 .unindent(),
10223 );
10224}
10225
10226#[test]
10227fn test_split_words() {
10228 fn split(text: &str) -> Vec<&str> {
10229 split_words(text).collect()
10230 }
10231
10232 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10233 assert_eq!(split("hello_world"), &["hello_", "world"]);
10234 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10235 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10236 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10237 assert_eq!(split("helloworld"), &["helloworld"]);
10238
10239 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10240}
10241
10242#[gpui::test]
10243async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10244 init_test(cx, |_| {});
10245
10246 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10247 let mut assert = |before, after| {
10248 let _state_context = cx.set_state(before);
10249 cx.update_editor(|editor, cx| {
10250 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10251 });
10252 cx.assert_editor_state(after);
10253 };
10254
10255 // Outside bracket jumps to outside of matching bracket
10256 assert("console.logˇ(var);", "console.log(var)ˇ;");
10257 assert("console.log(var)ˇ;", "console.logˇ(var);");
10258
10259 // Inside bracket jumps to inside of matching bracket
10260 assert("console.log(ˇvar);", "console.log(varˇ);");
10261 assert("console.log(varˇ);", "console.log(ˇvar);");
10262
10263 // When outside a bracket and inside, favor jumping to the inside bracket
10264 assert(
10265 "console.log('foo', [1, 2, 3]ˇ);",
10266 "console.log(ˇ'foo', [1, 2, 3]);",
10267 );
10268 assert(
10269 "console.log(ˇ'foo', [1, 2, 3]);",
10270 "console.log('foo', [1, 2, 3]ˇ);",
10271 );
10272
10273 // Bias forward if two options are equally likely
10274 assert(
10275 "let result = curried_fun()ˇ();",
10276 "let result = curried_fun()()ˇ;",
10277 );
10278
10279 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10280 assert(
10281 indoc! {"
10282 function test() {
10283 console.log('test')ˇ
10284 }"},
10285 indoc! {"
10286 function test() {
10287 console.logˇ('test')
10288 }"},
10289 );
10290}
10291
10292#[gpui::test]
10293async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10294 init_test(cx, |_| {});
10295
10296 let fs = FakeFs::new(cx.executor());
10297 fs.insert_tree(
10298 "/a",
10299 json!({
10300 "main.rs": "fn main() { let a = 5; }",
10301 "other.rs": "// Test file",
10302 }),
10303 )
10304 .await;
10305 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10306
10307 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10308 language_registry.add(Arc::new(Language::new(
10309 LanguageConfig {
10310 name: "Rust".into(),
10311 matcher: LanguageMatcher {
10312 path_suffixes: vec!["rs".to_string()],
10313 ..Default::default()
10314 },
10315 brackets: BracketPairConfig {
10316 pairs: vec![BracketPair {
10317 start: "{".to_string(),
10318 end: "}".to_string(),
10319 close: true,
10320 surround: true,
10321 newline: true,
10322 }],
10323 disabled_scopes_by_bracket_ix: Vec::new(),
10324 },
10325 ..Default::default()
10326 },
10327 Some(tree_sitter_rust::LANGUAGE.into()),
10328 )));
10329 let mut fake_servers = language_registry.register_fake_lsp(
10330 "Rust",
10331 FakeLspAdapter {
10332 capabilities: lsp::ServerCapabilities {
10333 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10334 first_trigger_character: "{".to_string(),
10335 more_trigger_character: None,
10336 }),
10337 ..Default::default()
10338 },
10339 ..Default::default()
10340 },
10341 );
10342
10343 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10344
10345 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10346
10347 let worktree_id = workspace
10348 .update(cx, |workspace, cx| {
10349 workspace.project().update(cx, |project, cx| {
10350 project.worktrees(cx).next().unwrap().read(cx).id()
10351 })
10352 })
10353 .unwrap();
10354
10355 let buffer = project
10356 .update(cx, |project, cx| {
10357 project.open_local_buffer("/a/main.rs", cx)
10358 })
10359 .await
10360 .unwrap();
10361 let editor_handle = workspace
10362 .update(cx, |workspace, cx| {
10363 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10364 })
10365 .unwrap()
10366 .await
10367 .unwrap()
10368 .downcast::<Editor>()
10369 .unwrap();
10370
10371 cx.executor().start_waiting();
10372 let fake_server = fake_servers.next().await.unwrap();
10373
10374 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10375 assert_eq!(
10376 params.text_document_position.text_document.uri,
10377 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10378 );
10379 assert_eq!(
10380 params.text_document_position.position,
10381 lsp::Position::new(0, 21),
10382 );
10383
10384 Ok(Some(vec![lsp::TextEdit {
10385 new_text: "]".to_string(),
10386 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10387 }]))
10388 });
10389
10390 editor_handle.update(cx, |editor, cx| {
10391 editor.focus(cx);
10392 editor.change_selections(None, cx, |s| {
10393 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10394 });
10395 editor.handle_input("{", cx);
10396 });
10397
10398 cx.executor().run_until_parked();
10399
10400 buffer.update(cx, |buffer, _| {
10401 assert_eq!(
10402 buffer.text(),
10403 "fn main() { let a = {5}; }",
10404 "No extra braces from on type formatting should appear in the buffer"
10405 )
10406 });
10407}
10408
10409#[gpui::test]
10410async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10411 init_test(cx, |_| {});
10412
10413 let fs = FakeFs::new(cx.executor());
10414 fs.insert_tree(
10415 "/a",
10416 json!({
10417 "main.rs": "fn main() { let a = 5; }",
10418 "other.rs": "// Test file",
10419 }),
10420 )
10421 .await;
10422
10423 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10424
10425 let server_restarts = Arc::new(AtomicUsize::new(0));
10426 let closure_restarts = Arc::clone(&server_restarts);
10427 let language_server_name = "test language server";
10428 let language_name: LanguageName = "Rust".into();
10429
10430 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10431 language_registry.add(Arc::new(Language::new(
10432 LanguageConfig {
10433 name: language_name.clone(),
10434 matcher: LanguageMatcher {
10435 path_suffixes: vec!["rs".to_string()],
10436 ..Default::default()
10437 },
10438 ..Default::default()
10439 },
10440 Some(tree_sitter_rust::LANGUAGE.into()),
10441 )));
10442 let mut fake_servers = language_registry.register_fake_lsp(
10443 "Rust",
10444 FakeLspAdapter {
10445 name: language_server_name,
10446 initialization_options: Some(json!({
10447 "testOptionValue": true
10448 })),
10449 initializer: Some(Box::new(move |fake_server| {
10450 let task_restarts = Arc::clone(&closure_restarts);
10451 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10452 task_restarts.fetch_add(1, atomic::Ordering::Release);
10453 futures::future::ready(Ok(()))
10454 });
10455 })),
10456 ..Default::default()
10457 },
10458 );
10459
10460 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10461 let _buffer = project
10462 .update(cx, |project, cx| {
10463 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10464 })
10465 .await
10466 .unwrap();
10467 let _fake_server = fake_servers.next().await.unwrap();
10468 update_test_language_settings(cx, |language_settings| {
10469 language_settings.languages.insert(
10470 language_name.clone(),
10471 LanguageSettingsContent {
10472 tab_size: NonZeroU32::new(8),
10473 ..Default::default()
10474 },
10475 );
10476 });
10477 cx.executor().run_until_parked();
10478 assert_eq!(
10479 server_restarts.load(atomic::Ordering::Acquire),
10480 0,
10481 "Should not restart LSP server on an unrelated change"
10482 );
10483
10484 update_test_project_settings(cx, |project_settings| {
10485 project_settings.lsp.insert(
10486 "Some other server name".into(),
10487 LspSettings {
10488 binary: None,
10489 settings: None,
10490 initialization_options: Some(json!({
10491 "some other init value": false
10492 })),
10493 },
10494 );
10495 });
10496 cx.executor().run_until_parked();
10497 assert_eq!(
10498 server_restarts.load(atomic::Ordering::Acquire),
10499 0,
10500 "Should not restart LSP server on an unrelated LSP settings change"
10501 );
10502
10503 update_test_project_settings(cx, |project_settings| {
10504 project_settings.lsp.insert(
10505 language_server_name.into(),
10506 LspSettings {
10507 binary: None,
10508 settings: None,
10509 initialization_options: Some(json!({
10510 "anotherInitValue": false
10511 })),
10512 },
10513 );
10514 });
10515 cx.executor().run_until_parked();
10516 assert_eq!(
10517 server_restarts.load(atomic::Ordering::Acquire),
10518 1,
10519 "Should restart LSP server on a related LSP settings change"
10520 );
10521
10522 update_test_project_settings(cx, |project_settings| {
10523 project_settings.lsp.insert(
10524 language_server_name.into(),
10525 LspSettings {
10526 binary: None,
10527 settings: None,
10528 initialization_options: Some(json!({
10529 "anotherInitValue": false
10530 })),
10531 },
10532 );
10533 });
10534 cx.executor().run_until_parked();
10535 assert_eq!(
10536 server_restarts.load(atomic::Ordering::Acquire),
10537 1,
10538 "Should not restart LSP server on a related LSP settings change that is the same"
10539 );
10540
10541 update_test_project_settings(cx, |project_settings| {
10542 project_settings.lsp.insert(
10543 language_server_name.into(),
10544 LspSettings {
10545 binary: None,
10546 settings: None,
10547 initialization_options: None,
10548 },
10549 );
10550 });
10551 cx.executor().run_until_parked();
10552 assert_eq!(
10553 server_restarts.load(atomic::Ordering::Acquire),
10554 2,
10555 "Should restart LSP server on another related LSP settings change"
10556 );
10557}
10558
10559#[gpui::test]
10560async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10561 init_test(cx, |_| {});
10562
10563 let mut cx = EditorLspTestContext::new_rust(
10564 lsp::ServerCapabilities {
10565 completion_provider: Some(lsp::CompletionOptions {
10566 trigger_characters: Some(vec![".".to_string()]),
10567 resolve_provider: Some(true),
10568 ..Default::default()
10569 }),
10570 ..Default::default()
10571 },
10572 cx,
10573 )
10574 .await;
10575
10576 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10577 cx.simulate_keystroke(".");
10578 let completion_item = lsp::CompletionItem {
10579 label: "some".into(),
10580 kind: Some(lsp::CompletionItemKind::SNIPPET),
10581 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10582 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10583 kind: lsp::MarkupKind::Markdown,
10584 value: "```rust\nSome(2)\n```".to_string(),
10585 })),
10586 deprecated: Some(false),
10587 sort_text: Some("fffffff2".to_string()),
10588 filter_text: Some("some".to_string()),
10589 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10590 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10591 range: lsp::Range {
10592 start: lsp::Position {
10593 line: 0,
10594 character: 22,
10595 },
10596 end: lsp::Position {
10597 line: 0,
10598 character: 22,
10599 },
10600 },
10601 new_text: "Some(2)".to_string(),
10602 })),
10603 additional_text_edits: Some(vec![lsp::TextEdit {
10604 range: lsp::Range {
10605 start: lsp::Position {
10606 line: 0,
10607 character: 20,
10608 },
10609 end: lsp::Position {
10610 line: 0,
10611 character: 22,
10612 },
10613 },
10614 new_text: "".to_string(),
10615 }]),
10616 ..Default::default()
10617 };
10618
10619 let closure_completion_item = completion_item.clone();
10620 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10621 let task_completion_item = closure_completion_item.clone();
10622 async move {
10623 Ok(Some(lsp::CompletionResponse::Array(vec![
10624 task_completion_item,
10625 ])))
10626 }
10627 });
10628
10629 request.next().await;
10630
10631 cx.condition(|editor, _| editor.context_menu_visible())
10632 .await;
10633 let apply_additional_edits = cx.update_editor(|editor, cx| {
10634 editor
10635 .confirm_completion(&ConfirmCompletion::default(), cx)
10636 .unwrap()
10637 });
10638 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10639
10640 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10641 let task_completion_item = completion_item.clone();
10642 async move { Ok(task_completion_item) }
10643 })
10644 .next()
10645 .await
10646 .unwrap();
10647 apply_additional_edits.await.unwrap();
10648 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10649}
10650
10651#[gpui::test]
10652async fn test_completions_resolve_updates_labels_if_filter_text_matches(
10653 cx: &mut gpui::TestAppContext,
10654) {
10655 init_test(cx, |_| {});
10656
10657 let mut cx = EditorLspTestContext::new_rust(
10658 lsp::ServerCapabilities {
10659 completion_provider: Some(lsp::CompletionOptions {
10660 trigger_characters: Some(vec![".".to_string()]),
10661 resolve_provider: Some(true),
10662 ..Default::default()
10663 }),
10664 ..Default::default()
10665 },
10666 cx,
10667 )
10668 .await;
10669
10670 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10671 cx.simulate_keystroke(".");
10672
10673 let item1 = lsp::CompletionItem {
10674 label: "id".to_string(),
10675 filter_text: Some("id".to_string()),
10676 detail: None,
10677 documentation: None,
10678 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10679 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10680 new_text: ".id".to_string(),
10681 })),
10682 ..lsp::CompletionItem::default()
10683 };
10684
10685 let item2 = lsp::CompletionItem {
10686 label: "other".to_string(),
10687 filter_text: Some("other".to_string()),
10688 detail: None,
10689 documentation: None,
10690 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10691 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10692 new_text: ".other".to_string(),
10693 })),
10694 ..lsp::CompletionItem::default()
10695 };
10696
10697 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10698 let item1 = item1.clone();
10699 let item2 = item2.clone();
10700 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
10701 })
10702 .next()
10703 .await;
10704
10705 cx.condition(|editor, _| editor.context_menu_visible())
10706 .await;
10707 cx.update_editor(|editor, _| {
10708 let context_menu = editor.context_menu.borrow_mut();
10709 let context_menu = context_menu
10710 .as_ref()
10711 .expect("Should have the context menu deployed");
10712 match context_menu {
10713 CodeContextMenu::Completions(completions_menu) => {
10714 let completions = completions_menu.completions.borrow_mut();
10715 assert_eq!(
10716 completions
10717 .iter()
10718 .map(|completion| &completion.label.text)
10719 .collect::<Vec<_>>(),
10720 vec!["id", "other"]
10721 )
10722 }
10723 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10724 }
10725 });
10726
10727 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10728 Ok(lsp::CompletionItem {
10729 label: "method id()".to_string(),
10730 filter_text: Some("id".to_string()),
10731 detail: Some("Now resolved!".to_string()),
10732 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10733 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10734 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10735 new_text: ".id".to_string(),
10736 })),
10737 ..lsp::CompletionItem::default()
10738 })
10739 })
10740 .next()
10741 .await;
10742 cx.run_until_parked();
10743
10744 cx.update_editor(|editor, cx| {
10745 editor.context_menu_next(&Default::default(), cx);
10746 });
10747
10748 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10749 Ok(lsp::CompletionItem {
10750 label: "invalid changed label".to_string(),
10751 detail: Some("Now resolved!".to_string()),
10752 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10753 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10754 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10755 new_text: ".id".to_string(),
10756 })),
10757 ..lsp::CompletionItem::default()
10758 })
10759 })
10760 .next()
10761 .await;
10762 cx.run_until_parked();
10763
10764 cx.update_editor(|editor, _| {
10765 let context_menu = editor.context_menu.borrow_mut();
10766 let context_menu = context_menu
10767 .as_ref()
10768 .expect("Should have the context menu deployed");
10769 match context_menu {
10770 CodeContextMenu::Completions(completions_menu) => {
10771 let completions = completions_menu.completions.borrow_mut();
10772 assert_eq!(
10773 completions
10774 .iter()
10775 .map(|completion| &completion.label.text)
10776 .collect::<Vec<_>>(),
10777 vec!["method id()", "other"],
10778 "Should update first completion label, but not second as the filter text did not match."
10779 );
10780 }
10781 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10782 }
10783 });
10784}
10785
10786#[gpui::test]
10787async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10788 init_test(cx, |_| {});
10789
10790 let mut cx = EditorLspTestContext::new_rust(
10791 lsp::ServerCapabilities {
10792 completion_provider: Some(lsp::CompletionOptions {
10793 trigger_characters: Some(vec![".".to_string()]),
10794 resolve_provider: Some(true),
10795 ..Default::default()
10796 }),
10797 ..Default::default()
10798 },
10799 cx,
10800 )
10801 .await;
10802
10803 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10804 cx.simulate_keystroke(".");
10805
10806 let default_commit_characters = vec!["?".to_string()];
10807 let default_data = json!({ "very": "special"});
10808 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10809 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10810 let default_edit_range = lsp::Range {
10811 start: lsp::Position {
10812 line: 0,
10813 character: 5,
10814 },
10815 end: lsp::Position {
10816 line: 0,
10817 character: 5,
10818 },
10819 };
10820
10821 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10822 let expect_first_item = Arc::new(AtomicBool::new(true));
10823 cx.lsp
10824 .server
10825 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10826 let closure_default_data = default_data.clone();
10827 let closure_resolve_requests_number = resolve_requests_number.clone();
10828 let closure_expect_first_item = expect_first_item.clone();
10829 let closure_default_commit_characters = default_commit_characters.clone();
10830 move |item_to_resolve, _| {
10831 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10832 let default_data = closure_default_data.clone();
10833 let default_commit_characters = closure_default_commit_characters.clone();
10834 let expect_first_item = closure_expect_first_item.clone();
10835 async move {
10836 if expect_first_item.load(atomic::Ordering::Acquire) {
10837 assert_eq!(
10838 item_to_resolve.label, "Some(2)",
10839 "Should have selected the first item"
10840 );
10841 assert_eq!(
10842 item_to_resolve.data,
10843 Some(json!({ "very": "special"})),
10844 "First item should bring its own data for resolving"
10845 );
10846 assert_eq!(
10847 item_to_resolve.commit_characters,
10848 Some(default_commit_characters),
10849 "First item had no own commit characters and should inherit the default ones"
10850 );
10851 assert!(
10852 matches!(
10853 item_to_resolve.text_edit,
10854 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10855 ),
10856 "First item should bring its own edit range for resolving"
10857 );
10858 assert_eq!(
10859 item_to_resolve.insert_text_format,
10860 Some(default_insert_text_format),
10861 "First item had no own insert text format and should inherit the default one"
10862 );
10863 assert_eq!(
10864 item_to_resolve.insert_text_mode,
10865 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10866 "First item should bring its own insert text mode for resolving"
10867 );
10868 Ok(item_to_resolve)
10869 } else {
10870 assert_eq!(
10871 item_to_resolve.label, "vec![2]",
10872 "Should have selected the last item"
10873 );
10874 assert_eq!(
10875 item_to_resolve.data,
10876 Some(default_data),
10877 "Last item has no own resolve data and should inherit the default one"
10878 );
10879 assert_eq!(
10880 item_to_resolve.commit_characters,
10881 Some(default_commit_characters),
10882 "Last item had no own commit characters and should inherit the default ones"
10883 );
10884 assert_eq!(
10885 item_to_resolve.text_edit,
10886 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10887 range: default_edit_range,
10888 new_text: "vec![2]".to_string()
10889 })),
10890 "Last item had no own edit range and should inherit the default one"
10891 );
10892 assert_eq!(
10893 item_to_resolve.insert_text_format,
10894 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10895 "Last item should bring its own insert text format for resolving"
10896 );
10897 assert_eq!(
10898 item_to_resolve.insert_text_mode,
10899 Some(default_insert_text_mode),
10900 "Last item had no own insert text mode and should inherit the default one"
10901 );
10902
10903 Ok(item_to_resolve)
10904 }
10905 }
10906 }
10907 }).detach();
10908
10909 let completion_data = default_data.clone();
10910 let completion_characters = default_commit_characters.clone();
10911 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10912 let default_data = completion_data.clone();
10913 let default_commit_characters = completion_characters.clone();
10914 async move {
10915 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10916 items: vec![
10917 lsp::CompletionItem {
10918 label: "Some(2)".into(),
10919 insert_text: Some("Some(2)".into()),
10920 data: Some(json!({ "very": "special"})),
10921 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10922 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10923 lsp::InsertReplaceEdit {
10924 new_text: "Some(2)".to_string(),
10925 insert: lsp::Range::default(),
10926 replace: lsp::Range::default(),
10927 },
10928 )),
10929 ..lsp::CompletionItem::default()
10930 },
10931 lsp::CompletionItem {
10932 label: "vec![2]".into(),
10933 insert_text: Some("vec![2]".into()),
10934 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10935 ..lsp::CompletionItem::default()
10936 },
10937 ],
10938 item_defaults: Some(lsp::CompletionListItemDefaults {
10939 data: Some(default_data.clone()),
10940 commit_characters: Some(default_commit_characters.clone()),
10941 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10942 default_edit_range,
10943 )),
10944 insert_text_format: Some(default_insert_text_format),
10945 insert_text_mode: Some(default_insert_text_mode),
10946 }),
10947 ..lsp::CompletionList::default()
10948 })))
10949 }
10950 })
10951 .next()
10952 .await;
10953
10954 cx.condition(|editor, _| editor.context_menu_visible())
10955 .await;
10956 cx.run_until_parked();
10957 cx.update_editor(|editor, _| {
10958 let menu = editor.context_menu.borrow_mut();
10959 match menu.as_ref().expect("should have the completions menu") {
10960 CodeContextMenu::Completions(completions_menu) => {
10961 assert_eq!(
10962 completion_menu_entries(&completions_menu.entries),
10963 vec!["Some(2)", "vec![2]"]
10964 );
10965 }
10966 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10967 }
10968 });
10969 assert_eq!(
10970 resolve_requests_number.load(atomic::Ordering::Acquire),
10971 1,
10972 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10973 );
10974
10975 cx.update_editor(|editor, cx| {
10976 editor.context_menu_first(&ContextMenuFirst, cx);
10977 });
10978 cx.run_until_parked();
10979 assert_eq!(
10980 resolve_requests_number.load(atomic::Ordering::Acquire),
10981 2,
10982 "After re-selecting the first item, another resolve request should have been sent"
10983 );
10984
10985 expect_first_item.store(false, atomic::Ordering::Release);
10986 cx.update_editor(|editor, cx| {
10987 editor.context_menu_last(&ContextMenuLast, cx);
10988 });
10989 cx.run_until_parked();
10990 assert_eq!(
10991 resolve_requests_number.load(atomic::Ordering::Acquire),
10992 3,
10993 "After selecting the other item, another resolve request should have been sent"
10994 );
10995}
10996
10997#[gpui::test]
10998async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10999 init_test(cx, |_| {});
11000
11001 let mut cx = EditorLspTestContext::new(
11002 Language::new(
11003 LanguageConfig {
11004 matcher: LanguageMatcher {
11005 path_suffixes: vec!["jsx".into()],
11006 ..Default::default()
11007 },
11008 overrides: [(
11009 "element".into(),
11010 LanguageConfigOverride {
11011 word_characters: Override::Set(['-'].into_iter().collect()),
11012 ..Default::default()
11013 },
11014 )]
11015 .into_iter()
11016 .collect(),
11017 ..Default::default()
11018 },
11019 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11020 )
11021 .with_override_query("(jsx_self_closing_element) @element")
11022 .unwrap(),
11023 lsp::ServerCapabilities {
11024 completion_provider: Some(lsp::CompletionOptions {
11025 trigger_characters: Some(vec![":".to_string()]),
11026 ..Default::default()
11027 }),
11028 ..Default::default()
11029 },
11030 cx,
11031 )
11032 .await;
11033
11034 cx.lsp
11035 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11036 Ok(Some(lsp::CompletionResponse::Array(vec![
11037 lsp::CompletionItem {
11038 label: "bg-blue".into(),
11039 ..Default::default()
11040 },
11041 lsp::CompletionItem {
11042 label: "bg-red".into(),
11043 ..Default::default()
11044 },
11045 lsp::CompletionItem {
11046 label: "bg-yellow".into(),
11047 ..Default::default()
11048 },
11049 ])))
11050 });
11051
11052 cx.set_state(r#"<p class="bgˇ" />"#);
11053
11054 // Trigger completion when typing a dash, because the dash is an extra
11055 // word character in the 'element' scope, which contains the cursor.
11056 cx.simulate_keystroke("-");
11057 cx.executor().run_until_parked();
11058 cx.update_editor(|editor, _| {
11059 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11060 {
11061 assert_eq!(
11062 completion_menu_entries(&menu.entries),
11063 &["bg-red", "bg-blue", "bg-yellow"]
11064 );
11065 } else {
11066 panic!("expected completion menu to be open");
11067 }
11068 });
11069
11070 cx.simulate_keystroke("l");
11071 cx.executor().run_until_parked();
11072 cx.update_editor(|editor, _| {
11073 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11074 {
11075 assert_eq!(
11076 completion_menu_entries(&menu.entries),
11077 &["bg-blue", "bg-yellow"]
11078 );
11079 } else {
11080 panic!("expected completion menu to be open");
11081 }
11082 });
11083
11084 // When filtering completions, consider the character after the '-' to
11085 // be the start of a subword.
11086 cx.set_state(r#"<p class="yelˇ" />"#);
11087 cx.simulate_keystroke("l");
11088 cx.executor().run_until_parked();
11089 cx.update_editor(|editor, _| {
11090 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11091 {
11092 assert_eq!(completion_menu_entries(&menu.entries), &["bg-yellow"]);
11093 } else {
11094 panic!("expected completion menu to be open");
11095 }
11096 });
11097}
11098
11099fn completion_menu_entries(entries: &[CompletionEntry]) -> Vec<&str> {
11100 entries
11101 .iter()
11102 .flat_map(|e| match e {
11103 CompletionEntry::Match(mat) => Some(mat.string.as_str()),
11104 _ => None,
11105 })
11106 .collect()
11107}
11108
11109#[gpui::test]
11110async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11111 init_test(cx, |settings| {
11112 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11113 FormatterList(vec![Formatter::Prettier].into()),
11114 ))
11115 });
11116
11117 let fs = FakeFs::new(cx.executor());
11118 fs.insert_file("/file.ts", Default::default()).await;
11119
11120 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11121 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11122
11123 language_registry.add(Arc::new(Language::new(
11124 LanguageConfig {
11125 name: "TypeScript".into(),
11126 matcher: LanguageMatcher {
11127 path_suffixes: vec!["ts".to_string()],
11128 ..Default::default()
11129 },
11130 ..Default::default()
11131 },
11132 Some(tree_sitter_rust::LANGUAGE.into()),
11133 )));
11134 update_test_language_settings(cx, |settings| {
11135 settings.defaults.prettier = Some(PrettierSettings {
11136 allowed: true,
11137 ..PrettierSettings::default()
11138 });
11139 });
11140
11141 let test_plugin = "test_plugin";
11142 let _ = language_registry.register_fake_lsp(
11143 "TypeScript",
11144 FakeLspAdapter {
11145 prettier_plugins: vec![test_plugin],
11146 ..Default::default()
11147 },
11148 );
11149
11150 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11151 let buffer = project
11152 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11153 .await
11154 .unwrap();
11155
11156 let buffer_text = "one\ntwo\nthree\n";
11157 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11158 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11159 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11160
11161 editor
11162 .update(cx, |editor, cx| {
11163 editor.perform_format(
11164 project.clone(),
11165 FormatTrigger::Manual,
11166 FormatTarget::Buffer,
11167 cx,
11168 )
11169 })
11170 .unwrap()
11171 .await;
11172 assert_eq!(
11173 editor.update(cx, |editor, cx| editor.text(cx)),
11174 buffer_text.to_string() + prettier_format_suffix,
11175 "Test prettier formatting was not applied to the original buffer text",
11176 );
11177
11178 update_test_language_settings(cx, |settings| {
11179 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11180 });
11181 let format = editor.update(cx, |editor, cx| {
11182 editor.perform_format(
11183 project.clone(),
11184 FormatTrigger::Manual,
11185 FormatTarget::Buffer,
11186 cx,
11187 )
11188 });
11189 format.await.unwrap();
11190 assert_eq!(
11191 editor.update(cx, |editor, cx| editor.text(cx)),
11192 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11193 "Autoformatting (via test prettier) was not applied to the original buffer text",
11194 );
11195}
11196
11197#[gpui::test]
11198async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11199 init_test(cx, |_| {});
11200 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11201 let base_text = indoc! {r#"
11202 struct Row;
11203 struct Row1;
11204 struct Row2;
11205
11206 struct Row4;
11207 struct Row5;
11208 struct Row6;
11209
11210 struct Row8;
11211 struct Row9;
11212 struct Row10;"#};
11213
11214 // When addition hunks are not adjacent to carets, no hunk revert is performed
11215 assert_hunk_revert(
11216 indoc! {r#"struct Row;
11217 struct Row1;
11218 struct Row1.1;
11219 struct Row1.2;
11220 struct Row2;ˇ
11221
11222 struct Row4;
11223 struct Row5;
11224 struct Row6;
11225
11226 struct Row8;
11227 ˇstruct Row9;
11228 struct Row9.1;
11229 struct Row9.2;
11230 struct Row9.3;
11231 struct Row10;"#},
11232 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11233 indoc! {r#"struct Row;
11234 struct Row1;
11235 struct Row1.1;
11236 struct Row1.2;
11237 struct Row2;ˇ
11238
11239 struct Row4;
11240 struct Row5;
11241 struct Row6;
11242
11243 struct Row8;
11244 ˇstruct Row9;
11245 struct Row9.1;
11246 struct Row9.2;
11247 struct Row9.3;
11248 struct Row10;"#},
11249 base_text,
11250 &mut cx,
11251 );
11252 // Same for selections
11253 assert_hunk_revert(
11254 indoc! {r#"struct Row;
11255 struct Row1;
11256 struct Row2;
11257 struct Row2.1;
11258 struct Row2.2;
11259 «ˇ
11260 struct Row4;
11261 struct» Row5;
11262 «struct Row6;
11263 ˇ»
11264 struct Row9.1;
11265 struct Row9.2;
11266 struct Row9.3;
11267 struct Row8;
11268 struct Row9;
11269 struct Row10;"#},
11270 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11271 indoc! {r#"struct Row;
11272 struct Row1;
11273 struct Row2;
11274 struct Row2.1;
11275 struct Row2.2;
11276 «ˇ
11277 struct Row4;
11278 struct» Row5;
11279 «struct Row6;
11280 ˇ»
11281 struct Row9.1;
11282 struct Row9.2;
11283 struct Row9.3;
11284 struct Row8;
11285 struct Row9;
11286 struct Row10;"#},
11287 base_text,
11288 &mut cx,
11289 );
11290
11291 // When carets and selections intersect the addition hunks, those are reverted.
11292 // Adjacent carets got merged.
11293 assert_hunk_revert(
11294 indoc! {r#"struct Row;
11295 ˇ// something on the top
11296 struct Row1;
11297 struct Row2;
11298 struct Roˇw3.1;
11299 struct Row2.2;
11300 struct Row2.3;ˇ
11301
11302 struct Row4;
11303 struct ˇRow5.1;
11304 struct Row5.2;
11305 struct «Rowˇ»5.3;
11306 struct Row5;
11307 struct Row6;
11308 ˇ
11309 struct Row9.1;
11310 struct «Rowˇ»9.2;
11311 struct «ˇRow»9.3;
11312 struct Row8;
11313 struct Row9;
11314 «ˇ// something on bottom»
11315 struct Row10;"#},
11316 vec![
11317 DiffHunkStatus::Added,
11318 DiffHunkStatus::Added,
11319 DiffHunkStatus::Added,
11320 DiffHunkStatus::Added,
11321 DiffHunkStatus::Added,
11322 ],
11323 indoc! {r#"struct Row;
11324 ˇstruct Row1;
11325 struct Row2;
11326 ˇ
11327 struct Row4;
11328 ˇstruct Row5;
11329 struct Row6;
11330 ˇ
11331 ˇstruct Row8;
11332 struct Row9;
11333 ˇstruct Row10;"#},
11334 base_text,
11335 &mut cx,
11336 );
11337}
11338
11339#[gpui::test]
11340async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11341 init_test(cx, |_| {});
11342 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11343 let base_text = indoc! {r#"
11344 struct Row;
11345 struct Row1;
11346 struct Row2;
11347
11348 struct Row4;
11349 struct Row5;
11350 struct Row6;
11351
11352 struct Row8;
11353 struct Row9;
11354 struct Row10;"#};
11355
11356 // Modification hunks behave the same as the addition ones.
11357 assert_hunk_revert(
11358 indoc! {r#"struct Row;
11359 struct Row1;
11360 struct Row33;
11361 ˇ
11362 struct Row4;
11363 struct Row5;
11364 struct Row6;
11365 ˇ
11366 struct Row99;
11367 struct Row9;
11368 struct Row10;"#},
11369 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11370 indoc! {r#"struct Row;
11371 struct Row1;
11372 struct Row33;
11373 ˇ
11374 struct Row4;
11375 struct Row5;
11376 struct Row6;
11377 ˇ
11378 struct Row99;
11379 struct Row9;
11380 struct Row10;"#},
11381 base_text,
11382 &mut cx,
11383 );
11384 assert_hunk_revert(
11385 indoc! {r#"struct Row;
11386 struct Row1;
11387 struct Row33;
11388 «ˇ
11389 struct Row4;
11390 struct» Row5;
11391 «struct Row6;
11392 ˇ»
11393 struct Row99;
11394 struct Row9;
11395 struct Row10;"#},
11396 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11397 indoc! {r#"struct Row;
11398 struct Row1;
11399 struct Row33;
11400 «ˇ
11401 struct Row4;
11402 struct» Row5;
11403 «struct Row6;
11404 ˇ»
11405 struct Row99;
11406 struct Row9;
11407 struct Row10;"#},
11408 base_text,
11409 &mut cx,
11410 );
11411
11412 assert_hunk_revert(
11413 indoc! {r#"ˇstruct Row1.1;
11414 struct Row1;
11415 «ˇstr»uct Row22;
11416
11417 struct ˇRow44;
11418 struct Row5;
11419 struct «Rˇ»ow66;ˇ
11420
11421 «struˇ»ct Row88;
11422 struct Row9;
11423 struct Row1011;ˇ"#},
11424 vec![
11425 DiffHunkStatus::Modified,
11426 DiffHunkStatus::Modified,
11427 DiffHunkStatus::Modified,
11428 DiffHunkStatus::Modified,
11429 DiffHunkStatus::Modified,
11430 DiffHunkStatus::Modified,
11431 ],
11432 indoc! {r#"struct Row;
11433 ˇstruct Row1;
11434 struct Row2;
11435 ˇ
11436 struct Row4;
11437 ˇstruct Row5;
11438 struct Row6;
11439 ˇ
11440 struct Row8;
11441 ˇstruct Row9;
11442 struct Row10;ˇ"#},
11443 base_text,
11444 &mut cx,
11445 );
11446}
11447
11448#[gpui::test]
11449async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11450 init_test(cx, |_| {});
11451 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11452 let base_text = indoc! {r#"struct Row;
11453struct Row1;
11454struct Row2;
11455
11456struct Row4;
11457struct Row5;
11458struct Row6;
11459
11460struct Row8;
11461struct Row9;
11462struct Row10;"#};
11463
11464 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11465 assert_hunk_revert(
11466 indoc! {r#"struct Row;
11467 struct Row2;
11468
11469 ˇstruct Row4;
11470 struct Row5;
11471 struct Row6;
11472 ˇ
11473 struct Row8;
11474 struct Row10;"#},
11475 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11476 indoc! {r#"struct Row;
11477 struct Row2;
11478
11479 ˇstruct Row4;
11480 struct Row5;
11481 struct Row6;
11482 ˇ
11483 struct Row8;
11484 struct Row10;"#},
11485 base_text,
11486 &mut cx,
11487 );
11488 assert_hunk_revert(
11489 indoc! {r#"struct Row;
11490 struct Row2;
11491
11492 «ˇstruct Row4;
11493 struct» Row5;
11494 «struct Row6;
11495 ˇ»
11496 struct Row8;
11497 struct Row10;"#},
11498 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11499 indoc! {r#"struct Row;
11500 struct Row2;
11501
11502 «ˇstruct Row4;
11503 struct» Row5;
11504 «struct Row6;
11505 ˇ»
11506 struct Row8;
11507 struct Row10;"#},
11508 base_text,
11509 &mut cx,
11510 );
11511
11512 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11513 assert_hunk_revert(
11514 indoc! {r#"struct Row;
11515 ˇstruct Row2;
11516
11517 struct Row4;
11518 struct Row5;
11519 struct Row6;
11520
11521 struct Row8;ˇ
11522 struct Row10;"#},
11523 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11524 indoc! {r#"struct Row;
11525 struct Row1;
11526 ˇstruct Row2;
11527
11528 struct Row4;
11529 struct Row5;
11530 struct Row6;
11531
11532 struct Row8;ˇ
11533 struct Row9;
11534 struct Row10;"#},
11535 base_text,
11536 &mut cx,
11537 );
11538 assert_hunk_revert(
11539 indoc! {r#"struct Row;
11540 struct Row2«ˇ;
11541 struct Row4;
11542 struct» Row5;
11543 «struct Row6;
11544
11545 struct Row8;ˇ»
11546 struct Row10;"#},
11547 vec![
11548 DiffHunkStatus::Removed,
11549 DiffHunkStatus::Removed,
11550 DiffHunkStatus::Removed,
11551 ],
11552 indoc! {r#"struct Row;
11553 struct Row1;
11554 struct Row2«ˇ;
11555
11556 struct Row4;
11557 struct» Row5;
11558 «struct Row6;
11559
11560 struct Row8;ˇ»
11561 struct Row9;
11562 struct Row10;"#},
11563 base_text,
11564 &mut cx,
11565 );
11566}
11567
11568#[gpui::test]
11569async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11570 init_test(cx, |_| {});
11571
11572 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11573 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11574 let base_text_3 =
11575 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11576
11577 let text_1 = edit_first_char_of_every_line(base_text_1);
11578 let text_2 = edit_first_char_of_every_line(base_text_2);
11579 let text_3 = edit_first_char_of_every_line(base_text_3);
11580
11581 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11582 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11583 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11584
11585 let multibuffer = cx.new_model(|cx| {
11586 let mut multibuffer = MultiBuffer::new(ReadWrite);
11587 multibuffer.push_excerpts(
11588 buffer_1.clone(),
11589 [
11590 ExcerptRange {
11591 context: Point::new(0, 0)..Point::new(3, 0),
11592 primary: None,
11593 },
11594 ExcerptRange {
11595 context: Point::new(5, 0)..Point::new(7, 0),
11596 primary: None,
11597 },
11598 ExcerptRange {
11599 context: Point::new(9, 0)..Point::new(10, 4),
11600 primary: None,
11601 },
11602 ],
11603 cx,
11604 );
11605 multibuffer.push_excerpts(
11606 buffer_2.clone(),
11607 [
11608 ExcerptRange {
11609 context: Point::new(0, 0)..Point::new(3, 0),
11610 primary: None,
11611 },
11612 ExcerptRange {
11613 context: Point::new(5, 0)..Point::new(7, 0),
11614 primary: None,
11615 },
11616 ExcerptRange {
11617 context: Point::new(9, 0)..Point::new(10, 4),
11618 primary: None,
11619 },
11620 ],
11621 cx,
11622 );
11623 multibuffer.push_excerpts(
11624 buffer_3.clone(),
11625 [
11626 ExcerptRange {
11627 context: Point::new(0, 0)..Point::new(3, 0),
11628 primary: None,
11629 },
11630 ExcerptRange {
11631 context: Point::new(5, 0)..Point::new(7, 0),
11632 primary: None,
11633 },
11634 ExcerptRange {
11635 context: Point::new(9, 0)..Point::new(10, 4),
11636 primary: None,
11637 },
11638 ],
11639 cx,
11640 );
11641 multibuffer
11642 });
11643
11644 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11645 editor.update(cx, |editor, cx| {
11646 for (buffer, diff_base) in [
11647 (buffer_1.clone(), base_text_1),
11648 (buffer_2.clone(), base_text_2),
11649 (buffer_3.clone(), base_text_3),
11650 ] {
11651 let change_set = cx.new_model(|cx| {
11652 BufferChangeSet::new_with_base_text(
11653 diff_base.to_string(),
11654 buffer.read(cx).text_snapshot(),
11655 cx,
11656 )
11657 });
11658 editor.diff_map.add_change_set(change_set, cx)
11659 }
11660 });
11661 cx.executor().run_until_parked();
11662
11663 editor.update(cx, |editor, cx| {
11664 assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
11665 editor.select_all(&SelectAll, cx);
11666 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11667 });
11668 cx.executor().run_until_parked();
11669
11670 // When all ranges are selected, all buffer hunks are reverted.
11671 editor.update(cx, |editor, cx| {
11672 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");
11673 });
11674 buffer_1.update(cx, |buffer, _| {
11675 assert_eq!(buffer.text(), base_text_1);
11676 });
11677 buffer_2.update(cx, |buffer, _| {
11678 assert_eq!(buffer.text(), base_text_2);
11679 });
11680 buffer_3.update(cx, |buffer, _| {
11681 assert_eq!(buffer.text(), base_text_3);
11682 });
11683
11684 editor.update(cx, |editor, cx| {
11685 editor.undo(&Default::default(), cx);
11686 });
11687
11688 editor.update(cx, |editor, cx| {
11689 editor.change_selections(None, cx, |s| {
11690 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11691 });
11692 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11693 });
11694
11695 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11696 // but not affect buffer_2 and its related excerpts.
11697 editor.update(cx, |editor, cx| {
11698 assert_eq!(
11699 editor.text(cx),
11700 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
11701 );
11702 });
11703 buffer_1.update(cx, |buffer, _| {
11704 assert_eq!(buffer.text(), base_text_1);
11705 });
11706 buffer_2.update(cx, |buffer, _| {
11707 assert_eq!(
11708 buffer.text(),
11709 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11710 );
11711 });
11712 buffer_3.update(cx, |buffer, _| {
11713 assert_eq!(
11714 buffer.text(),
11715 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11716 );
11717 });
11718
11719 fn edit_first_char_of_every_line(text: &str) -> String {
11720 text.split('\n')
11721 .map(|line| format!("X{}", &line[1..]))
11722 .collect::<Vec<_>>()
11723 .join("\n")
11724 }
11725}
11726
11727#[gpui::test]
11728async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11729 init_test(cx, |_| {});
11730
11731 let cols = 4;
11732 let rows = 10;
11733 let sample_text_1 = sample_text(rows, cols, 'a');
11734 assert_eq!(
11735 sample_text_1,
11736 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11737 );
11738 let sample_text_2 = sample_text(rows, cols, 'l');
11739 assert_eq!(
11740 sample_text_2,
11741 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11742 );
11743 let sample_text_3 = sample_text(rows, cols, 'v');
11744 assert_eq!(
11745 sample_text_3,
11746 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11747 );
11748
11749 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11750 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11751 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11752
11753 let multi_buffer = cx.new_model(|cx| {
11754 let mut multibuffer = MultiBuffer::new(ReadWrite);
11755 multibuffer.push_excerpts(
11756 buffer_1.clone(),
11757 [
11758 ExcerptRange {
11759 context: Point::new(0, 0)..Point::new(3, 0),
11760 primary: None,
11761 },
11762 ExcerptRange {
11763 context: Point::new(5, 0)..Point::new(7, 0),
11764 primary: None,
11765 },
11766 ExcerptRange {
11767 context: Point::new(9, 0)..Point::new(10, 4),
11768 primary: None,
11769 },
11770 ],
11771 cx,
11772 );
11773 multibuffer.push_excerpts(
11774 buffer_2.clone(),
11775 [
11776 ExcerptRange {
11777 context: Point::new(0, 0)..Point::new(3, 0),
11778 primary: None,
11779 },
11780 ExcerptRange {
11781 context: Point::new(5, 0)..Point::new(7, 0),
11782 primary: None,
11783 },
11784 ExcerptRange {
11785 context: Point::new(9, 0)..Point::new(10, 4),
11786 primary: None,
11787 },
11788 ],
11789 cx,
11790 );
11791 multibuffer.push_excerpts(
11792 buffer_3.clone(),
11793 [
11794 ExcerptRange {
11795 context: Point::new(0, 0)..Point::new(3, 0),
11796 primary: None,
11797 },
11798 ExcerptRange {
11799 context: Point::new(5, 0)..Point::new(7, 0),
11800 primary: None,
11801 },
11802 ExcerptRange {
11803 context: Point::new(9, 0)..Point::new(10, 4),
11804 primary: None,
11805 },
11806 ],
11807 cx,
11808 );
11809 multibuffer
11810 });
11811
11812 let fs = FakeFs::new(cx.executor());
11813 fs.insert_tree(
11814 "/a",
11815 json!({
11816 "main.rs": sample_text_1,
11817 "other.rs": sample_text_2,
11818 "lib.rs": sample_text_3,
11819 }),
11820 )
11821 .await;
11822 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11823 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11824 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11825 let multi_buffer_editor = cx.new_view(|cx| {
11826 Editor::new(
11827 EditorMode::Full,
11828 multi_buffer,
11829 Some(project.clone()),
11830 true,
11831 cx,
11832 )
11833 });
11834 let multibuffer_item_id = workspace
11835 .update(cx, |workspace, cx| {
11836 assert!(
11837 workspace.active_item(cx).is_none(),
11838 "active item should be None before the first item is added"
11839 );
11840 workspace.add_item_to_active_pane(
11841 Box::new(multi_buffer_editor.clone()),
11842 None,
11843 true,
11844 cx,
11845 );
11846 let active_item = workspace
11847 .active_item(cx)
11848 .expect("should have an active item after adding the multi buffer");
11849 assert!(
11850 !active_item.is_singleton(cx),
11851 "A multi buffer was expected to active after adding"
11852 );
11853 active_item.item_id()
11854 })
11855 .unwrap();
11856 cx.executor().run_until_parked();
11857
11858 multi_buffer_editor.update(cx, |editor, cx| {
11859 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11860 editor.open_excerpts(&OpenExcerpts, cx);
11861 });
11862 cx.executor().run_until_parked();
11863 let first_item_id = workspace
11864 .update(cx, |workspace, cx| {
11865 let active_item = workspace
11866 .active_item(cx)
11867 .expect("should have an active item after navigating into the 1st buffer");
11868 let first_item_id = active_item.item_id();
11869 assert_ne!(
11870 first_item_id, multibuffer_item_id,
11871 "Should navigate into the 1st buffer and activate it"
11872 );
11873 assert!(
11874 active_item.is_singleton(cx),
11875 "New active item should be a singleton buffer"
11876 );
11877 assert_eq!(
11878 active_item
11879 .act_as::<Editor>(cx)
11880 .expect("should have navigated into an editor for the 1st buffer")
11881 .read(cx)
11882 .text(cx),
11883 sample_text_1
11884 );
11885
11886 workspace
11887 .go_back(workspace.active_pane().downgrade(), cx)
11888 .detach_and_log_err(cx);
11889
11890 first_item_id
11891 })
11892 .unwrap();
11893 cx.executor().run_until_parked();
11894 workspace
11895 .update(cx, |workspace, cx| {
11896 let active_item = workspace
11897 .active_item(cx)
11898 .expect("should have an active item after navigating back");
11899 assert_eq!(
11900 active_item.item_id(),
11901 multibuffer_item_id,
11902 "Should navigate back to the multi buffer"
11903 );
11904 assert!(!active_item.is_singleton(cx));
11905 })
11906 .unwrap();
11907
11908 multi_buffer_editor.update(cx, |editor, cx| {
11909 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11910 s.select_ranges(Some(39..40))
11911 });
11912 editor.open_excerpts(&OpenExcerpts, cx);
11913 });
11914 cx.executor().run_until_parked();
11915 let second_item_id = workspace
11916 .update(cx, |workspace, cx| {
11917 let active_item = workspace
11918 .active_item(cx)
11919 .expect("should have an active item after navigating into the 2nd buffer");
11920 let second_item_id = active_item.item_id();
11921 assert_ne!(
11922 second_item_id, multibuffer_item_id,
11923 "Should navigate away from the multibuffer"
11924 );
11925 assert_ne!(
11926 second_item_id, first_item_id,
11927 "Should navigate into the 2nd buffer and activate it"
11928 );
11929 assert!(
11930 active_item.is_singleton(cx),
11931 "New active item should be a singleton buffer"
11932 );
11933 assert_eq!(
11934 active_item
11935 .act_as::<Editor>(cx)
11936 .expect("should have navigated into an editor")
11937 .read(cx)
11938 .text(cx),
11939 sample_text_2
11940 );
11941
11942 workspace
11943 .go_back(workspace.active_pane().downgrade(), cx)
11944 .detach_and_log_err(cx);
11945
11946 second_item_id
11947 })
11948 .unwrap();
11949 cx.executor().run_until_parked();
11950 workspace
11951 .update(cx, |workspace, cx| {
11952 let active_item = workspace
11953 .active_item(cx)
11954 .expect("should have an active item after navigating back from the 2nd buffer");
11955 assert_eq!(
11956 active_item.item_id(),
11957 multibuffer_item_id,
11958 "Should navigate back from the 2nd buffer to the multi buffer"
11959 );
11960 assert!(!active_item.is_singleton(cx));
11961 })
11962 .unwrap();
11963
11964 multi_buffer_editor.update(cx, |editor, cx| {
11965 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11966 s.select_ranges(Some(70..70))
11967 });
11968 editor.open_excerpts(&OpenExcerpts, cx);
11969 });
11970 cx.executor().run_until_parked();
11971 workspace
11972 .update(cx, |workspace, cx| {
11973 let active_item = workspace
11974 .active_item(cx)
11975 .expect("should have an active item after navigating into the 3rd buffer");
11976 let third_item_id = active_item.item_id();
11977 assert_ne!(
11978 third_item_id, multibuffer_item_id,
11979 "Should navigate into the 3rd buffer and activate it"
11980 );
11981 assert_ne!(third_item_id, first_item_id);
11982 assert_ne!(third_item_id, second_item_id);
11983 assert!(
11984 active_item.is_singleton(cx),
11985 "New active item should be a singleton buffer"
11986 );
11987 assert_eq!(
11988 active_item
11989 .act_as::<Editor>(cx)
11990 .expect("should have navigated into an editor")
11991 .read(cx)
11992 .text(cx),
11993 sample_text_3
11994 );
11995
11996 workspace
11997 .go_back(workspace.active_pane().downgrade(), cx)
11998 .detach_and_log_err(cx);
11999 })
12000 .unwrap();
12001 cx.executor().run_until_parked();
12002 workspace
12003 .update(cx, |workspace, cx| {
12004 let active_item = workspace
12005 .active_item(cx)
12006 .expect("should have an active item after navigating back from the 3rd buffer");
12007 assert_eq!(
12008 active_item.item_id(),
12009 multibuffer_item_id,
12010 "Should navigate back from the 3rd buffer to the multi buffer"
12011 );
12012 assert!(!active_item.is_singleton(cx));
12013 })
12014 .unwrap();
12015}
12016
12017#[gpui::test]
12018async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12019 init_test(cx, |_| {});
12020
12021 let mut cx = EditorTestContext::new(cx).await;
12022
12023 let diff_base = r#"
12024 use some::mod;
12025
12026 const A: u32 = 42;
12027
12028 fn main() {
12029 println!("hello");
12030
12031 println!("world");
12032 }
12033 "#
12034 .unindent();
12035
12036 cx.set_state(
12037 &r#"
12038 use some::modified;
12039
12040 ˇ
12041 fn main() {
12042 println!("hello there");
12043
12044 println!("around the");
12045 println!("world");
12046 }
12047 "#
12048 .unindent(),
12049 );
12050
12051 cx.set_diff_base(&diff_base);
12052 executor.run_until_parked();
12053
12054 cx.update_editor(|editor, cx| {
12055 editor.go_to_next_hunk(&GoToHunk, cx);
12056 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12057 });
12058 executor.run_until_parked();
12059 cx.assert_state_with_diff(
12060 r#"
12061 use some::modified;
12062
12063
12064 fn main() {
12065 - println!("hello");
12066 + ˇ println!("hello there");
12067
12068 println!("around the");
12069 println!("world");
12070 }
12071 "#
12072 .unindent(),
12073 );
12074
12075 cx.update_editor(|editor, cx| {
12076 for _ in 0..3 {
12077 editor.go_to_next_hunk(&GoToHunk, cx);
12078 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12079 }
12080 });
12081 executor.run_until_parked();
12082 cx.assert_state_with_diff(
12083 r#"
12084 - use some::mod;
12085 + use some::modified;
12086
12087 - const A: u32 = 42;
12088 ˇ
12089 fn main() {
12090 - println!("hello");
12091 + println!("hello there");
12092
12093 + println!("around the");
12094 println!("world");
12095 }
12096 "#
12097 .unindent(),
12098 );
12099
12100 cx.update_editor(|editor, cx| {
12101 editor.cancel(&Cancel, cx);
12102 });
12103
12104 cx.assert_state_with_diff(
12105 r#"
12106 use some::modified;
12107
12108 ˇ
12109 fn main() {
12110 println!("hello there");
12111
12112 println!("around the");
12113 println!("world");
12114 }
12115 "#
12116 .unindent(),
12117 );
12118}
12119
12120#[gpui::test]
12121async fn test_diff_base_change_with_expanded_diff_hunks(
12122 executor: BackgroundExecutor,
12123 cx: &mut gpui::TestAppContext,
12124) {
12125 init_test(cx, |_| {});
12126
12127 let mut cx = EditorTestContext::new(cx).await;
12128
12129 let diff_base = r#"
12130 use some::mod1;
12131 use some::mod2;
12132
12133 const A: u32 = 42;
12134 const B: u32 = 42;
12135 const C: u32 = 42;
12136
12137 fn main() {
12138 println!("hello");
12139
12140 println!("world");
12141 }
12142 "#
12143 .unindent();
12144
12145 cx.set_state(
12146 &r#"
12147 use some::mod2;
12148
12149 const A: u32 = 42;
12150 const C: u32 = 42;
12151
12152 fn main(ˇ) {
12153 //println!("hello");
12154
12155 println!("world");
12156 //
12157 //
12158 }
12159 "#
12160 .unindent(),
12161 );
12162
12163 cx.set_diff_base(&diff_base);
12164 executor.run_until_parked();
12165
12166 cx.update_editor(|editor, cx| {
12167 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12168 });
12169 executor.run_until_parked();
12170 cx.assert_state_with_diff(
12171 r#"
12172 - use some::mod1;
12173 use some::mod2;
12174
12175 const A: u32 = 42;
12176 - const B: u32 = 42;
12177 const C: u32 = 42;
12178
12179 fn main(ˇ) {
12180 - println!("hello");
12181 + //println!("hello");
12182
12183 println!("world");
12184 + //
12185 + //
12186 }
12187 "#
12188 .unindent(),
12189 );
12190
12191 cx.set_diff_base("new diff base!");
12192 executor.run_until_parked();
12193 cx.assert_state_with_diff(
12194 r#"
12195 use some::mod2;
12196
12197 const A: u32 = 42;
12198 const C: u32 = 42;
12199
12200 fn main(ˇ) {
12201 //println!("hello");
12202
12203 println!("world");
12204 //
12205 //
12206 }
12207 "#
12208 .unindent(),
12209 );
12210
12211 cx.update_editor(|editor, cx| {
12212 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12213 });
12214 executor.run_until_parked();
12215 cx.assert_state_with_diff(
12216 r#"
12217 - new diff base!
12218 + use some::mod2;
12219 +
12220 + const A: u32 = 42;
12221 + const C: u32 = 42;
12222 +
12223 + fn main(ˇ) {
12224 + //println!("hello");
12225 +
12226 + println!("world");
12227 + //
12228 + //
12229 + }
12230 "#
12231 .unindent(),
12232 );
12233}
12234
12235#[gpui::test]
12236async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12237 init_test(cx, |_| {});
12238
12239 let mut cx = EditorTestContext::new(cx).await;
12240
12241 let diff_base = r#"
12242 use some::mod1;
12243 use some::mod2;
12244
12245 const A: u32 = 42;
12246 const B: u32 = 42;
12247 const C: u32 = 42;
12248
12249 fn main() {
12250 println!("hello");
12251
12252 println!("world");
12253 }
12254
12255 fn another() {
12256 println!("another");
12257 }
12258
12259 fn another2() {
12260 println!("another2");
12261 }
12262 "#
12263 .unindent();
12264
12265 cx.set_state(
12266 &r#"
12267 «use some::mod2;
12268
12269 const A: u32 = 42;
12270 const C: u32 = 42;
12271
12272 fn main() {
12273 //println!("hello");
12274
12275 println!("world");
12276 //
12277 //ˇ»
12278 }
12279
12280 fn another() {
12281 println!("another");
12282 println!("another");
12283 }
12284
12285 println!("another2");
12286 }
12287 "#
12288 .unindent(),
12289 );
12290
12291 cx.set_diff_base(&diff_base);
12292 executor.run_until_parked();
12293
12294 cx.update_editor(|editor, cx| {
12295 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12296 });
12297 executor.run_until_parked();
12298
12299 cx.assert_state_with_diff(
12300 r#"
12301 - use some::mod1;
12302 «use some::mod2;
12303
12304 const A: u32 = 42;
12305 - const B: u32 = 42;
12306 const C: u32 = 42;
12307
12308 fn main() {
12309 - println!("hello");
12310 + //println!("hello");
12311
12312 println!("world");
12313 + //
12314 + //ˇ»
12315 }
12316
12317 fn another() {
12318 println!("another");
12319 + println!("another");
12320 }
12321
12322 - fn another2() {
12323 println!("another2");
12324 }
12325 "#
12326 .unindent(),
12327 );
12328
12329 // Fold across some of the diff hunks. They should no longer appear expanded.
12330 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12331 cx.executor().run_until_parked();
12332
12333 // Hunks are not shown if their position is within a fold
12334 cx.assert_state_with_diff(
12335 r#"
12336 «use some::mod2;
12337
12338 const A: u32 = 42;
12339 const C: u32 = 42;
12340
12341 fn main() {
12342 //println!("hello");
12343
12344 println!("world");
12345 //
12346 //ˇ»
12347 }
12348
12349 fn another() {
12350 println!("another");
12351 + println!("another");
12352 }
12353
12354 - fn another2() {
12355 println!("another2");
12356 }
12357 "#
12358 .unindent(),
12359 );
12360
12361 cx.update_editor(|editor, cx| {
12362 editor.select_all(&SelectAll, cx);
12363 editor.unfold_lines(&UnfoldLines, cx);
12364 });
12365 cx.executor().run_until_parked();
12366
12367 // The deletions reappear when unfolding.
12368 cx.assert_state_with_diff(
12369 r#"
12370 - use some::mod1;
12371 «use some::mod2;
12372
12373 const A: u32 = 42;
12374 - const B: u32 = 42;
12375 const C: u32 = 42;
12376
12377 fn main() {
12378 - println!("hello");
12379 + //println!("hello");
12380
12381 println!("world");
12382 + //
12383 + //
12384 }
12385
12386 fn another() {
12387 println!("another");
12388 + println!("another");
12389 }
12390
12391 - fn another2() {
12392 println!("another2");
12393 }
12394 ˇ»"#
12395 .unindent(),
12396 );
12397}
12398
12399#[gpui::test]
12400async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12401 init_test(cx, |_| {});
12402
12403 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12404 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12405 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12406 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12407 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12408 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12409
12410 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12411 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12412 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12413
12414 let multi_buffer = cx.new_model(|cx| {
12415 let mut multibuffer = MultiBuffer::new(ReadWrite);
12416 multibuffer.push_excerpts(
12417 buffer_1.clone(),
12418 [
12419 ExcerptRange {
12420 context: Point::new(0, 0)..Point::new(3, 0),
12421 primary: None,
12422 },
12423 ExcerptRange {
12424 context: Point::new(5, 0)..Point::new(7, 0),
12425 primary: None,
12426 },
12427 ExcerptRange {
12428 context: Point::new(9, 0)..Point::new(10, 3),
12429 primary: None,
12430 },
12431 ],
12432 cx,
12433 );
12434 multibuffer.push_excerpts(
12435 buffer_2.clone(),
12436 [
12437 ExcerptRange {
12438 context: Point::new(0, 0)..Point::new(3, 0),
12439 primary: None,
12440 },
12441 ExcerptRange {
12442 context: Point::new(5, 0)..Point::new(7, 0),
12443 primary: None,
12444 },
12445 ExcerptRange {
12446 context: Point::new(9, 0)..Point::new(10, 3),
12447 primary: None,
12448 },
12449 ],
12450 cx,
12451 );
12452 multibuffer.push_excerpts(
12453 buffer_3.clone(),
12454 [
12455 ExcerptRange {
12456 context: Point::new(0, 0)..Point::new(3, 0),
12457 primary: None,
12458 },
12459 ExcerptRange {
12460 context: Point::new(5, 0)..Point::new(7, 0),
12461 primary: None,
12462 },
12463 ExcerptRange {
12464 context: Point::new(9, 0)..Point::new(10, 3),
12465 primary: None,
12466 },
12467 ],
12468 cx,
12469 );
12470 multibuffer
12471 });
12472
12473 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12474 editor
12475 .update(cx, |editor, cx| {
12476 for (buffer, diff_base) in [
12477 (buffer_1.clone(), file_1_old),
12478 (buffer_2.clone(), file_2_old),
12479 (buffer_3.clone(), file_3_old),
12480 ] {
12481 let change_set = cx.new_model(|cx| {
12482 BufferChangeSet::new_with_base_text(
12483 diff_base.to_string(),
12484 buffer.read(cx).text_snapshot(),
12485 cx,
12486 )
12487 });
12488 editor.diff_map.add_change_set(change_set, cx)
12489 }
12490 })
12491 .unwrap();
12492
12493 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12494 cx.run_until_parked();
12495
12496 cx.assert_editor_state(
12497 &"
12498 ˇaaa
12499 ccc
12500 ddd
12501
12502 ggg
12503 hhh
12504
12505
12506 lll
12507 mmm
12508 NNN
12509
12510 qqq
12511 rrr
12512
12513 uuu
12514 111
12515 222
12516 333
12517
12518 666
12519 777
12520
12521 000
12522 !!!"
12523 .unindent(),
12524 );
12525
12526 cx.update_editor(|editor, cx| {
12527 editor.select_all(&SelectAll, cx);
12528 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12529 });
12530 cx.executor().run_until_parked();
12531
12532 cx.assert_state_with_diff(
12533 "
12534 «aaa
12535 - bbb
12536 ccc
12537 ddd
12538
12539 ggg
12540 hhh
12541
12542
12543 lll
12544 mmm
12545 - nnn
12546 + NNN
12547
12548 qqq
12549 rrr
12550
12551 uuu
12552 111
12553 222
12554 333
12555
12556 + 666
12557 777
12558
12559 000
12560 !!!ˇ»"
12561 .unindent(),
12562 );
12563}
12564
12565#[gpui::test]
12566async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12567 init_test(cx, |_| {});
12568
12569 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12570 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12571
12572 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12573 let multi_buffer = cx.new_model(|cx| {
12574 let mut multibuffer = MultiBuffer::new(ReadWrite);
12575 multibuffer.push_excerpts(
12576 buffer.clone(),
12577 [
12578 ExcerptRange {
12579 context: Point::new(0, 0)..Point::new(2, 0),
12580 primary: None,
12581 },
12582 ExcerptRange {
12583 context: Point::new(5, 0)..Point::new(7, 0),
12584 primary: None,
12585 },
12586 ],
12587 cx,
12588 );
12589 multibuffer
12590 });
12591
12592 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12593 editor
12594 .update(cx, |editor, cx| {
12595 let buffer = buffer.read(cx).text_snapshot();
12596 let change_set = cx
12597 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12598 editor.diff_map.add_change_set(change_set, cx)
12599 })
12600 .unwrap();
12601
12602 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12603 cx.run_until_parked();
12604
12605 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12606 cx.executor().run_until_parked();
12607
12608 cx.assert_state_with_diff(
12609 "
12610 ˇaaa
12611 - bbb
12612 + BBB
12613
12614 - ddd
12615 - eee
12616 + EEE
12617 fff
12618 "
12619 .unindent(),
12620 );
12621}
12622
12623#[gpui::test]
12624async fn test_edits_around_expanded_insertion_hunks(
12625 executor: BackgroundExecutor,
12626 cx: &mut gpui::TestAppContext,
12627) {
12628 init_test(cx, |_| {});
12629
12630 let mut cx = EditorTestContext::new(cx).await;
12631
12632 let diff_base = r#"
12633 use some::mod1;
12634 use some::mod2;
12635
12636 const A: u32 = 42;
12637
12638 fn main() {
12639 println!("hello");
12640
12641 println!("world");
12642 }
12643 "#
12644 .unindent();
12645 executor.run_until_parked();
12646 cx.set_state(
12647 &r#"
12648 use some::mod1;
12649 use some::mod2;
12650
12651 const A: u32 = 42;
12652 const B: u32 = 42;
12653 const C: u32 = 42;
12654 ˇ
12655
12656 fn main() {
12657 println!("hello");
12658
12659 println!("world");
12660 }
12661 "#
12662 .unindent(),
12663 );
12664
12665 cx.set_diff_base(&diff_base);
12666 executor.run_until_parked();
12667
12668 cx.update_editor(|editor, cx| {
12669 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12670 });
12671 executor.run_until_parked();
12672
12673 cx.assert_state_with_diff(
12674 r#"
12675 use some::mod1;
12676 use some::mod2;
12677
12678 const A: u32 = 42;
12679 + const B: u32 = 42;
12680 + const C: u32 = 42;
12681 + ˇ
12682
12683 fn main() {
12684 println!("hello");
12685
12686 println!("world");
12687 }
12688 "#
12689 .unindent(),
12690 );
12691
12692 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12693 executor.run_until_parked();
12694
12695 cx.assert_state_with_diff(
12696 r#"
12697 use some::mod1;
12698 use some::mod2;
12699
12700 const A: u32 = 42;
12701 + const B: u32 = 42;
12702 + const C: u32 = 42;
12703 + const D: u32 = 42;
12704 + ˇ
12705
12706 fn main() {
12707 println!("hello");
12708
12709 println!("world");
12710 }
12711 "#
12712 .unindent(),
12713 );
12714
12715 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12716 executor.run_until_parked();
12717
12718 cx.assert_state_with_diff(
12719 r#"
12720 use some::mod1;
12721 use some::mod2;
12722
12723 const A: u32 = 42;
12724 + const B: u32 = 42;
12725 + const C: u32 = 42;
12726 + const D: u32 = 42;
12727 + const E: u32 = 42;
12728 + ˇ
12729
12730 fn main() {
12731 println!("hello");
12732
12733 println!("world");
12734 }
12735 "#
12736 .unindent(),
12737 );
12738
12739 cx.update_editor(|editor, cx| {
12740 editor.delete_line(&DeleteLine, cx);
12741 });
12742 executor.run_until_parked();
12743
12744 cx.assert_state_with_diff(
12745 r#"
12746 use some::mod1;
12747 use some::mod2;
12748
12749 const A: u32 = 42;
12750 + const B: u32 = 42;
12751 + const C: u32 = 42;
12752 + const D: u32 = 42;
12753 + const E: u32 = 42;
12754 ˇ
12755 fn main() {
12756 println!("hello");
12757
12758 println!("world");
12759 }
12760 "#
12761 .unindent(),
12762 );
12763
12764 cx.update_editor(|editor, cx| {
12765 editor.move_up(&MoveUp, cx);
12766 editor.delete_line(&DeleteLine, cx);
12767 editor.move_up(&MoveUp, cx);
12768 editor.delete_line(&DeleteLine, cx);
12769 editor.move_up(&MoveUp, cx);
12770 editor.delete_line(&DeleteLine, cx);
12771 });
12772 executor.run_until_parked();
12773 cx.assert_state_with_diff(
12774 r#"
12775 use some::mod1;
12776 use some::mod2;
12777
12778 const A: u32 = 42;
12779 + const B: u32 = 42;
12780 ˇ
12781 fn main() {
12782 println!("hello");
12783
12784 println!("world");
12785 }
12786 "#
12787 .unindent(),
12788 );
12789
12790 cx.update_editor(|editor, cx| {
12791 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12792 editor.delete_line(&DeleteLine, cx);
12793 });
12794 executor.run_until_parked();
12795 cx.assert_state_with_diff(
12796 r#"
12797 use some::mod1;
12798 - use some::mod2;
12799 -
12800 - const A: u32 = 42;
12801 ˇ
12802 fn main() {
12803 println!("hello");
12804
12805 println!("world");
12806 }
12807 "#
12808 .unindent(),
12809 );
12810}
12811
12812#[gpui::test]
12813async fn test_edits_around_expanded_deletion_hunks(
12814 executor: BackgroundExecutor,
12815 cx: &mut gpui::TestAppContext,
12816) {
12817 init_test(cx, |_| {});
12818
12819 let mut cx = EditorTestContext::new(cx).await;
12820
12821 let diff_base = r#"
12822 use some::mod1;
12823 use some::mod2;
12824
12825 const A: u32 = 42;
12826 const B: u32 = 42;
12827 const C: u32 = 42;
12828
12829
12830 fn main() {
12831 println!("hello");
12832
12833 println!("world");
12834 }
12835 "#
12836 .unindent();
12837 executor.run_until_parked();
12838 cx.set_state(
12839 &r#"
12840 use some::mod1;
12841 use some::mod2;
12842
12843 ˇconst B: u32 = 42;
12844 const C: u32 = 42;
12845
12846
12847 fn main() {
12848 println!("hello");
12849
12850 println!("world");
12851 }
12852 "#
12853 .unindent(),
12854 );
12855
12856 cx.set_diff_base(&diff_base);
12857 executor.run_until_parked();
12858
12859 cx.update_editor(|editor, cx| {
12860 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12861 });
12862 executor.run_until_parked();
12863
12864 cx.assert_state_with_diff(
12865 r#"
12866 use some::mod1;
12867 use some::mod2;
12868
12869 - const A: u32 = 42;
12870 ˇconst B: u32 = 42;
12871 const C: u32 = 42;
12872
12873
12874 fn main() {
12875 println!("hello");
12876
12877 println!("world");
12878 }
12879 "#
12880 .unindent(),
12881 );
12882
12883 cx.update_editor(|editor, cx| {
12884 editor.delete_line(&DeleteLine, cx);
12885 });
12886 executor.run_until_parked();
12887 cx.assert_state_with_diff(
12888 r#"
12889 use some::mod1;
12890 use some::mod2;
12891
12892 - const A: u32 = 42;
12893 - const B: u32 = 42;
12894 ˇconst C: u32 = 42;
12895
12896
12897 fn main() {
12898 println!("hello");
12899
12900 println!("world");
12901 }
12902 "#
12903 .unindent(),
12904 );
12905
12906 cx.update_editor(|editor, cx| {
12907 editor.delete_line(&DeleteLine, cx);
12908 });
12909 executor.run_until_parked();
12910 cx.assert_state_with_diff(
12911 r#"
12912 use some::mod1;
12913 use some::mod2;
12914
12915 - const A: u32 = 42;
12916 - const B: u32 = 42;
12917 - const C: u32 = 42;
12918 ˇ
12919
12920 fn main() {
12921 println!("hello");
12922
12923 println!("world");
12924 }
12925 "#
12926 .unindent(),
12927 );
12928
12929 cx.update_editor(|editor, cx| {
12930 editor.handle_input("replacement", cx);
12931 });
12932 executor.run_until_parked();
12933 cx.assert_state_with_diff(
12934 r#"
12935 use some::mod1;
12936 use some::mod2;
12937
12938 - const A: u32 = 42;
12939 - const B: u32 = 42;
12940 - const C: u32 = 42;
12941 -
12942 + replacementˇ
12943
12944 fn main() {
12945 println!("hello");
12946
12947 println!("world");
12948 }
12949 "#
12950 .unindent(),
12951 );
12952}
12953
12954#[gpui::test]
12955async fn test_edit_after_expanded_modification_hunk(
12956 executor: BackgroundExecutor,
12957 cx: &mut gpui::TestAppContext,
12958) {
12959 init_test(cx, |_| {});
12960
12961 let mut cx = EditorTestContext::new(cx).await;
12962
12963 let diff_base = r#"
12964 use some::mod1;
12965 use some::mod2;
12966
12967 const A: u32 = 42;
12968 const B: u32 = 42;
12969 const C: u32 = 42;
12970 const D: u32 = 42;
12971
12972
12973 fn main() {
12974 println!("hello");
12975
12976 println!("world");
12977 }"#
12978 .unindent();
12979
12980 cx.set_state(
12981 &r#"
12982 use some::mod1;
12983 use some::mod2;
12984
12985 const A: u32 = 42;
12986 const B: u32 = 42;
12987 const C: u32 = 43ˇ
12988 const D: u32 = 42;
12989
12990
12991 fn main() {
12992 println!("hello");
12993
12994 println!("world");
12995 }"#
12996 .unindent(),
12997 );
12998
12999 cx.set_diff_base(&diff_base);
13000 executor.run_until_parked();
13001 cx.update_editor(|editor, cx| {
13002 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13003 });
13004 executor.run_until_parked();
13005
13006 cx.assert_state_with_diff(
13007 r#"
13008 use some::mod1;
13009 use some::mod2;
13010
13011 const A: u32 = 42;
13012 const B: u32 = 42;
13013 - const C: u32 = 42;
13014 + const C: u32 = 43ˇ
13015 const D: u32 = 42;
13016
13017
13018 fn main() {
13019 println!("hello");
13020
13021 println!("world");
13022 }"#
13023 .unindent(),
13024 );
13025
13026 cx.update_editor(|editor, cx| {
13027 editor.handle_input("\nnew_line\n", cx);
13028 });
13029 executor.run_until_parked();
13030
13031 cx.assert_state_with_diff(
13032 r#"
13033 use some::mod1;
13034 use some::mod2;
13035
13036 const A: u32 = 42;
13037 const B: u32 = 42;
13038 - const C: u32 = 42;
13039 + const C: u32 = 43
13040 + new_line
13041 + ˇ
13042 const D: u32 = 42;
13043
13044
13045 fn main() {
13046 println!("hello");
13047
13048 println!("world");
13049 }"#
13050 .unindent(),
13051 );
13052}
13053
13054async fn setup_indent_guides_editor(
13055 text: &str,
13056 cx: &mut gpui::TestAppContext,
13057) -> (BufferId, EditorTestContext) {
13058 init_test(cx, |_| {});
13059
13060 let mut cx = EditorTestContext::new(cx).await;
13061
13062 let buffer_id = cx.update_editor(|editor, cx| {
13063 editor.set_text(text, cx);
13064 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13065
13066 buffer_ids[0]
13067 });
13068
13069 (buffer_id, cx)
13070}
13071
13072fn assert_indent_guides(
13073 range: Range<u32>,
13074 expected: Vec<IndentGuide>,
13075 active_indices: Option<Vec<usize>>,
13076 cx: &mut EditorTestContext,
13077) {
13078 let indent_guides = cx.update_editor(|editor, cx| {
13079 let snapshot = editor.snapshot(cx).display_snapshot;
13080 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13081 editor,
13082 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13083 true,
13084 &snapshot,
13085 cx,
13086 );
13087
13088 indent_guides.sort_by(|a, b| {
13089 a.depth.cmp(&b.depth).then(
13090 a.start_row
13091 .cmp(&b.start_row)
13092 .then(a.end_row.cmp(&b.end_row)),
13093 )
13094 });
13095 indent_guides
13096 });
13097
13098 if let Some(expected) = active_indices {
13099 let active_indices = cx.update_editor(|editor, cx| {
13100 let snapshot = editor.snapshot(cx).display_snapshot;
13101 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13102 });
13103
13104 assert_eq!(
13105 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13106 expected,
13107 "Active indent guide indices do not match"
13108 );
13109 }
13110
13111 let expected: Vec<_> = expected
13112 .into_iter()
13113 .map(|guide| MultiBufferIndentGuide {
13114 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13115 buffer: guide,
13116 })
13117 .collect();
13118
13119 assert_eq!(indent_guides, expected, "Indent guides do not match");
13120}
13121
13122fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13123 IndentGuide {
13124 buffer_id,
13125 start_row,
13126 end_row,
13127 depth,
13128 tab_size: 4,
13129 settings: IndentGuideSettings {
13130 enabled: true,
13131 line_width: 1,
13132 active_line_width: 1,
13133 ..Default::default()
13134 },
13135 }
13136}
13137
13138#[gpui::test]
13139async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13140 let (buffer_id, mut cx) = setup_indent_guides_editor(
13141 &"
13142 fn main() {
13143 let a = 1;
13144 }"
13145 .unindent(),
13146 cx,
13147 )
13148 .await;
13149
13150 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13151}
13152
13153#[gpui::test]
13154async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13155 let (buffer_id, mut cx) = setup_indent_guides_editor(
13156 &"
13157 fn main() {
13158 let a = 1;
13159 let b = 2;
13160 }"
13161 .unindent(),
13162 cx,
13163 )
13164 .await;
13165
13166 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13167}
13168
13169#[gpui::test]
13170async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13171 let (buffer_id, mut cx) = setup_indent_guides_editor(
13172 &"
13173 fn main() {
13174 let a = 1;
13175 if a == 3 {
13176 let b = 2;
13177 } else {
13178 let c = 3;
13179 }
13180 }"
13181 .unindent(),
13182 cx,
13183 )
13184 .await;
13185
13186 assert_indent_guides(
13187 0..8,
13188 vec![
13189 indent_guide(buffer_id, 1, 6, 0),
13190 indent_guide(buffer_id, 3, 3, 1),
13191 indent_guide(buffer_id, 5, 5, 1),
13192 ],
13193 None,
13194 &mut cx,
13195 );
13196}
13197
13198#[gpui::test]
13199async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13200 let (buffer_id, mut cx) = setup_indent_guides_editor(
13201 &"
13202 fn main() {
13203 let a = 1;
13204 let b = 2;
13205 let c = 3;
13206 }"
13207 .unindent(),
13208 cx,
13209 )
13210 .await;
13211
13212 assert_indent_guides(
13213 0..5,
13214 vec![
13215 indent_guide(buffer_id, 1, 3, 0),
13216 indent_guide(buffer_id, 2, 2, 1),
13217 ],
13218 None,
13219 &mut cx,
13220 );
13221}
13222
13223#[gpui::test]
13224async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13225 let (buffer_id, mut cx) = setup_indent_guides_editor(
13226 &"
13227 fn main() {
13228 let a = 1;
13229
13230 let c = 3;
13231 }"
13232 .unindent(),
13233 cx,
13234 )
13235 .await;
13236
13237 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13238}
13239
13240#[gpui::test]
13241async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13242 let (buffer_id, mut cx) = setup_indent_guides_editor(
13243 &"
13244 fn main() {
13245 let a = 1;
13246
13247 let c = 3;
13248
13249 if a == 3 {
13250 let b = 2;
13251 } else {
13252 let c = 3;
13253 }
13254 }"
13255 .unindent(),
13256 cx,
13257 )
13258 .await;
13259
13260 assert_indent_guides(
13261 0..11,
13262 vec![
13263 indent_guide(buffer_id, 1, 9, 0),
13264 indent_guide(buffer_id, 6, 6, 1),
13265 indent_guide(buffer_id, 8, 8, 1),
13266 ],
13267 None,
13268 &mut cx,
13269 );
13270}
13271
13272#[gpui::test]
13273async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13274 let (buffer_id, mut cx) = setup_indent_guides_editor(
13275 &"
13276 fn main() {
13277 let a = 1;
13278
13279 let c = 3;
13280
13281 if a == 3 {
13282 let b = 2;
13283 } else {
13284 let c = 3;
13285 }
13286 }"
13287 .unindent(),
13288 cx,
13289 )
13290 .await;
13291
13292 assert_indent_guides(
13293 1..11,
13294 vec![
13295 indent_guide(buffer_id, 1, 9, 0),
13296 indent_guide(buffer_id, 6, 6, 1),
13297 indent_guide(buffer_id, 8, 8, 1),
13298 ],
13299 None,
13300 &mut cx,
13301 );
13302}
13303
13304#[gpui::test]
13305async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13306 let (buffer_id, mut cx) = setup_indent_guides_editor(
13307 &"
13308 fn main() {
13309 let a = 1;
13310
13311 let c = 3;
13312
13313 if a == 3 {
13314 let b = 2;
13315 } else {
13316 let c = 3;
13317 }
13318 }"
13319 .unindent(),
13320 cx,
13321 )
13322 .await;
13323
13324 assert_indent_guides(
13325 1..10,
13326 vec![
13327 indent_guide(buffer_id, 1, 9, 0),
13328 indent_guide(buffer_id, 6, 6, 1),
13329 indent_guide(buffer_id, 8, 8, 1),
13330 ],
13331 None,
13332 &mut cx,
13333 );
13334}
13335
13336#[gpui::test]
13337async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13338 let (buffer_id, mut cx) = setup_indent_guides_editor(
13339 &"
13340 block1
13341 block2
13342 block3
13343 block4
13344 block2
13345 block1
13346 block1"
13347 .unindent(),
13348 cx,
13349 )
13350 .await;
13351
13352 assert_indent_guides(
13353 1..10,
13354 vec![
13355 indent_guide(buffer_id, 1, 4, 0),
13356 indent_guide(buffer_id, 2, 3, 1),
13357 indent_guide(buffer_id, 3, 3, 2),
13358 ],
13359 None,
13360 &mut cx,
13361 );
13362}
13363
13364#[gpui::test]
13365async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13366 let (buffer_id, mut cx) = setup_indent_guides_editor(
13367 &"
13368 block1
13369 block2
13370 block3
13371
13372 block1
13373 block1"
13374 .unindent(),
13375 cx,
13376 )
13377 .await;
13378
13379 assert_indent_guides(
13380 0..6,
13381 vec![
13382 indent_guide(buffer_id, 1, 2, 0),
13383 indent_guide(buffer_id, 2, 2, 1),
13384 ],
13385 None,
13386 &mut cx,
13387 );
13388}
13389
13390#[gpui::test]
13391async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13392 let (buffer_id, mut cx) = setup_indent_guides_editor(
13393 &"
13394 block1
13395
13396
13397
13398 block2
13399 "
13400 .unindent(),
13401 cx,
13402 )
13403 .await;
13404
13405 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13406}
13407
13408#[gpui::test]
13409async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13410 let (buffer_id, mut cx) = setup_indent_guides_editor(
13411 &"
13412 def a:
13413 \tb = 3
13414 \tif True:
13415 \t\tc = 4
13416 \t\td = 5
13417 \tprint(b)
13418 "
13419 .unindent(),
13420 cx,
13421 )
13422 .await;
13423
13424 assert_indent_guides(
13425 0..6,
13426 vec![
13427 indent_guide(buffer_id, 1, 6, 0),
13428 indent_guide(buffer_id, 3, 4, 1),
13429 ],
13430 None,
13431 &mut cx,
13432 );
13433}
13434
13435#[gpui::test]
13436async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13437 let (buffer_id, mut cx) = setup_indent_guides_editor(
13438 &"
13439 fn main() {
13440 let a = 1;
13441 }"
13442 .unindent(),
13443 cx,
13444 )
13445 .await;
13446
13447 cx.update_editor(|editor, cx| {
13448 editor.change_selections(None, cx, |s| {
13449 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13450 });
13451 });
13452
13453 assert_indent_guides(
13454 0..3,
13455 vec![indent_guide(buffer_id, 1, 1, 0)],
13456 Some(vec![0]),
13457 &mut cx,
13458 );
13459}
13460
13461#[gpui::test]
13462async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13463 let (buffer_id, mut cx) = setup_indent_guides_editor(
13464 &"
13465 fn main() {
13466 if 1 == 2 {
13467 let a = 1;
13468 }
13469 }"
13470 .unindent(),
13471 cx,
13472 )
13473 .await;
13474
13475 cx.update_editor(|editor, cx| {
13476 editor.change_selections(None, cx, |s| {
13477 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13478 });
13479 });
13480
13481 assert_indent_guides(
13482 0..4,
13483 vec![
13484 indent_guide(buffer_id, 1, 3, 0),
13485 indent_guide(buffer_id, 2, 2, 1),
13486 ],
13487 Some(vec![1]),
13488 &mut cx,
13489 );
13490
13491 cx.update_editor(|editor, cx| {
13492 editor.change_selections(None, cx, |s| {
13493 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13494 });
13495 });
13496
13497 assert_indent_guides(
13498 0..4,
13499 vec![
13500 indent_guide(buffer_id, 1, 3, 0),
13501 indent_guide(buffer_id, 2, 2, 1),
13502 ],
13503 Some(vec![1]),
13504 &mut cx,
13505 );
13506
13507 cx.update_editor(|editor, cx| {
13508 editor.change_selections(None, cx, |s| {
13509 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13510 });
13511 });
13512
13513 assert_indent_guides(
13514 0..4,
13515 vec![
13516 indent_guide(buffer_id, 1, 3, 0),
13517 indent_guide(buffer_id, 2, 2, 1),
13518 ],
13519 Some(vec![0]),
13520 &mut cx,
13521 );
13522}
13523
13524#[gpui::test]
13525async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13526 let (buffer_id, mut cx) = setup_indent_guides_editor(
13527 &"
13528 fn main() {
13529 let a = 1;
13530
13531 let b = 2;
13532 }"
13533 .unindent(),
13534 cx,
13535 )
13536 .await;
13537
13538 cx.update_editor(|editor, cx| {
13539 editor.change_selections(None, cx, |s| {
13540 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13541 });
13542 });
13543
13544 assert_indent_guides(
13545 0..5,
13546 vec![indent_guide(buffer_id, 1, 3, 0)],
13547 Some(vec![0]),
13548 &mut cx,
13549 );
13550}
13551
13552#[gpui::test]
13553async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13554 let (buffer_id, mut cx) = setup_indent_guides_editor(
13555 &"
13556 def m:
13557 a = 1
13558 pass"
13559 .unindent(),
13560 cx,
13561 )
13562 .await;
13563
13564 cx.update_editor(|editor, cx| {
13565 editor.change_selections(None, cx, |s| {
13566 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13567 });
13568 });
13569
13570 assert_indent_guides(
13571 0..3,
13572 vec![indent_guide(buffer_id, 1, 2, 0)],
13573 Some(vec![0]),
13574 &mut cx,
13575 );
13576}
13577
13578#[gpui::test]
13579fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13580 init_test(cx, |_| {});
13581
13582 let editor = cx.add_window(|cx| {
13583 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13584 build_editor(buffer, cx)
13585 });
13586
13587 let render_args = Arc::new(Mutex::new(None));
13588 let snapshot = editor
13589 .update(cx, |editor, cx| {
13590 let snapshot = editor.buffer().read(cx).snapshot(cx);
13591 let range =
13592 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13593
13594 struct RenderArgs {
13595 row: MultiBufferRow,
13596 folded: bool,
13597 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13598 }
13599
13600 let crease = Crease::inline(
13601 range,
13602 FoldPlaceholder::test(),
13603 {
13604 let toggle_callback = render_args.clone();
13605 move |row, folded, callback, _cx| {
13606 *toggle_callback.lock() = Some(RenderArgs {
13607 row,
13608 folded,
13609 callback,
13610 });
13611 div()
13612 }
13613 },
13614 |_row, _folded, _cx| div(),
13615 );
13616
13617 editor.insert_creases(Some(crease), cx);
13618 let snapshot = editor.snapshot(cx);
13619 let _div =
13620 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13621 snapshot
13622 })
13623 .unwrap();
13624
13625 let render_args = render_args.lock().take().unwrap();
13626 assert_eq!(render_args.row, MultiBufferRow(1));
13627 assert!(!render_args.folded);
13628 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13629
13630 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13631 .unwrap();
13632 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13633 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13634
13635 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13636 .unwrap();
13637 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13638 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13639}
13640
13641#[gpui::test]
13642async fn test_input_text(cx: &mut gpui::TestAppContext) {
13643 init_test(cx, |_| {});
13644 let mut cx = EditorTestContext::new(cx).await;
13645
13646 cx.set_state(
13647 &r#"ˇone
13648 two
13649
13650 three
13651 fourˇ
13652 five
13653
13654 siˇx"#
13655 .unindent(),
13656 );
13657
13658 cx.dispatch_action(HandleInput(String::new()));
13659 cx.assert_editor_state(
13660 &r#"ˇone
13661 two
13662
13663 three
13664 fourˇ
13665 five
13666
13667 siˇx"#
13668 .unindent(),
13669 );
13670
13671 cx.dispatch_action(HandleInput("AAAA".to_string()));
13672 cx.assert_editor_state(
13673 &r#"AAAAˇone
13674 two
13675
13676 three
13677 fourAAAAˇ
13678 five
13679
13680 siAAAAˇx"#
13681 .unindent(),
13682 );
13683}
13684
13685#[gpui::test]
13686async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13687 init_test(cx, |_| {});
13688
13689 let mut cx = EditorTestContext::new(cx).await;
13690 cx.set_state(
13691 r#"let foo = 1;
13692let foo = 2;
13693let foo = 3;
13694let fooˇ = 4;
13695let foo = 5;
13696let foo = 6;
13697let foo = 7;
13698let foo = 8;
13699let foo = 9;
13700let foo = 10;
13701let foo = 11;
13702let foo = 12;
13703let foo = 13;
13704let foo = 14;
13705let foo = 15;"#,
13706 );
13707
13708 cx.update_editor(|e, cx| {
13709 assert_eq!(
13710 e.next_scroll_position,
13711 NextScrollCursorCenterTopBottom::Center,
13712 "Default next scroll direction is center",
13713 );
13714
13715 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13716 assert_eq!(
13717 e.next_scroll_position,
13718 NextScrollCursorCenterTopBottom::Top,
13719 "After center, next scroll direction should be top",
13720 );
13721
13722 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13723 assert_eq!(
13724 e.next_scroll_position,
13725 NextScrollCursorCenterTopBottom::Bottom,
13726 "After top, next scroll direction should be bottom",
13727 );
13728
13729 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13730 assert_eq!(
13731 e.next_scroll_position,
13732 NextScrollCursorCenterTopBottom::Center,
13733 "After bottom, scrolling should start over",
13734 );
13735
13736 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13737 assert_eq!(
13738 e.next_scroll_position,
13739 NextScrollCursorCenterTopBottom::Top,
13740 "Scrolling continues if retriggered fast enough"
13741 );
13742 });
13743
13744 cx.executor()
13745 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13746 cx.executor().run_until_parked();
13747 cx.update_editor(|e, _| {
13748 assert_eq!(
13749 e.next_scroll_position,
13750 NextScrollCursorCenterTopBottom::Center,
13751 "If scrolling is not triggered fast enough, it should reset"
13752 );
13753 });
13754}
13755
13756#[gpui::test]
13757async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13758 init_test(cx, |_| {});
13759 let mut cx = EditorLspTestContext::new_rust(
13760 lsp::ServerCapabilities {
13761 definition_provider: Some(lsp::OneOf::Left(true)),
13762 references_provider: Some(lsp::OneOf::Left(true)),
13763 ..lsp::ServerCapabilities::default()
13764 },
13765 cx,
13766 )
13767 .await;
13768
13769 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13770 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13771 move |params, _| async move {
13772 if empty_go_to_definition {
13773 Ok(None)
13774 } else {
13775 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13776 uri: params.text_document_position_params.text_document.uri,
13777 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13778 })))
13779 }
13780 },
13781 );
13782 let references =
13783 cx.lsp
13784 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13785 Ok(Some(vec![lsp::Location {
13786 uri: params.text_document_position.text_document.uri,
13787 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13788 }]))
13789 });
13790 (go_to_definition, references)
13791 };
13792
13793 cx.set_state(
13794 &r#"fn one() {
13795 let mut a = ˇtwo();
13796 }
13797
13798 fn two() {}"#
13799 .unindent(),
13800 );
13801 set_up_lsp_handlers(false, &mut cx);
13802 let navigated = cx
13803 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13804 .await
13805 .expect("Failed to navigate to definition");
13806 assert_eq!(
13807 navigated,
13808 Navigated::Yes,
13809 "Should have navigated to definition from the GetDefinition response"
13810 );
13811 cx.assert_editor_state(
13812 &r#"fn one() {
13813 let mut a = two();
13814 }
13815
13816 fn «twoˇ»() {}"#
13817 .unindent(),
13818 );
13819
13820 let editors = cx.update_workspace(|workspace, cx| {
13821 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13822 });
13823 cx.update_editor(|_, test_editor_cx| {
13824 assert_eq!(
13825 editors.len(),
13826 1,
13827 "Initially, only one, test, editor should be open in the workspace"
13828 );
13829 assert_eq!(
13830 test_editor_cx.view(),
13831 editors.last().expect("Asserted len is 1")
13832 );
13833 });
13834
13835 set_up_lsp_handlers(true, &mut cx);
13836 let navigated = cx
13837 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13838 .await
13839 .expect("Failed to navigate to lookup references");
13840 assert_eq!(
13841 navigated,
13842 Navigated::Yes,
13843 "Should have navigated to references as a fallback after empty GoToDefinition response"
13844 );
13845 // We should not change the selections in the existing file,
13846 // if opening another milti buffer with the references
13847 cx.assert_editor_state(
13848 &r#"fn one() {
13849 let mut a = two();
13850 }
13851
13852 fn «twoˇ»() {}"#
13853 .unindent(),
13854 );
13855 let editors = cx.update_workspace(|workspace, cx| {
13856 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13857 });
13858 cx.update_editor(|_, test_editor_cx| {
13859 assert_eq!(
13860 editors.len(),
13861 2,
13862 "After falling back to references search, we open a new editor with the results"
13863 );
13864 let references_fallback_text = editors
13865 .into_iter()
13866 .find(|new_editor| new_editor != test_editor_cx.view())
13867 .expect("Should have one non-test editor now")
13868 .read(test_editor_cx)
13869 .text(test_editor_cx);
13870 assert_eq!(
13871 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13872 "Should use the range from the references response and not the GoToDefinition one"
13873 );
13874 });
13875}
13876
13877#[gpui::test]
13878async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13879 init_test(cx, |_| {});
13880
13881 let language = Arc::new(Language::new(
13882 LanguageConfig::default(),
13883 Some(tree_sitter_rust::LANGUAGE.into()),
13884 ));
13885
13886 let text = r#"
13887 #[cfg(test)]
13888 mod tests() {
13889 #[test]
13890 fn runnable_1() {
13891 let a = 1;
13892 }
13893
13894 #[test]
13895 fn runnable_2() {
13896 let a = 1;
13897 let b = 2;
13898 }
13899 }
13900 "#
13901 .unindent();
13902
13903 let fs = FakeFs::new(cx.executor());
13904 fs.insert_file("/file.rs", Default::default()).await;
13905
13906 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13907 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13908 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13909 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13910 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13911
13912 let editor = cx.new_view(|cx| {
13913 Editor::new(
13914 EditorMode::Full,
13915 multi_buffer,
13916 Some(project.clone()),
13917 true,
13918 cx,
13919 )
13920 });
13921
13922 editor.update(cx, |editor, cx| {
13923 editor.tasks.insert(
13924 (buffer.read(cx).remote_id(), 3),
13925 RunnableTasks {
13926 templates: vec![],
13927 offset: MultiBufferOffset(43),
13928 column: 0,
13929 extra_variables: HashMap::default(),
13930 context_range: BufferOffset(43)..BufferOffset(85),
13931 },
13932 );
13933 editor.tasks.insert(
13934 (buffer.read(cx).remote_id(), 8),
13935 RunnableTasks {
13936 templates: vec![],
13937 offset: MultiBufferOffset(86),
13938 column: 0,
13939 extra_variables: HashMap::default(),
13940 context_range: BufferOffset(86)..BufferOffset(191),
13941 },
13942 );
13943
13944 // Test finding task when cursor is inside function body
13945 editor.change_selections(None, cx, |s| {
13946 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13947 });
13948 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13949 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13950
13951 // Test finding task when cursor is on function name
13952 editor.change_selections(None, cx, |s| {
13953 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13954 });
13955 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13956 assert_eq!(row, 8, "Should find task when cursor is on function name");
13957 });
13958}
13959
13960#[gpui::test]
13961async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
13962 init_test(cx, |_| {});
13963
13964 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
13965 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
13966 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
13967
13968 let fs = FakeFs::new(cx.executor());
13969 fs.insert_tree(
13970 "/a",
13971 json!({
13972 "first.rs": sample_text_1,
13973 "second.rs": sample_text_2,
13974 "third.rs": sample_text_3,
13975 }),
13976 )
13977 .await;
13978 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13979 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13980 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13981 let worktree = project.update(cx, |project, cx| {
13982 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
13983 assert_eq!(worktrees.len(), 1);
13984 worktrees.pop().unwrap()
13985 });
13986 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13987
13988 let buffer_1 = project
13989 .update(cx, |project, cx| {
13990 project.open_buffer((worktree_id, "first.rs"), cx)
13991 })
13992 .await
13993 .unwrap();
13994 let buffer_2 = project
13995 .update(cx, |project, cx| {
13996 project.open_buffer((worktree_id, "second.rs"), cx)
13997 })
13998 .await
13999 .unwrap();
14000 let buffer_3 = project
14001 .update(cx, |project, cx| {
14002 project.open_buffer((worktree_id, "third.rs"), cx)
14003 })
14004 .await
14005 .unwrap();
14006
14007 let multi_buffer = cx.new_model(|cx| {
14008 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14009 multi_buffer.push_excerpts(
14010 buffer_1.clone(),
14011 [
14012 ExcerptRange {
14013 context: Point::new(0, 0)..Point::new(3, 0),
14014 primary: None,
14015 },
14016 ExcerptRange {
14017 context: Point::new(5, 0)..Point::new(7, 0),
14018 primary: None,
14019 },
14020 ExcerptRange {
14021 context: Point::new(9, 0)..Point::new(10, 4),
14022 primary: None,
14023 },
14024 ],
14025 cx,
14026 );
14027 multi_buffer.push_excerpts(
14028 buffer_2.clone(),
14029 [
14030 ExcerptRange {
14031 context: Point::new(0, 0)..Point::new(3, 0),
14032 primary: None,
14033 },
14034 ExcerptRange {
14035 context: Point::new(5, 0)..Point::new(7, 0),
14036 primary: None,
14037 },
14038 ExcerptRange {
14039 context: Point::new(9, 0)..Point::new(10, 4),
14040 primary: None,
14041 },
14042 ],
14043 cx,
14044 );
14045 multi_buffer.push_excerpts(
14046 buffer_3.clone(),
14047 [
14048 ExcerptRange {
14049 context: Point::new(0, 0)..Point::new(3, 0),
14050 primary: None,
14051 },
14052 ExcerptRange {
14053 context: Point::new(5, 0)..Point::new(7, 0),
14054 primary: None,
14055 },
14056 ExcerptRange {
14057 context: Point::new(9, 0)..Point::new(10, 4),
14058 primary: None,
14059 },
14060 ],
14061 cx,
14062 );
14063 multi_buffer
14064 });
14065 let multi_buffer_editor = cx.new_view(|cx| {
14066 Editor::new(
14067 EditorMode::Full,
14068 multi_buffer,
14069 Some(project.clone()),
14070 true,
14071 cx,
14072 )
14073 });
14074
14075 let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
14076 assert_eq!(
14077 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14078 full_text,
14079 );
14080
14081 multi_buffer_editor.update(cx, |editor, cx| {
14082 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14083 });
14084 assert_eq!(
14085 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14086 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14087 "After folding the first buffer, its text should not be displayed"
14088 );
14089
14090 multi_buffer_editor.update(cx, |editor, cx| {
14091 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14092 });
14093 assert_eq!(
14094 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14095 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14096 "After folding the second buffer, its text should not be displayed"
14097 );
14098
14099 multi_buffer_editor.update(cx, |editor, cx| {
14100 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14101 });
14102 assert_eq!(
14103 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14104 "\n\n\n\n\n",
14105 "After folding the third buffer, its text should not be displayed"
14106 );
14107
14108 // Emulate selection inside the fold logic, that should work
14109 multi_buffer_editor.update(cx, |editor, cx| {
14110 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14111 });
14112
14113 multi_buffer_editor.update(cx, |editor, cx| {
14114 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14115 });
14116 assert_eq!(
14117 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14118 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14119 "After unfolding the second buffer, its text should be displayed"
14120 );
14121
14122 multi_buffer_editor.update(cx, |editor, cx| {
14123 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14124 });
14125 assert_eq!(
14126 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14127 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14128 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14129 );
14130
14131 multi_buffer_editor.update(cx, |editor, cx| {
14132 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14133 });
14134 assert_eq!(
14135 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14136 full_text,
14137 "After unfolding the all buffers, all original text should be displayed"
14138 );
14139}
14140
14141#[gpui::test]
14142async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14143 init_test(cx, |_| {});
14144
14145 let sample_text_1 = "1111\n2222\n3333".to_string();
14146 let sample_text_2 = "4444\n5555\n6666".to_string();
14147 let sample_text_3 = "7777\n8888\n9999".to_string();
14148
14149 let fs = FakeFs::new(cx.executor());
14150 fs.insert_tree(
14151 "/a",
14152 json!({
14153 "first.rs": sample_text_1,
14154 "second.rs": sample_text_2,
14155 "third.rs": sample_text_3,
14156 }),
14157 )
14158 .await;
14159 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14160 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14161 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14162 let worktree = project.update(cx, |project, cx| {
14163 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14164 assert_eq!(worktrees.len(), 1);
14165 worktrees.pop().unwrap()
14166 });
14167 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14168
14169 let buffer_1 = project
14170 .update(cx, |project, cx| {
14171 project.open_buffer((worktree_id, "first.rs"), cx)
14172 })
14173 .await
14174 .unwrap();
14175 let buffer_2 = project
14176 .update(cx, |project, cx| {
14177 project.open_buffer((worktree_id, "second.rs"), cx)
14178 })
14179 .await
14180 .unwrap();
14181 let buffer_3 = project
14182 .update(cx, |project, cx| {
14183 project.open_buffer((worktree_id, "third.rs"), cx)
14184 })
14185 .await
14186 .unwrap();
14187
14188 let multi_buffer = cx.new_model(|cx| {
14189 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14190 multi_buffer.push_excerpts(
14191 buffer_1.clone(),
14192 [ExcerptRange {
14193 context: Point::new(0, 0)..Point::new(3, 0),
14194 primary: None,
14195 }],
14196 cx,
14197 );
14198 multi_buffer.push_excerpts(
14199 buffer_2.clone(),
14200 [ExcerptRange {
14201 context: Point::new(0, 0)..Point::new(3, 0),
14202 primary: None,
14203 }],
14204 cx,
14205 );
14206 multi_buffer.push_excerpts(
14207 buffer_3.clone(),
14208 [ExcerptRange {
14209 context: Point::new(0, 0)..Point::new(3, 0),
14210 primary: None,
14211 }],
14212 cx,
14213 );
14214 multi_buffer
14215 });
14216
14217 let multi_buffer_editor = cx.new_view(|cx| {
14218 Editor::new(
14219 EditorMode::Full,
14220 multi_buffer,
14221 Some(project.clone()),
14222 true,
14223 cx,
14224 )
14225 });
14226
14227 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14228 assert_eq!(
14229 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14230 full_text,
14231 );
14232
14233 multi_buffer_editor.update(cx, |editor, cx| {
14234 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14235 });
14236 assert_eq!(
14237 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14238 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14239 "After folding the first buffer, its text should not be displayed"
14240 );
14241
14242 multi_buffer_editor.update(cx, |editor, cx| {
14243 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14244 });
14245
14246 assert_eq!(
14247 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14248 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14249 "After folding the second buffer, its text should not be displayed"
14250 );
14251
14252 multi_buffer_editor.update(cx, |editor, cx| {
14253 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14254 });
14255 assert_eq!(
14256 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14257 "\n\n\n\n\n",
14258 "After folding the third buffer, its text should not be displayed"
14259 );
14260
14261 multi_buffer_editor.update(cx, |editor, cx| {
14262 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14263 });
14264 assert_eq!(
14265 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14266 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14267 "After unfolding the second buffer, its text should be displayed"
14268 );
14269
14270 multi_buffer_editor.update(cx, |editor, cx| {
14271 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14272 });
14273 assert_eq!(
14274 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14275 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14276 "After unfolding the first buffer, its text should be displayed"
14277 );
14278
14279 multi_buffer_editor.update(cx, |editor, cx| {
14280 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14281 });
14282 assert_eq!(
14283 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14284 full_text,
14285 "After unfolding all buffers, all original text should be displayed"
14286 );
14287}
14288
14289#[gpui::test]
14290async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14291 init_test(cx, |_| {});
14292
14293 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14294
14295 let fs = FakeFs::new(cx.executor());
14296 fs.insert_tree(
14297 "/a",
14298 json!({
14299 "main.rs": sample_text,
14300 }),
14301 )
14302 .await;
14303 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14304 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14305 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14306 let worktree = project.update(cx, |project, cx| {
14307 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14308 assert_eq!(worktrees.len(), 1);
14309 worktrees.pop().unwrap()
14310 });
14311 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14312
14313 let buffer_1 = project
14314 .update(cx, |project, cx| {
14315 project.open_buffer((worktree_id, "main.rs"), cx)
14316 })
14317 .await
14318 .unwrap();
14319
14320 let multi_buffer = cx.new_model(|cx| {
14321 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14322 multi_buffer.push_excerpts(
14323 buffer_1.clone(),
14324 [ExcerptRange {
14325 context: Point::new(0, 0)
14326 ..Point::new(
14327 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14328 0,
14329 ),
14330 primary: None,
14331 }],
14332 cx,
14333 );
14334 multi_buffer
14335 });
14336 let multi_buffer_editor = cx.new_view(|cx| {
14337 Editor::new(
14338 EditorMode::Full,
14339 multi_buffer,
14340 Some(project.clone()),
14341 true,
14342 cx,
14343 )
14344 });
14345
14346 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14347 multi_buffer_editor.update(cx, |editor, cx| {
14348 enum TestHighlight {}
14349 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14350 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14351 editor.highlight_text::<TestHighlight>(
14352 vec![highlight_range.clone()],
14353 HighlightStyle::color(Hsla::green()),
14354 cx,
14355 );
14356 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14357 });
14358
14359 let full_text = format!("\n\n\n{sample_text}\n");
14360 assert_eq!(
14361 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14362 full_text,
14363 );
14364}
14365
14366#[gpui::test]
14367fn test_inline_completion_text(cx: &mut TestAppContext) {
14368 init_test(cx, |_| {});
14369
14370 // Simple insertion
14371 {
14372 let window = cx.add_window(|cx| {
14373 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14374 Editor::new(EditorMode::Full, buffer, None, true, cx)
14375 });
14376 let cx = &mut VisualTestContext::from_window(*window, cx);
14377
14378 window
14379 .update(cx, |editor, cx| {
14380 let snapshot = editor.snapshot(cx);
14381 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14382 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14383 let edits = vec![(edit_range, " beautiful".to_string())];
14384
14385 let InlineCompletionText::Edit { text, highlights } =
14386 inline_completion_edit_text(&snapshot, &edits, false, cx)
14387 else {
14388 panic!("Failed to generate inline completion text");
14389 };
14390
14391 assert_eq!(text, "Hello, beautiful world!");
14392 assert_eq!(highlights.len(), 1);
14393 assert_eq!(highlights[0].0, 6..16);
14394 assert_eq!(
14395 highlights[0].1.background_color,
14396 Some(cx.theme().status().created_background)
14397 );
14398 })
14399 .unwrap();
14400 }
14401
14402 // Replacement
14403 {
14404 let window = cx.add_window(|cx| {
14405 let buffer = MultiBuffer::build_simple("This is a test.", cx);
14406 Editor::new(EditorMode::Full, buffer, None, true, cx)
14407 });
14408 let cx = &mut VisualTestContext::from_window(*window, cx);
14409
14410 window
14411 .update(cx, |editor, cx| {
14412 let snapshot = editor.snapshot(cx);
14413 let edits = vec![(
14414 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14415 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
14416 "That".to_string(),
14417 )];
14418
14419 let InlineCompletionText::Edit { text, highlights } =
14420 inline_completion_edit_text(&snapshot, &edits, false, cx)
14421 else {
14422 panic!("Failed to generate inline completion text");
14423 };
14424
14425 assert_eq!(text, "That is a test.");
14426 assert_eq!(highlights.len(), 1);
14427 assert_eq!(highlights[0].0, 0..4);
14428 assert_eq!(
14429 highlights[0].1.background_color,
14430 Some(cx.theme().status().created_background)
14431 );
14432 })
14433 .unwrap();
14434 }
14435
14436 // Multiple edits
14437 {
14438 let window = cx.add_window(|cx| {
14439 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14440 Editor::new(EditorMode::Full, buffer, None, true, cx)
14441 });
14442 let cx = &mut VisualTestContext::from_window(*window, cx);
14443
14444 window
14445 .update(cx, |editor, cx| {
14446 let snapshot = editor.snapshot(cx);
14447 let edits = vec![
14448 (
14449 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14450 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
14451 "Greetings".into(),
14452 ),
14453 (
14454 snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
14455 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
14456 " and universe".into(),
14457 ),
14458 ];
14459
14460 let InlineCompletionText::Edit { text, highlights } =
14461 inline_completion_edit_text(&snapshot, &edits, false, cx)
14462 else {
14463 panic!("Failed to generate inline completion text");
14464 };
14465
14466 assert_eq!(text, "Greetings, world and universe!");
14467 assert_eq!(highlights.len(), 2);
14468 assert_eq!(highlights[0].0, 0..9);
14469 assert_eq!(highlights[1].0, 16..29);
14470 assert_eq!(
14471 highlights[0].1.background_color,
14472 Some(cx.theme().status().created_background)
14473 );
14474 assert_eq!(
14475 highlights[1].1.background_color,
14476 Some(cx.theme().status().created_background)
14477 );
14478 })
14479 .unwrap();
14480 }
14481
14482 // Multiple lines with edits
14483 {
14484 let window = cx.add_window(|cx| {
14485 let buffer =
14486 MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
14487 Editor::new(EditorMode::Full, buffer, None, true, cx)
14488 });
14489 let cx = &mut VisualTestContext::from_window(*window, cx);
14490
14491 window
14492 .update(cx, |editor, cx| {
14493 let snapshot = editor.snapshot(cx);
14494 let edits = vec![
14495 (
14496 snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
14497 ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
14498 "modified".to_string(),
14499 ),
14500 (
14501 snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
14502 ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
14503 "New third line".to_string(),
14504 ),
14505 (
14506 snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
14507 ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
14508 " updated".to_string(),
14509 ),
14510 ];
14511
14512 let InlineCompletionText::Edit { text, highlights } =
14513 inline_completion_edit_text(&snapshot, &edits, false, cx)
14514 else {
14515 panic!("Failed to generate inline completion text");
14516 };
14517
14518 assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
14519 assert_eq!(highlights.len(), 3);
14520 assert_eq!(highlights[0].0, 7..15); // "modified"
14521 assert_eq!(highlights[1].0, 16..30); // "New third line"
14522 assert_eq!(highlights[2].0, 37..45); // " updated"
14523
14524 for highlight in &highlights {
14525 assert_eq!(
14526 highlight.1.background_color,
14527 Some(cx.theme().status().created_background)
14528 );
14529 }
14530 })
14531 .unwrap();
14532 }
14533}
14534
14535#[gpui::test]
14536fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
14537 init_test(cx, |_| {});
14538
14539 // Deletion
14540 {
14541 let window = cx.add_window(|cx| {
14542 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14543 Editor::new(EditorMode::Full, buffer, None, true, cx)
14544 });
14545 let cx = &mut VisualTestContext::from_window(*window, cx);
14546
14547 window
14548 .update(cx, |editor, cx| {
14549 let snapshot = editor.snapshot(cx);
14550 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
14551 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
14552 let edits = vec![(edit_range, "".to_string())];
14553
14554 let InlineCompletionText::Edit { text, highlights } =
14555 inline_completion_edit_text(&snapshot, &edits, true, cx)
14556 else {
14557 panic!("Failed to generate inline completion text");
14558 };
14559
14560 assert_eq!(text, "Hello, world!");
14561 assert_eq!(highlights.len(), 1);
14562 assert_eq!(highlights[0].0, 5..11);
14563 assert_eq!(
14564 highlights[0].1.background_color,
14565 Some(cx.theme().status().deleted_background)
14566 );
14567 })
14568 .unwrap();
14569 }
14570
14571 // Insertion
14572 {
14573 let window = cx.add_window(|cx| {
14574 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14575 Editor::new(EditorMode::Full, buffer, None, true, cx)
14576 });
14577 let cx = &mut VisualTestContext::from_window(*window, cx);
14578
14579 window
14580 .update(cx, |editor, cx| {
14581 let snapshot = editor.snapshot(cx);
14582 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14583 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14584 let edits = vec![(edit_range, " digital".to_string())];
14585
14586 let InlineCompletionText::Edit { text, highlights } =
14587 inline_completion_edit_text(&snapshot, &edits, true, cx)
14588 else {
14589 panic!("Failed to generate inline completion text");
14590 };
14591
14592 assert_eq!(text, "Hello, digital world!");
14593 assert_eq!(highlights.len(), 1);
14594 assert_eq!(highlights[0].0, 6..14);
14595 assert_eq!(
14596 highlights[0].1.background_color,
14597 Some(cx.theme().status().created_background)
14598 );
14599 })
14600 .unwrap();
14601 }
14602}
14603
14604fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
14605 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
14606 point..point
14607}
14608
14609fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
14610 let (text, ranges) = marked_text_ranges(marked_text, true);
14611 assert_eq!(view.text(cx), text);
14612 assert_eq!(
14613 view.selections.ranges(cx),
14614 ranges,
14615 "Assert selections are {}",
14616 marked_text
14617 );
14618}
14619
14620pub fn handle_signature_help_request(
14621 cx: &mut EditorLspTestContext,
14622 mocked_response: lsp::SignatureHelp,
14623) -> impl Future<Output = ()> {
14624 let mut request =
14625 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
14626 let mocked_response = mocked_response.clone();
14627 async move { Ok(Some(mocked_response)) }
14628 });
14629
14630 async move {
14631 request.next().await;
14632 }
14633}
14634
14635/// Handle completion request passing a marked string specifying where the completion
14636/// should be triggered from using '|' character, what range should be replaced, and what completions
14637/// should be returned using '<' and '>' to delimit the range
14638pub fn handle_completion_request(
14639 cx: &mut EditorLspTestContext,
14640 marked_string: &str,
14641 completions: Vec<&'static str>,
14642 counter: Arc<AtomicUsize>,
14643) -> impl Future<Output = ()> {
14644 let complete_from_marker: TextRangeMarker = '|'.into();
14645 let replace_range_marker: TextRangeMarker = ('<', '>').into();
14646 let (_, mut marked_ranges) = marked_text_ranges_by(
14647 marked_string,
14648 vec![complete_from_marker.clone(), replace_range_marker.clone()],
14649 );
14650
14651 let complete_from_position =
14652 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
14653 let replace_range =
14654 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
14655
14656 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
14657 let completions = completions.clone();
14658 counter.fetch_add(1, atomic::Ordering::Release);
14659 async move {
14660 assert_eq!(params.text_document_position.text_document.uri, url.clone());
14661 assert_eq!(
14662 params.text_document_position.position,
14663 complete_from_position
14664 );
14665 Ok(Some(lsp::CompletionResponse::Array(
14666 completions
14667 .iter()
14668 .map(|completion_text| lsp::CompletionItem {
14669 label: completion_text.to_string(),
14670 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14671 range: replace_range,
14672 new_text: completion_text.to_string(),
14673 })),
14674 ..Default::default()
14675 })
14676 .collect(),
14677 )))
14678 }
14679 });
14680
14681 async move {
14682 request.next().await;
14683 }
14684}
14685
14686fn handle_resolve_completion_request(
14687 cx: &mut EditorLspTestContext,
14688 edits: Option<Vec<(&'static str, &'static str)>>,
14689) -> impl Future<Output = ()> {
14690 let edits = edits.map(|edits| {
14691 edits
14692 .iter()
14693 .map(|(marked_string, new_text)| {
14694 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
14695 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14696 lsp::TextEdit::new(replace_range, new_text.to_string())
14697 })
14698 .collect::<Vec<_>>()
14699 });
14700
14701 let mut request =
14702 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14703 let edits = edits.clone();
14704 async move {
14705 Ok(lsp::CompletionItem {
14706 additional_text_edits: edits,
14707 ..Default::default()
14708 })
14709 }
14710 });
14711
14712 async move {
14713 request.next().await;
14714 }
14715}
14716
14717pub(crate) fn update_test_language_settings(
14718 cx: &mut TestAppContext,
14719 f: impl Fn(&mut AllLanguageSettingsContent),
14720) {
14721 cx.update(|cx| {
14722 SettingsStore::update_global(cx, |store, cx| {
14723 store.update_user_settings::<AllLanguageSettings>(cx, f);
14724 });
14725 });
14726}
14727
14728pub(crate) fn update_test_project_settings(
14729 cx: &mut TestAppContext,
14730 f: impl Fn(&mut ProjectSettings),
14731) {
14732 cx.update(|cx| {
14733 SettingsStore::update_global(cx, |store, cx| {
14734 store.update_user_settings::<ProjectSettings>(cx, f);
14735 });
14736 });
14737}
14738
14739pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14740 cx.update(|cx| {
14741 assets::Assets.load_test_fonts(cx);
14742 let store = SettingsStore::test(cx);
14743 cx.set_global(store);
14744 theme::init(theme::LoadThemes::JustBase, cx);
14745 release_channel::init(SemanticVersion::default(), cx);
14746 client::init_settings(cx);
14747 language::init(cx);
14748 Project::init_settings(cx);
14749 workspace::init_settings(cx);
14750 crate::init(cx);
14751 });
14752
14753 update_test_language_settings(cx, f);
14754}
14755
14756#[track_caller]
14757fn assert_hunk_revert(
14758 not_reverted_text_with_selections: &str,
14759 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14760 expected_reverted_text_with_selections: &str,
14761 base_text: &str,
14762 cx: &mut EditorLspTestContext,
14763) {
14764 cx.set_state(not_reverted_text_with_selections);
14765 cx.set_diff_base(base_text);
14766 cx.executor().run_until_parked();
14767
14768 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14769 let snapshot = editor.snapshot(cx);
14770 let reverted_hunk_statuses = snapshot
14771 .diff_map
14772 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14773 .map(|hunk| hunk_status(&hunk))
14774 .collect::<Vec<_>>();
14775
14776 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14777 reverted_hunk_statuses
14778 });
14779 cx.executor().run_until_parked();
14780 cx.assert_editor_state(expected_reverted_text_with_selections);
14781 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14782}