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.read().is_none()));
8346 cx.simulate_keystroke("s");
8347 assert!(cx.editor(|e, _| e.context_menu.read().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.read().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.read().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.read().as_ref() {
8472 assert_eq!(
8473 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8474 &["first", "last"]
8475 );
8476 } else {
8477 panic!("expected completion menu to be open");
8478 }
8479 });
8480
8481 cx.update_editor(|editor, cx| {
8482 editor.move_page_down(&MovePageDown::default(), cx);
8483 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8484 assert!(
8485 menu.selected_item == 1,
8486 "expected PageDown to select the last item from the context menu"
8487 );
8488 } else {
8489 panic!("expected completion menu to stay open after PageDown");
8490 }
8491 });
8492
8493 cx.update_editor(|editor, cx| {
8494 editor.move_page_up(&MovePageUp::default(), cx);
8495 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
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.read().as_ref() {
8564 assert_eq!(
8565 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8566 &["r", "ret", "Range", "return"]
8567 );
8568 } else {
8569 panic!("expected completion menu to be open");
8570 }
8571 });
8572}
8573
8574#[gpui::test]
8575async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8576 init_test(cx, |_| {});
8577
8578 let mut cx = EditorLspTestContext::new_rust(
8579 lsp::ServerCapabilities {
8580 completion_provider: Some(lsp::CompletionOptions {
8581 trigger_characters: Some(vec![".".to_string()]),
8582 resolve_provider: Some(true),
8583 ..Default::default()
8584 }),
8585 ..Default::default()
8586 },
8587 cx,
8588 )
8589 .await;
8590
8591 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8592 cx.simulate_keystroke(".");
8593 let completion_item = lsp::CompletionItem {
8594 label: "Some".into(),
8595 kind: Some(lsp::CompletionItemKind::SNIPPET),
8596 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8597 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8598 kind: lsp::MarkupKind::Markdown,
8599 value: "```rust\nSome(2)\n```".to_string(),
8600 })),
8601 deprecated: Some(false),
8602 sort_text: Some("Some".to_string()),
8603 filter_text: Some("Some".to_string()),
8604 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8605 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8606 range: lsp::Range {
8607 start: lsp::Position {
8608 line: 0,
8609 character: 22,
8610 },
8611 end: lsp::Position {
8612 line: 0,
8613 character: 22,
8614 },
8615 },
8616 new_text: "Some(2)".to_string(),
8617 })),
8618 additional_text_edits: Some(vec![lsp::TextEdit {
8619 range: lsp::Range {
8620 start: lsp::Position {
8621 line: 0,
8622 character: 20,
8623 },
8624 end: lsp::Position {
8625 line: 0,
8626 character: 22,
8627 },
8628 },
8629 new_text: "".to_string(),
8630 }]),
8631 ..Default::default()
8632 };
8633
8634 let closure_completion_item = completion_item.clone();
8635 let counter = Arc::new(AtomicUsize::new(0));
8636 let counter_clone = counter.clone();
8637 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8638 let task_completion_item = closure_completion_item.clone();
8639 counter_clone.fetch_add(1, atomic::Ordering::Release);
8640 async move {
8641 Ok(Some(lsp::CompletionResponse::Array(vec![
8642 task_completion_item,
8643 ])))
8644 }
8645 });
8646
8647 cx.condition(|editor, _| editor.context_menu_visible())
8648 .await;
8649 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8650 assert!(request.next().await.is_some());
8651 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8652
8653 cx.simulate_keystroke("S");
8654 cx.simulate_keystroke("o");
8655 cx.simulate_keystroke("m");
8656 cx.condition(|editor, _| editor.context_menu_visible())
8657 .await;
8658 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8659 assert!(request.next().await.is_some());
8660 assert!(request.next().await.is_some());
8661 assert!(request.next().await.is_some());
8662 request.close();
8663 assert!(request.next().await.is_none());
8664 assert_eq!(
8665 counter.load(atomic::Ordering::Acquire),
8666 4,
8667 "With the completions menu open, only one LSP request should happen per input"
8668 );
8669}
8670
8671#[gpui::test]
8672async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8673 init_test(cx, |_| {});
8674 let mut cx = EditorTestContext::new(cx).await;
8675 let language = Arc::new(Language::new(
8676 LanguageConfig {
8677 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8678 ..Default::default()
8679 },
8680 Some(tree_sitter_rust::LANGUAGE.into()),
8681 ));
8682 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8683
8684 // If multiple selections intersect a line, the line is only toggled once.
8685 cx.set_state(indoc! {"
8686 fn a() {
8687 «//b();
8688 ˇ»// «c();
8689 //ˇ» d();
8690 }
8691 "});
8692
8693 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8694
8695 cx.assert_editor_state(indoc! {"
8696 fn a() {
8697 «b();
8698 c();
8699 ˇ» d();
8700 }
8701 "});
8702
8703 // The comment prefix is inserted at the same column for every line in a
8704 // selection.
8705 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8706
8707 cx.assert_editor_state(indoc! {"
8708 fn a() {
8709 // «b();
8710 // c();
8711 ˇ»// d();
8712 }
8713 "});
8714
8715 // If a selection ends at the beginning of a line, that line is not toggled.
8716 cx.set_selections_state(indoc! {"
8717 fn a() {
8718 // b();
8719 «// c();
8720 ˇ» // d();
8721 }
8722 "});
8723
8724 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8725
8726 cx.assert_editor_state(indoc! {"
8727 fn a() {
8728 // b();
8729 «c();
8730 ˇ» // d();
8731 }
8732 "});
8733
8734 // If a selection span a single line and is empty, the line is toggled.
8735 cx.set_state(indoc! {"
8736 fn a() {
8737 a();
8738 b();
8739 ˇ
8740 }
8741 "});
8742
8743 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8744
8745 cx.assert_editor_state(indoc! {"
8746 fn a() {
8747 a();
8748 b();
8749 //•ˇ
8750 }
8751 "});
8752
8753 // If a selection span multiple lines, empty lines are not toggled.
8754 cx.set_state(indoc! {"
8755 fn a() {
8756 «a();
8757
8758 c();ˇ»
8759 }
8760 "});
8761
8762 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8763
8764 cx.assert_editor_state(indoc! {"
8765 fn a() {
8766 // «a();
8767
8768 // c();ˇ»
8769 }
8770 "});
8771
8772 // If a selection includes multiple comment prefixes, all lines are uncommented.
8773 cx.set_state(indoc! {"
8774 fn a() {
8775 «// a();
8776 /// b();
8777 //! c();ˇ»
8778 }
8779 "});
8780
8781 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8782
8783 cx.assert_editor_state(indoc! {"
8784 fn a() {
8785 «a();
8786 b();
8787 c();ˇ»
8788 }
8789 "});
8790}
8791
8792#[gpui::test]
8793async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8794 init_test(cx, |_| {});
8795 let mut cx = EditorTestContext::new(cx).await;
8796 let language = Arc::new(Language::new(
8797 LanguageConfig {
8798 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8799 ..Default::default()
8800 },
8801 Some(tree_sitter_rust::LANGUAGE.into()),
8802 ));
8803 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8804
8805 let toggle_comments = &ToggleComments {
8806 advance_downwards: false,
8807 ignore_indent: true,
8808 };
8809
8810 // If multiple selections intersect a line, the line is only toggled once.
8811 cx.set_state(indoc! {"
8812 fn a() {
8813 // «b();
8814 // c();
8815 // ˇ» d();
8816 }
8817 "});
8818
8819 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8820
8821 cx.assert_editor_state(indoc! {"
8822 fn a() {
8823 «b();
8824 c();
8825 ˇ» d();
8826 }
8827 "});
8828
8829 // The comment prefix is inserted at the beginning of each line
8830 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8831
8832 cx.assert_editor_state(indoc! {"
8833 fn a() {
8834 // «b();
8835 // c();
8836 // ˇ» d();
8837 }
8838 "});
8839
8840 // If a selection ends at the beginning of a line, that line is not toggled.
8841 cx.set_selections_state(indoc! {"
8842 fn a() {
8843 // b();
8844 // «c();
8845 ˇ»// d();
8846 }
8847 "});
8848
8849 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8850
8851 cx.assert_editor_state(indoc! {"
8852 fn a() {
8853 // b();
8854 «c();
8855 ˇ»// d();
8856 }
8857 "});
8858
8859 // If a selection span a single line and is empty, the line is toggled.
8860 cx.set_state(indoc! {"
8861 fn a() {
8862 a();
8863 b();
8864 ˇ
8865 }
8866 "});
8867
8868 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8869
8870 cx.assert_editor_state(indoc! {"
8871 fn a() {
8872 a();
8873 b();
8874 //ˇ
8875 }
8876 "});
8877
8878 // If a selection span multiple lines, empty lines are not toggled.
8879 cx.set_state(indoc! {"
8880 fn a() {
8881 «a();
8882
8883 c();ˇ»
8884 }
8885 "});
8886
8887 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8888
8889 cx.assert_editor_state(indoc! {"
8890 fn a() {
8891 // «a();
8892
8893 // c();ˇ»
8894 }
8895 "});
8896
8897 // If a selection includes multiple comment prefixes, all lines are uncommented.
8898 cx.set_state(indoc! {"
8899 fn a() {
8900 // «a();
8901 /// b();
8902 //! c();ˇ»
8903 }
8904 "});
8905
8906 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8907
8908 cx.assert_editor_state(indoc! {"
8909 fn a() {
8910 «a();
8911 b();
8912 c();ˇ»
8913 }
8914 "});
8915}
8916
8917#[gpui::test]
8918async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8919 init_test(cx, |_| {});
8920
8921 let language = Arc::new(Language::new(
8922 LanguageConfig {
8923 line_comments: vec!["// ".into()],
8924 ..Default::default()
8925 },
8926 Some(tree_sitter_rust::LANGUAGE.into()),
8927 ));
8928
8929 let mut cx = EditorTestContext::new(cx).await;
8930
8931 cx.language_registry().add(language.clone());
8932 cx.update_buffer(|buffer, cx| {
8933 buffer.set_language(Some(language), cx);
8934 });
8935
8936 let toggle_comments = &ToggleComments {
8937 advance_downwards: true,
8938 ignore_indent: false,
8939 };
8940
8941 // Single cursor on one line -> advance
8942 // Cursor moves horizontally 3 characters as well on non-blank line
8943 cx.set_state(indoc!(
8944 "fn a() {
8945 ˇdog();
8946 cat();
8947 }"
8948 ));
8949 cx.update_editor(|editor, cx| {
8950 editor.toggle_comments(toggle_comments, cx);
8951 });
8952 cx.assert_editor_state(indoc!(
8953 "fn a() {
8954 // dog();
8955 catˇ();
8956 }"
8957 ));
8958
8959 // Single selection on one line -> don't advance
8960 cx.set_state(indoc!(
8961 "fn a() {
8962 «dog()ˇ»;
8963 cat();
8964 }"
8965 ));
8966 cx.update_editor(|editor, cx| {
8967 editor.toggle_comments(toggle_comments, cx);
8968 });
8969 cx.assert_editor_state(indoc!(
8970 "fn a() {
8971 // «dog()ˇ»;
8972 cat();
8973 }"
8974 ));
8975
8976 // Multiple cursors on one line -> advance
8977 cx.set_state(indoc!(
8978 "fn a() {
8979 ˇdˇog();
8980 cat();
8981 }"
8982 ));
8983 cx.update_editor(|editor, cx| {
8984 editor.toggle_comments(toggle_comments, cx);
8985 });
8986 cx.assert_editor_state(indoc!(
8987 "fn a() {
8988 // dog();
8989 catˇ(ˇ);
8990 }"
8991 ));
8992
8993 // Multiple cursors on one line, with selection -> don't advance
8994 cx.set_state(indoc!(
8995 "fn a() {
8996 ˇdˇog«()ˇ»;
8997 cat();
8998 }"
8999 ));
9000 cx.update_editor(|editor, cx| {
9001 editor.toggle_comments(toggle_comments, cx);
9002 });
9003 cx.assert_editor_state(indoc!(
9004 "fn a() {
9005 // ˇdˇog«()ˇ»;
9006 cat();
9007 }"
9008 ));
9009
9010 // Single cursor on one line -> advance
9011 // Cursor moves to column 0 on blank line
9012 cx.set_state(indoc!(
9013 "fn a() {
9014 ˇdog();
9015
9016 cat();
9017 }"
9018 ));
9019 cx.update_editor(|editor, cx| {
9020 editor.toggle_comments(toggle_comments, cx);
9021 });
9022 cx.assert_editor_state(indoc!(
9023 "fn a() {
9024 // dog();
9025 ˇ
9026 cat();
9027 }"
9028 ));
9029
9030 // Single cursor on one line -> advance
9031 // Cursor starts and ends at column 0
9032 cx.set_state(indoc!(
9033 "fn a() {
9034 ˇ dog();
9035 cat();
9036 }"
9037 ));
9038 cx.update_editor(|editor, cx| {
9039 editor.toggle_comments(toggle_comments, cx);
9040 });
9041 cx.assert_editor_state(indoc!(
9042 "fn a() {
9043 // dog();
9044 ˇ cat();
9045 }"
9046 ));
9047}
9048
9049#[gpui::test]
9050async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9051 init_test(cx, |_| {});
9052
9053 let mut cx = EditorTestContext::new(cx).await;
9054
9055 let html_language = Arc::new(
9056 Language::new(
9057 LanguageConfig {
9058 name: "HTML".into(),
9059 block_comment: Some(("<!-- ".into(), " -->".into())),
9060 ..Default::default()
9061 },
9062 Some(tree_sitter_html::language()),
9063 )
9064 .with_injection_query(
9065 r#"
9066 (script_element
9067 (raw_text) @content
9068 (#set! "language" "javascript"))
9069 "#,
9070 )
9071 .unwrap(),
9072 );
9073
9074 let javascript_language = Arc::new(Language::new(
9075 LanguageConfig {
9076 name: "JavaScript".into(),
9077 line_comments: vec!["// ".into()],
9078 ..Default::default()
9079 },
9080 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9081 ));
9082
9083 cx.language_registry().add(html_language.clone());
9084 cx.language_registry().add(javascript_language.clone());
9085 cx.update_buffer(|buffer, cx| {
9086 buffer.set_language(Some(html_language), cx);
9087 });
9088
9089 // Toggle comments for empty selections
9090 cx.set_state(
9091 &r#"
9092 <p>A</p>ˇ
9093 <p>B</p>ˇ
9094 <p>C</p>ˇ
9095 "#
9096 .unindent(),
9097 );
9098 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9099 cx.assert_editor_state(
9100 &r#"
9101 <!-- <p>A</p>ˇ -->
9102 <!-- <p>B</p>ˇ -->
9103 <!-- <p>C</p>ˇ -->
9104 "#
9105 .unindent(),
9106 );
9107 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9108 cx.assert_editor_state(
9109 &r#"
9110 <p>A</p>ˇ
9111 <p>B</p>ˇ
9112 <p>C</p>ˇ
9113 "#
9114 .unindent(),
9115 );
9116
9117 // Toggle comments for mixture of empty and non-empty selections, where
9118 // multiple selections occupy a given line.
9119 cx.set_state(
9120 &r#"
9121 <p>A«</p>
9122 <p>ˇ»B</p>ˇ
9123 <p>C«</p>
9124 <p>ˇ»D</p>ˇ
9125 "#
9126 .unindent(),
9127 );
9128
9129 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9130 cx.assert_editor_state(
9131 &r#"
9132 <!-- <p>A«</p>
9133 <p>ˇ»B</p>ˇ -->
9134 <!-- <p>C«</p>
9135 <p>ˇ»D</p>ˇ -->
9136 "#
9137 .unindent(),
9138 );
9139 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9140 cx.assert_editor_state(
9141 &r#"
9142 <p>A«</p>
9143 <p>ˇ»B</p>ˇ
9144 <p>C«</p>
9145 <p>ˇ»D</p>ˇ
9146 "#
9147 .unindent(),
9148 );
9149
9150 // Toggle comments when different languages are active for different
9151 // selections.
9152 cx.set_state(
9153 &r#"
9154 ˇ<script>
9155 ˇvar x = new Y();
9156 ˇ</script>
9157 "#
9158 .unindent(),
9159 );
9160 cx.executor().run_until_parked();
9161 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9162 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9163 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9164 cx.assert_editor_state(
9165 &r#"
9166 <!-- ˇ<script> -->
9167 // ˇvar x = new Y();
9168 // ˇ</script>
9169 "#
9170 .unindent(),
9171 );
9172}
9173
9174#[gpui::test]
9175fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9176 init_test(cx, |_| {});
9177
9178 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9179 let multibuffer = cx.new_model(|cx| {
9180 let mut multibuffer = MultiBuffer::new(ReadWrite);
9181 multibuffer.push_excerpts(
9182 buffer.clone(),
9183 [
9184 ExcerptRange {
9185 context: Point::new(0, 0)..Point::new(0, 4),
9186 primary: None,
9187 },
9188 ExcerptRange {
9189 context: Point::new(1, 0)..Point::new(1, 4),
9190 primary: None,
9191 },
9192 ],
9193 cx,
9194 );
9195 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9196 multibuffer
9197 });
9198
9199 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9200 view.update(cx, |view, cx| {
9201 assert_eq!(view.text(cx), "aaaa\nbbbb");
9202 view.change_selections(None, cx, |s| {
9203 s.select_ranges([
9204 Point::new(0, 0)..Point::new(0, 0),
9205 Point::new(1, 0)..Point::new(1, 0),
9206 ])
9207 });
9208
9209 view.handle_input("X", cx);
9210 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9211 assert_eq!(
9212 view.selections.ranges(cx),
9213 [
9214 Point::new(0, 1)..Point::new(0, 1),
9215 Point::new(1, 1)..Point::new(1, 1),
9216 ]
9217 );
9218
9219 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9220 view.change_selections(None, cx, |s| {
9221 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9222 });
9223 view.backspace(&Default::default(), cx);
9224 assert_eq!(view.text(cx), "Xa\nbbb");
9225 assert_eq!(
9226 view.selections.ranges(cx),
9227 [Point::new(1, 0)..Point::new(1, 0)]
9228 );
9229
9230 view.change_selections(None, cx, |s| {
9231 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9232 });
9233 view.backspace(&Default::default(), cx);
9234 assert_eq!(view.text(cx), "X\nbb");
9235 assert_eq!(
9236 view.selections.ranges(cx),
9237 [Point::new(0, 1)..Point::new(0, 1)]
9238 );
9239 });
9240}
9241
9242#[gpui::test]
9243fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9244 init_test(cx, |_| {});
9245
9246 let markers = vec![('[', ']').into(), ('(', ')').into()];
9247 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9248 indoc! {"
9249 [aaaa
9250 (bbbb]
9251 cccc)",
9252 },
9253 markers.clone(),
9254 );
9255 let excerpt_ranges = markers.into_iter().map(|marker| {
9256 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9257 ExcerptRange {
9258 context,
9259 primary: None,
9260 }
9261 });
9262 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9263 let multibuffer = cx.new_model(|cx| {
9264 let mut multibuffer = MultiBuffer::new(ReadWrite);
9265 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9266 multibuffer
9267 });
9268
9269 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9270 view.update(cx, |view, cx| {
9271 let (expected_text, selection_ranges) = marked_text_ranges(
9272 indoc! {"
9273 aaaa
9274 bˇbbb
9275 bˇbbˇb
9276 cccc"
9277 },
9278 true,
9279 );
9280 assert_eq!(view.text(cx), expected_text);
9281 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9282
9283 view.handle_input("X", cx);
9284
9285 let (expected_text, expected_selections) = marked_text_ranges(
9286 indoc! {"
9287 aaaa
9288 bXˇbbXb
9289 bXˇbbXˇb
9290 cccc"
9291 },
9292 false,
9293 );
9294 assert_eq!(view.text(cx), expected_text);
9295 assert_eq!(view.selections.ranges(cx), expected_selections);
9296
9297 view.newline(&Newline, cx);
9298 let (expected_text, expected_selections) = marked_text_ranges(
9299 indoc! {"
9300 aaaa
9301 bX
9302 ˇbbX
9303 b
9304 bX
9305 ˇbbX
9306 ˇb
9307 cccc"
9308 },
9309 false,
9310 );
9311 assert_eq!(view.text(cx), expected_text);
9312 assert_eq!(view.selections.ranges(cx), expected_selections);
9313 });
9314}
9315
9316#[gpui::test]
9317fn test_refresh_selections(cx: &mut TestAppContext) {
9318 init_test(cx, |_| {});
9319
9320 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9321 let mut excerpt1_id = None;
9322 let multibuffer = cx.new_model(|cx| {
9323 let mut multibuffer = MultiBuffer::new(ReadWrite);
9324 excerpt1_id = multibuffer
9325 .push_excerpts(
9326 buffer.clone(),
9327 [
9328 ExcerptRange {
9329 context: Point::new(0, 0)..Point::new(1, 4),
9330 primary: None,
9331 },
9332 ExcerptRange {
9333 context: Point::new(1, 0)..Point::new(2, 4),
9334 primary: None,
9335 },
9336 ],
9337 cx,
9338 )
9339 .into_iter()
9340 .next();
9341 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9342 multibuffer
9343 });
9344
9345 let editor = cx.add_window(|cx| {
9346 let mut editor = build_editor(multibuffer.clone(), cx);
9347 let snapshot = editor.snapshot(cx);
9348 editor.change_selections(None, cx, |s| {
9349 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9350 });
9351 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9352 assert_eq!(
9353 editor.selections.ranges(cx),
9354 [
9355 Point::new(1, 3)..Point::new(1, 3),
9356 Point::new(2, 1)..Point::new(2, 1),
9357 ]
9358 );
9359 editor
9360 });
9361
9362 // Refreshing selections is a no-op when excerpts haven't changed.
9363 _ = editor.update(cx, |editor, cx| {
9364 editor.change_selections(None, cx, |s| s.refresh());
9365 assert_eq!(
9366 editor.selections.ranges(cx),
9367 [
9368 Point::new(1, 3)..Point::new(1, 3),
9369 Point::new(2, 1)..Point::new(2, 1),
9370 ]
9371 );
9372 });
9373
9374 multibuffer.update(cx, |multibuffer, cx| {
9375 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9376 });
9377 _ = editor.update(cx, |editor, cx| {
9378 // Removing an excerpt causes the first selection to become degenerate.
9379 assert_eq!(
9380 editor.selections.ranges(cx),
9381 [
9382 Point::new(0, 0)..Point::new(0, 0),
9383 Point::new(0, 1)..Point::new(0, 1)
9384 ]
9385 );
9386
9387 // Refreshing selections will relocate the first selection to the original buffer
9388 // location.
9389 editor.change_selections(None, cx, |s| s.refresh());
9390 assert_eq!(
9391 editor.selections.ranges(cx),
9392 [
9393 Point::new(0, 1)..Point::new(0, 1),
9394 Point::new(0, 3)..Point::new(0, 3)
9395 ]
9396 );
9397 assert!(editor.selections.pending_anchor().is_some());
9398 });
9399}
9400
9401#[gpui::test]
9402fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9403 init_test(cx, |_| {});
9404
9405 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9406 let mut excerpt1_id = None;
9407 let multibuffer = cx.new_model(|cx| {
9408 let mut multibuffer = MultiBuffer::new(ReadWrite);
9409 excerpt1_id = multibuffer
9410 .push_excerpts(
9411 buffer.clone(),
9412 [
9413 ExcerptRange {
9414 context: Point::new(0, 0)..Point::new(1, 4),
9415 primary: None,
9416 },
9417 ExcerptRange {
9418 context: Point::new(1, 0)..Point::new(2, 4),
9419 primary: None,
9420 },
9421 ],
9422 cx,
9423 )
9424 .into_iter()
9425 .next();
9426 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9427 multibuffer
9428 });
9429
9430 let editor = cx.add_window(|cx| {
9431 let mut editor = build_editor(multibuffer.clone(), cx);
9432 let snapshot = editor.snapshot(cx);
9433 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9434 assert_eq!(
9435 editor.selections.ranges(cx),
9436 [Point::new(1, 3)..Point::new(1, 3)]
9437 );
9438 editor
9439 });
9440
9441 multibuffer.update(cx, |multibuffer, cx| {
9442 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9443 });
9444 _ = editor.update(cx, |editor, cx| {
9445 assert_eq!(
9446 editor.selections.ranges(cx),
9447 [Point::new(0, 0)..Point::new(0, 0)]
9448 );
9449
9450 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9451 editor.change_selections(None, cx, |s| s.refresh());
9452 assert_eq!(
9453 editor.selections.ranges(cx),
9454 [Point::new(0, 3)..Point::new(0, 3)]
9455 );
9456 assert!(editor.selections.pending_anchor().is_some());
9457 });
9458}
9459
9460#[gpui::test]
9461async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9462 init_test(cx, |_| {});
9463
9464 let language = Arc::new(
9465 Language::new(
9466 LanguageConfig {
9467 brackets: BracketPairConfig {
9468 pairs: vec![
9469 BracketPair {
9470 start: "{".to_string(),
9471 end: "}".to_string(),
9472 close: true,
9473 surround: true,
9474 newline: true,
9475 },
9476 BracketPair {
9477 start: "/* ".to_string(),
9478 end: " */".to_string(),
9479 close: true,
9480 surround: true,
9481 newline: true,
9482 },
9483 ],
9484 ..Default::default()
9485 },
9486 ..Default::default()
9487 },
9488 Some(tree_sitter_rust::LANGUAGE.into()),
9489 )
9490 .with_indents_query("")
9491 .unwrap(),
9492 );
9493
9494 let text = concat!(
9495 "{ }\n", //
9496 " x\n", //
9497 " /* */\n", //
9498 "x\n", //
9499 "{{} }\n", //
9500 );
9501
9502 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9503 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9504 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9505 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9506 .await;
9507
9508 view.update(cx, |view, cx| {
9509 view.change_selections(None, cx, |s| {
9510 s.select_display_ranges([
9511 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9512 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9513 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9514 ])
9515 });
9516 view.newline(&Newline, cx);
9517
9518 assert_eq!(
9519 view.buffer().read(cx).read(cx).text(),
9520 concat!(
9521 "{ \n", // Suppress rustfmt
9522 "\n", //
9523 "}\n", //
9524 " x\n", //
9525 " /* \n", //
9526 " \n", //
9527 " */\n", //
9528 "x\n", //
9529 "{{} \n", //
9530 "}\n", //
9531 )
9532 );
9533 });
9534}
9535
9536#[gpui::test]
9537fn test_highlighted_ranges(cx: &mut TestAppContext) {
9538 init_test(cx, |_| {});
9539
9540 let editor = cx.add_window(|cx| {
9541 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9542 build_editor(buffer.clone(), cx)
9543 });
9544
9545 _ = editor.update(cx, |editor, cx| {
9546 struct Type1;
9547 struct Type2;
9548
9549 let buffer = editor.buffer.read(cx).snapshot(cx);
9550
9551 let anchor_range =
9552 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9553
9554 editor.highlight_background::<Type1>(
9555 &[
9556 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9557 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9558 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9559 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9560 ],
9561 |_| Hsla::red(),
9562 cx,
9563 );
9564 editor.highlight_background::<Type2>(
9565 &[
9566 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9567 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9568 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9569 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9570 ],
9571 |_| Hsla::green(),
9572 cx,
9573 );
9574
9575 let snapshot = editor.snapshot(cx);
9576 let mut highlighted_ranges = editor.background_highlights_in_range(
9577 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9578 &snapshot,
9579 cx.theme().colors(),
9580 );
9581 // Enforce a consistent ordering based on color without relying on the ordering of the
9582 // highlight's `TypeId` which is non-executor.
9583 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9584 assert_eq!(
9585 highlighted_ranges,
9586 &[
9587 (
9588 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9589 Hsla::red(),
9590 ),
9591 (
9592 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9593 Hsla::red(),
9594 ),
9595 (
9596 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9597 Hsla::green(),
9598 ),
9599 (
9600 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9601 Hsla::green(),
9602 ),
9603 ]
9604 );
9605 assert_eq!(
9606 editor.background_highlights_in_range(
9607 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9608 &snapshot,
9609 cx.theme().colors(),
9610 ),
9611 &[(
9612 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9613 Hsla::red(),
9614 )]
9615 );
9616 });
9617}
9618
9619#[gpui::test]
9620async fn test_following(cx: &mut gpui::TestAppContext) {
9621 init_test(cx, |_| {});
9622
9623 let fs = FakeFs::new(cx.executor());
9624 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9625
9626 let buffer = project.update(cx, |project, cx| {
9627 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9628 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9629 });
9630 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9631 let follower = cx.update(|cx| {
9632 cx.open_window(
9633 WindowOptions {
9634 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9635 gpui::Point::new(px(0.), px(0.)),
9636 gpui::Point::new(px(10.), px(80.)),
9637 ))),
9638 ..Default::default()
9639 },
9640 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9641 )
9642 .unwrap()
9643 });
9644
9645 let is_still_following = Rc::new(RefCell::new(true));
9646 let follower_edit_event_count = Rc::new(RefCell::new(0));
9647 let pending_update = Rc::new(RefCell::new(None));
9648 _ = follower.update(cx, {
9649 let update = pending_update.clone();
9650 let is_still_following = is_still_following.clone();
9651 let follower_edit_event_count = follower_edit_event_count.clone();
9652 |_, cx| {
9653 cx.subscribe(
9654 &leader.root_view(cx).unwrap(),
9655 move |_, leader, event, cx| {
9656 leader
9657 .read(cx)
9658 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9659 },
9660 )
9661 .detach();
9662
9663 cx.subscribe(
9664 &follower.root_view(cx).unwrap(),
9665 move |_, _, event: &EditorEvent, _cx| {
9666 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9667 *is_still_following.borrow_mut() = false;
9668 }
9669
9670 if let EditorEvent::BufferEdited = event {
9671 *follower_edit_event_count.borrow_mut() += 1;
9672 }
9673 },
9674 )
9675 .detach();
9676 }
9677 });
9678
9679 // Update the selections only
9680 _ = leader.update(cx, |leader, cx| {
9681 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9682 });
9683 follower
9684 .update(cx, |follower, cx| {
9685 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9686 })
9687 .unwrap()
9688 .await
9689 .unwrap();
9690 _ = follower.update(cx, |follower, cx| {
9691 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9692 });
9693 assert!(*is_still_following.borrow());
9694 assert_eq!(*follower_edit_event_count.borrow(), 0);
9695
9696 // Update the scroll position only
9697 _ = leader.update(cx, |leader, cx| {
9698 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9699 });
9700 follower
9701 .update(cx, |follower, cx| {
9702 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9703 })
9704 .unwrap()
9705 .await
9706 .unwrap();
9707 assert_eq!(
9708 follower
9709 .update(cx, |follower, cx| follower.scroll_position(cx))
9710 .unwrap(),
9711 gpui::Point::new(1.5, 3.5)
9712 );
9713 assert!(*is_still_following.borrow());
9714 assert_eq!(*follower_edit_event_count.borrow(), 0);
9715
9716 // Update the selections and scroll position. The follower's scroll position is updated
9717 // via autoscroll, not via the leader's exact scroll position.
9718 _ = leader.update(cx, |leader, cx| {
9719 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9720 leader.request_autoscroll(Autoscroll::newest(), cx);
9721 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9722 });
9723 follower
9724 .update(cx, |follower, cx| {
9725 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9726 })
9727 .unwrap()
9728 .await
9729 .unwrap();
9730 _ = follower.update(cx, |follower, cx| {
9731 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9732 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9733 });
9734 assert!(*is_still_following.borrow());
9735
9736 // Creating a pending selection that precedes another selection
9737 _ = leader.update(cx, |leader, cx| {
9738 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9739 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9740 });
9741 follower
9742 .update(cx, |follower, cx| {
9743 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9744 })
9745 .unwrap()
9746 .await
9747 .unwrap();
9748 _ = follower.update(cx, |follower, cx| {
9749 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9750 });
9751 assert!(*is_still_following.borrow());
9752
9753 // Extend the pending selection so that it surrounds another selection
9754 _ = leader.update(cx, |leader, cx| {
9755 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9756 });
9757 follower
9758 .update(cx, |follower, cx| {
9759 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9760 })
9761 .unwrap()
9762 .await
9763 .unwrap();
9764 _ = follower.update(cx, |follower, cx| {
9765 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9766 });
9767
9768 // Scrolling locally breaks the follow
9769 _ = follower.update(cx, |follower, cx| {
9770 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9771 follower.set_scroll_anchor(
9772 ScrollAnchor {
9773 anchor: top_anchor,
9774 offset: gpui::Point::new(0.0, 0.5),
9775 },
9776 cx,
9777 );
9778 });
9779 assert!(!(*is_still_following.borrow()));
9780}
9781
9782#[gpui::test]
9783async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9784 init_test(cx, |_| {});
9785
9786 let fs = FakeFs::new(cx.executor());
9787 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9788 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9789 let pane = workspace
9790 .update(cx, |workspace, _| workspace.active_pane().clone())
9791 .unwrap();
9792
9793 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9794
9795 let leader = pane.update(cx, |_, cx| {
9796 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9797 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9798 });
9799
9800 // Start following the editor when it has no excerpts.
9801 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9802 let follower_1 = cx
9803 .update_window(*workspace.deref(), |_, cx| {
9804 Editor::from_state_proto(
9805 workspace.root_view(cx).unwrap(),
9806 ViewId {
9807 creator: Default::default(),
9808 id: 0,
9809 },
9810 &mut state_message,
9811 cx,
9812 )
9813 })
9814 .unwrap()
9815 .unwrap()
9816 .await
9817 .unwrap();
9818
9819 let update_message = Rc::new(RefCell::new(None));
9820 follower_1.update(cx, {
9821 let update = update_message.clone();
9822 |_, cx| {
9823 cx.subscribe(&leader, move |_, leader, event, cx| {
9824 leader
9825 .read(cx)
9826 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9827 })
9828 .detach();
9829 }
9830 });
9831
9832 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9833 (
9834 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9835 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9836 )
9837 });
9838
9839 // Insert some excerpts.
9840 leader.update(cx, |leader, cx| {
9841 leader.buffer.update(cx, |multibuffer, cx| {
9842 let excerpt_ids = multibuffer.push_excerpts(
9843 buffer_1.clone(),
9844 [
9845 ExcerptRange {
9846 context: 1..6,
9847 primary: None,
9848 },
9849 ExcerptRange {
9850 context: 12..15,
9851 primary: None,
9852 },
9853 ExcerptRange {
9854 context: 0..3,
9855 primary: None,
9856 },
9857 ],
9858 cx,
9859 );
9860 multibuffer.insert_excerpts_after(
9861 excerpt_ids[0],
9862 buffer_2.clone(),
9863 [
9864 ExcerptRange {
9865 context: 8..12,
9866 primary: None,
9867 },
9868 ExcerptRange {
9869 context: 0..6,
9870 primary: None,
9871 },
9872 ],
9873 cx,
9874 );
9875 });
9876 });
9877
9878 // Apply the update of adding the excerpts.
9879 follower_1
9880 .update(cx, |follower, cx| {
9881 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9882 })
9883 .await
9884 .unwrap();
9885 assert_eq!(
9886 follower_1.update(cx, |editor, cx| editor.text(cx)),
9887 leader.update(cx, |editor, cx| editor.text(cx))
9888 );
9889 update_message.borrow_mut().take();
9890
9891 // Start following separately after it already has excerpts.
9892 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9893 let follower_2 = cx
9894 .update_window(*workspace.deref(), |_, cx| {
9895 Editor::from_state_proto(
9896 workspace.root_view(cx).unwrap().clone(),
9897 ViewId {
9898 creator: Default::default(),
9899 id: 0,
9900 },
9901 &mut state_message,
9902 cx,
9903 )
9904 })
9905 .unwrap()
9906 .unwrap()
9907 .await
9908 .unwrap();
9909 assert_eq!(
9910 follower_2.update(cx, |editor, cx| editor.text(cx)),
9911 leader.update(cx, |editor, cx| editor.text(cx))
9912 );
9913
9914 // Remove some excerpts.
9915 leader.update(cx, |leader, cx| {
9916 leader.buffer.update(cx, |multibuffer, cx| {
9917 let excerpt_ids = multibuffer.excerpt_ids();
9918 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9919 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9920 });
9921 });
9922
9923 // Apply the update of removing the excerpts.
9924 follower_1
9925 .update(cx, |follower, cx| {
9926 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9927 })
9928 .await
9929 .unwrap();
9930 follower_2
9931 .update(cx, |follower, cx| {
9932 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9933 })
9934 .await
9935 .unwrap();
9936 update_message.borrow_mut().take();
9937 assert_eq!(
9938 follower_1.update(cx, |editor, cx| editor.text(cx)),
9939 leader.update(cx, |editor, cx| editor.text(cx))
9940 );
9941}
9942
9943#[gpui::test]
9944async fn go_to_prev_overlapping_diagnostic(
9945 executor: BackgroundExecutor,
9946 cx: &mut gpui::TestAppContext,
9947) {
9948 init_test(cx, |_| {});
9949
9950 let mut cx = EditorTestContext::new(cx).await;
9951 let lsp_store =
9952 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9953
9954 cx.set_state(indoc! {"
9955 ˇfn func(abc def: i32) -> u32 {
9956 }
9957 "});
9958
9959 cx.update(|cx| {
9960 lsp_store.update(cx, |lsp_store, cx| {
9961 lsp_store
9962 .update_diagnostics(
9963 LanguageServerId(0),
9964 lsp::PublishDiagnosticsParams {
9965 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9966 version: None,
9967 diagnostics: vec![
9968 lsp::Diagnostic {
9969 range: lsp::Range::new(
9970 lsp::Position::new(0, 11),
9971 lsp::Position::new(0, 12),
9972 ),
9973 severity: Some(lsp::DiagnosticSeverity::ERROR),
9974 ..Default::default()
9975 },
9976 lsp::Diagnostic {
9977 range: lsp::Range::new(
9978 lsp::Position::new(0, 12),
9979 lsp::Position::new(0, 15),
9980 ),
9981 severity: Some(lsp::DiagnosticSeverity::ERROR),
9982 ..Default::default()
9983 },
9984 lsp::Diagnostic {
9985 range: lsp::Range::new(
9986 lsp::Position::new(0, 25),
9987 lsp::Position::new(0, 28),
9988 ),
9989 severity: Some(lsp::DiagnosticSeverity::ERROR),
9990 ..Default::default()
9991 },
9992 ],
9993 },
9994 &[],
9995 cx,
9996 )
9997 .unwrap()
9998 });
9999 });
10000
10001 executor.run_until_parked();
10002
10003 cx.update_editor(|editor, cx| {
10004 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10005 });
10006
10007 cx.assert_editor_state(indoc! {"
10008 fn func(abc def: i32) -> ˇu32 {
10009 }
10010 "});
10011
10012 cx.update_editor(|editor, cx| {
10013 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10014 });
10015
10016 cx.assert_editor_state(indoc! {"
10017 fn func(abc ˇdef: i32) -> u32 {
10018 }
10019 "});
10020
10021 cx.update_editor(|editor, cx| {
10022 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10023 });
10024
10025 cx.assert_editor_state(indoc! {"
10026 fn func(abcˇ def: i32) -> u32 {
10027 }
10028 "});
10029
10030 cx.update_editor(|editor, cx| {
10031 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10032 });
10033
10034 cx.assert_editor_state(indoc! {"
10035 fn func(abc def: i32) -> ˇu32 {
10036 }
10037 "});
10038}
10039
10040#[gpui::test]
10041async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10042 init_test(cx, |_| {});
10043
10044 let mut cx = EditorTestContext::new(cx).await;
10045
10046 cx.set_state(indoc! {"
10047 fn func(abˇc def: i32) -> u32 {
10048 }
10049 "});
10050 let lsp_store =
10051 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10052
10053 cx.update(|cx| {
10054 lsp_store.update(cx, |lsp_store, cx| {
10055 lsp_store.update_diagnostics(
10056 LanguageServerId(0),
10057 lsp::PublishDiagnosticsParams {
10058 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10059 version: None,
10060 diagnostics: vec![lsp::Diagnostic {
10061 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10062 severity: Some(lsp::DiagnosticSeverity::ERROR),
10063 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10064 ..Default::default()
10065 }],
10066 },
10067 &[],
10068 cx,
10069 )
10070 })
10071 }).unwrap();
10072 cx.run_until_parked();
10073 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10074 cx.run_until_parked();
10075 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10076}
10077
10078#[gpui::test]
10079async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10080 init_test(cx, |_| {});
10081
10082 let mut cx = EditorTestContext::new(cx).await;
10083
10084 let diff_base = r#"
10085 use some::mod;
10086
10087 const A: u32 = 42;
10088
10089 fn main() {
10090 println!("hello");
10091
10092 println!("world");
10093 }
10094 "#
10095 .unindent();
10096
10097 // Edits are modified, removed, modified, added
10098 cx.set_state(
10099 &r#"
10100 use some::modified;
10101
10102 ˇ
10103 fn main() {
10104 println!("hello there");
10105
10106 println!("around the");
10107 println!("world");
10108 }
10109 "#
10110 .unindent(),
10111 );
10112
10113 cx.set_diff_base(&diff_base);
10114 executor.run_until_parked();
10115
10116 cx.update_editor(|editor, cx| {
10117 //Wrap around the bottom of the buffer
10118 for _ in 0..3 {
10119 editor.go_to_next_hunk(&GoToHunk, cx);
10120 }
10121 });
10122
10123 cx.assert_editor_state(
10124 &r#"
10125 ˇuse some::modified;
10126
10127
10128 fn main() {
10129 println!("hello there");
10130
10131 println!("around the");
10132 println!("world");
10133 }
10134 "#
10135 .unindent(),
10136 );
10137
10138 cx.update_editor(|editor, cx| {
10139 //Wrap around the top of the buffer
10140 for _ in 0..2 {
10141 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10142 }
10143 });
10144
10145 cx.assert_editor_state(
10146 &r#"
10147 use some::modified;
10148
10149
10150 fn main() {
10151 ˇ println!("hello there");
10152
10153 println!("around the");
10154 println!("world");
10155 }
10156 "#
10157 .unindent(),
10158 );
10159
10160 cx.update_editor(|editor, cx| {
10161 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10162 });
10163
10164 cx.assert_editor_state(
10165 &r#"
10166 use some::modified;
10167
10168 ˇ
10169 fn main() {
10170 println!("hello there");
10171
10172 println!("around the");
10173 println!("world");
10174 }
10175 "#
10176 .unindent(),
10177 );
10178
10179 cx.update_editor(|editor, cx| {
10180 for _ in 0..3 {
10181 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10182 }
10183 });
10184
10185 cx.assert_editor_state(
10186 &r#"
10187 use some::modified;
10188
10189
10190 fn main() {
10191 ˇ println!("hello there");
10192
10193 println!("around the");
10194 println!("world");
10195 }
10196 "#
10197 .unindent(),
10198 );
10199
10200 cx.update_editor(|editor, cx| {
10201 editor.fold(&Fold, cx);
10202
10203 //Make sure that the fold only gets one hunk
10204 for _ in 0..4 {
10205 editor.go_to_next_hunk(&GoToHunk, cx);
10206 }
10207 });
10208
10209 cx.assert_editor_state(
10210 &r#"
10211 ˇuse some::modified;
10212
10213
10214 fn main() {
10215 println!("hello there");
10216
10217 println!("around the");
10218 println!("world");
10219 }
10220 "#
10221 .unindent(),
10222 );
10223}
10224
10225#[test]
10226fn test_split_words() {
10227 fn split(text: &str) -> Vec<&str> {
10228 split_words(text).collect()
10229 }
10230
10231 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10232 assert_eq!(split("hello_world"), &["hello_", "world"]);
10233 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10234 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10235 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10236 assert_eq!(split("helloworld"), &["helloworld"]);
10237
10238 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10239}
10240
10241#[gpui::test]
10242async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10243 init_test(cx, |_| {});
10244
10245 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10246 let mut assert = |before, after| {
10247 let _state_context = cx.set_state(before);
10248 cx.update_editor(|editor, cx| {
10249 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10250 });
10251 cx.assert_editor_state(after);
10252 };
10253
10254 // Outside bracket jumps to outside of matching bracket
10255 assert("console.logˇ(var);", "console.log(var)ˇ;");
10256 assert("console.log(var)ˇ;", "console.logˇ(var);");
10257
10258 // Inside bracket jumps to inside of matching bracket
10259 assert("console.log(ˇvar);", "console.log(varˇ);");
10260 assert("console.log(varˇ);", "console.log(ˇvar);");
10261
10262 // When outside a bracket and inside, favor jumping to the inside bracket
10263 assert(
10264 "console.log('foo', [1, 2, 3]ˇ);",
10265 "console.log(ˇ'foo', [1, 2, 3]);",
10266 );
10267 assert(
10268 "console.log(ˇ'foo', [1, 2, 3]);",
10269 "console.log('foo', [1, 2, 3]ˇ);",
10270 );
10271
10272 // Bias forward if two options are equally likely
10273 assert(
10274 "let result = curried_fun()ˇ();",
10275 "let result = curried_fun()()ˇ;",
10276 );
10277
10278 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10279 assert(
10280 indoc! {"
10281 function test() {
10282 console.log('test')ˇ
10283 }"},
10284 indoc! {"
10285 function test() {
10286 console.logˇ('test')
10287 }"},
10288 );
10289}
10290
10291#[gpui::test]
10292async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10293 init_test(cx, |_| {});
10294
10295 let fs = FakeFs::new(cx.executor());
10296 fs.insert_tree(
10297 "/a",
10298 json!({
10299 "main.rs": "fn main() { let a = 5; }",
10300 "other.rs": "// Test file",
10301 }),
10302 )
10303 .await;
10304 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10305
10306 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10307 language_registry.add(Arc::new(Language::new(
10308 LanguageConfig {
10309 name: "Rust".into(),
10310 matcher: LanguageMatcher {
10311 path_suffixes: vec!["rs".to_string()],
10312 ..Default::default()
10313 },
10314 brackets: BracketPairConfig {
10315 pairs: vec![BracketPair {
10316 start: "{".to_string(),
10317 end: "}".to_string(),
10318 close: true,
10319 surround: true,
10320 newline: true,
10321 }],
10322 disabled_scopes_by_bracket_ix: Vec::new(),
10323 },
10324 ..Default::default()
10325 },
10326 Some(tree_sitter_rust::LANGUAGE.into()),
10327 )));
10328 let mut fake_servers = language_registry.register_fake_lsp(
10329 "Rust",
10330 FakeLspAdapter {
10331 capabilities: lsp::ServerCapabilities {
10332 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10333 first_trigger_character: "{".to_string(),
10334 more_trigger_character: None,
10335 }),
10336 ..Default::default()
10337 },
10338 ..Default::default()
10339 },
10340 );
10341
10342 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10343
10344 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10345
10346 let worktree_id = workspace
10347 .update(cx, |workspace, cx| {
10348 workspace.project().update(cx, |project, cx| {
10349 project.worktrees(cx).next().unwrap().read(cx).id()
10350 })
10351 })
10352 .unwrap();
10353
10354 let buffer = project
10355 .update(cx, |project, cx| {
10356 project.open_local_buffer("/a/main.rs", cx)
10357 })
10358 .await
10359 .unwrap();
10360 let editor_handle = workspace
10361 .update(cx, |workspace, cx| {
10362 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10363 })
10364 .unwrap()
10365 .await
10366 .unwrap()
10367 .downcast::<Editor>()
10368 .unwrap();
10369
10370 cx.executor().start_waiting();
10371 let fake_server = fake_servers.next().await.unwrap();
10372
10373 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10374 assert_eq!(
10375 params.text_document_position.text_document.uri,
10376 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10377 );
10378 assert_eq!(
10379 params.text_document_position.position,
10380 lsp::Position::new(0, 21),
10381 );
10382
10383 Ok(Some(vec![lsp::TextEdit {
10384 new_text: "]".to_string(),
10385 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10386 }]))
10387 });
10388
10389 editor_handle.update(cx, |editor, cx| {
10390 editor.focus(cx);
10391 editor.change_selections(None, cx, |s| {
10392 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10393 });
10394 editor.handle_input("{", cx);
10395 });
10396
10397 cx.executor().run_until_parked();
10398
10399 buffer.update(cx, |buffer, _| {
10400 assert_eq!(
10401 buffer.text(),
10402 "fn main() { let a = {5}; }",
10403 "No extra braces from on type formatting should appear in the buffer"
10404 )
10405 });
10406}
10407
10408#[gpui::test]
10409async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10410 init_test(cx, |_| {});
10411
10412 let fs = FakeFs::new(cx.executor());
10413 fs.insert_tree(
10414 "/a",
10415 json!({
10416 "main.rs": "fn main() { let a = 5; }",
10417 "other.rs": "// Test file",
10418 }),
10419 )
10420 .await;
10421
10422 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10423
10424 let server_restarts = Arc::new(AtomicUsize::new(0));
10425 let closure_restarts = Arc::clone(&server_restarts);
10426 let language_server_name = "test language server";
10427 let language_name: LanguageName = "Rust".into();
10428
10429 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10430 language_registry.add(Arc::new(Language::new(
10431 LanguageConfig {
10432 name: language_name.clone(),
10433 matcher: LanguageMatcher {
10434 path_suffixes: vec!["rs".to_string()],
10435 ..Default::default()
10436 },
10437 ..Default::default()
10438 },
10439 Some(tree_sitter_rust::LANGUAGE.into()),
10440 )));
10441 let mut fake_servers = language_registry.register_fake_lsp(
10442 "Rust",
10443 FakeLspAdapter {
10444 name: language_server_name,
10445 initialization_options: Some(json!({
10446 "testOptionValue": true
10447 })),
10448 initializer: Some(Box::new(move |fake_server| {
10449 let task_restarts = Arc::clone(&closure_restarts);
10450 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10451 task_restarts.fetch_add(1, atomic::Ordering::Release);
10452 futures::future::ready(Ok(()))
10453 });
10454 })),
10455 ..Default::default()
10456 },
10457 );
10458
10459 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10460 let _buffer = project
10461 .update(cx, |project, cx| {
10462 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10463 })
10464 .await
10465 .unwrap();
10466 let _fake_server = fake_servers.next().await.unwrap();
10467 update_test_language_settings(cx, |language_settings| {
10468 language_settings.languages.insert(
10469 language_name.clone(),
10470 LanguageSettingsContent {
10471 tab_size: NonZeroU32::new(8),
10472 ..Default::default()
10473 },
10474 );
10475 });
10476 cx.executor().run_until_parked();
10477 assert_eq!(
10478 server_restarts.load(atomic::Ordering::Acquire),
10479 0,
10480 "Should not restart LSP server on an unrelated change"
10481 );
10482
10483 update_test_project_settings(cx, |project_settings| {
10484 project_settings.lsp.insert(
10485 "Some other server name".into(),
10486 LspSettings {
10487 binary: None,
10488 settings: None,
10489 initialization_options: Some(json!({
10490 "some other init value": false
10491 })),
10492 },
10493 );
10494 });
10495 cx.executor().run_until_parked();
10496 assert_eq!(
10497 server_restarts.load(atomic::Ordering::Acquire),
10498 0,
10499 "Should not restart LSP server on an unrelated LSP settings change"
10500 );
10501
10502 update_test_project_settings(cx, |project_settings| {
10503 project_settings.lsp.insert(
10504 language_server_name.into(),
10505 LspSettings {
10506 binary: None,
10507 settings: None,
10508 initialization_options: Some(json!({
10509 "anotherInitValue": false
10510 })),
10511 },
10512 );
10513 });
10514 cx.executor().run_until_parked();
10515 assert_eq!(
10516 server_restarts.load(atomic::Ordering::Acquire),
10517 1,
10518 "Should restart LSP server on a related LSP settings change"
10519 );
10520
10521 update_test_project_settings(cx, |project_settings| {
10522 project_settings.lsp.insert(
10523 language_server_name.into(),
10524 LspSettings {
10525 binary: None,
10526 settings: None,
10527 initialization_options: Some(json!({
10528 "anotherInitValue": false
10529 })),
10530 },
10531 );
10532 });
10533 cx.executor().run_until_parked();
10534 assert_eq!(
10535 server_restarts.load(atomic::Ordering::Acquire),
10536 1,
10537 "Should not restart LSP server on a related LSP settings change that is the same"
10538 );
10539
10540 update_test_project_settings(cx, |project_settings| {
10541 project_settings.lsp.insert(
10542 language_server_name.into(),
10543 LspSettings {
10544 binary: None,
10545 settings: None,
10546 initialization_options: None,
10547 },
10548 );
10549 });
10550 cx.executor().run_until_parked();
10551 assert_eq!(
10552 server_restarts.load(atomic::Ordering::Acquire),
10553 2,
10554 "Should restart LSP server on another related LSP settings change"
10555 );
10556}
10557
10558#[gpui::test]
10559async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10560 init_test(cx, |_| {});
10561
10562 let mut cx = EditorLspTestContext::new_rust(
10563 lsp::ServerCapabilities {
10564 completion_provider: Some(lsp::CompletionOptions {
10565 trigger_characters: Some(vec![".".to_string()]),
10566 resolve_provider: Some(true),
10567 ..Default::default()
10568 }),
10569 ..Default::default()
10570 },
10571 cx,
10572 )
10573 .await;
10574
10575 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10576 cx.simulate_keystroke(".");
10577 let completion_item = lsp::CompletionItem {
10578 label: "some".into(),
10579 kind: Some(lsp::CompletionItemKind::SNIPPET),
10580 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10581 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10582 kind: lsp::MarkupKind::Markdown,
10583 value: "```rust\nSome(2)\n```".to_string(),
10584 })),
10585 deprecated: Some(false),
10586 sort_text: Some("fffffff2".to_string()),
10587 filter_text: Some("some".to_string()),
10588 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10589 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10590 range: lsp::Range {
10591 start: lsp::Position {
10592 line: 0,
10593 character: 22,
10594 },
10595 end: lsp::Position {
10596 line: 0,
10597 character: 22,
10598 },
10599 },
10600 new_text: "Some(2)".to_string(),
10601 })),
10602 additional_text_edits: Some(vec![lsp::TextEdit {
10603 range: lsp::Range {
10604 start: lsp::Position {
10605 line: 0,
10606 character: 20,
10607 },
10608 end: lsp::Position {
10609 line: 0,
10610 character: 22,
10611 },
10612 },
10613 new_text: "".to_string(),
10614 }]),
10615 ..Default::default()
10616 };
10617
10618 let closure_completion_item = completion_item.clone();
10619 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10620 let task_completion_item = closure_completion_item.clone();
10621 async move {
10622 Ok(Some(lsp::CompletionResponse::Array(vec![
10623 task_completion_item,
10624 ])))
10625 }
10626 });
10627
10628 request.next().await;
10629
10630 cx.condition(|editor, _| editor.context_menu_visible())
10631 .await;
10632 let apply_additional_edits = cx.update_editor(|editor, cx| {
10633 editor
10634 .confirm_completion(&ConfirmCompletion::default(), cx)
10635 .unwrap()
10636 });
10637 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10638
10639 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10640 let task_completion_item = completion_item.clone();
10641 async move { Ok(task_completion_item) }
10642 })
10643 .next()
10644 .await
10645 .unwrap();
10646 apply_additional_edits.await.unwrap();
10647 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10648}
10649
10650#[gpui::test]
10651async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) {
10652 init_test(cx, |_| {});
10653
10654 let mut cx = EditorLspTestContext::new_rust(
10655 lsp::ServerCapabilities {
10656 completion_provider: Some(lsp::CompletionOptions {
10657 trigger_characters: Some(vec![".".to_string()]),
10658 resolve_provider: Some(true),
10659 ..Default::default()
10660 }),
10661 ..Default::default()
10662 },
10663 cx,
10664 )
10665 .await;
10666
10667 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10668 cx.simulate_keystroke(".");
10669
10670 let completion_item = lsp::CompletionItem {
10671 label: "unresolved".to_string(),
10672 detail: None,
10673 documentation: None,
10674 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10675 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10676 new_text: ".unresolved".to_string(),
10677 })),
10678 ..lsp::CompletionItem::default()
10679 };
10680
10681 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10682 let item = completion_item.clone();
10683 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10684 })
10685 .next()
10686 .await;
10687
10688 cx.condition(|editor, _| editor.context_menu_visible())
10689 .await;
10690 cx.update_editor(|editor, _| {
10691 let context_menu = editor.context_menu.read();
10692 let context_menu = context_menu
10693 .as_ref()
10694 .expect("Should have the context menu deployed");
10695 match context_menu {
10696 CodeContextMenu::Completions(completions_menu) => {
10697 let completions = completions_menu.completions.read();
10698 assert_eq!(completions.len(), 1, "Should have one completion");
10699 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10700 }
10701 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10702 }
10703 });
10704
10705 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10706 Ok(lsp::CompletionItem {
10707 label: "resolved".to_string(),
10708 detail: Some("Now resolved!".to_string()),
10709 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10710 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10711 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10712 new_text: ".resolved".to_string(),
10713 })),
10714 ..lsp::CompletionItem::default()
10715 })
10716 })
10717 .next()
10718 .await;
10719 cx.run_until_parked();
10720
10721 cx.update_editor(|editor, _| {
10722 let context_menu = editor.context_menu.read();
10723 let context_menu = context_menu
10724 .as_ref()
10725 .expect("Should have the context menu deployed");
10726 match context_menu {
10727 CodeContextMenu::Completions(completions_menu) => {
10728 let completions = completions_menu.completions.read();
10729 assert_eq!(completions.len(), 1, "Should have one completion");
10730 assert_eq!(
10731 completions.get(0).unwrap().label.text,
10732 "resolved",
10733 "Should update the completion label after resolving"
10734 );
10735 }
10736 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10737 }
10738 });
10739}
10740
10741#[gpui::test]
10742async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10743 init_test(cx, |_| {});
10744
10745 let mut cx = EditorLspTestContext::new_rust(
10746 lsp::ServerCapabilities {
10747 completion_provider: Some(lsp::CompletionOptions {
10748 trigger_characters: Some(vec![".".to_string()]),
10749 resolve_provider: Some(true),
10750 ..Default::default()
10751 }),
10752 ..Default::default()
10753 },
10754 cx,
10755 )
10756 .await;
10757
10758 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10759 cx.simulate_keystroke(".");
10760
10761 let default_commit_characters = vec!["?".to_string()];
10762 let default_data = json!({ "very": "special"});
10763 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10764 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10765 let default_edit_range = lsp::Range {
10766 start: lsp::Position {
10767 line: 0,
10768 character: 5,
10769 },
10770 end: lsp::Position {
10771 line: 0,
10772 character: 5,
10773 },
10774 };
10775
10776 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10777 let expect_first_item = Arc::new(AtomicBool::new(true));
10778 cx.lsp
10779 .server
10780 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10781 let closure_default_data = default_data.clone();
10782 let closure_resolve_requests_number = resolve_requests_number.clone();
10783 let closure_expect_first_item = expect_first_item.clone();
10784 let closure_default_commit_characters = default_commit_characters.clone();
10785 move |item_to_resolve, _| {
10786 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10787 let default_data = closure_default_data.clone();
10788 let default_commit_characters = closure_default_commit_characters.clone();
10789 let expect_first_item = closure_expect_first_item.clone();
10790 async move {
10791 if expect_first_item.load(atomic::Ordering::Acquire) {
10792 assert_eq!(
10793 item_to_resolve.label, "Some(2)",
10794 "Should have selected the first item"
10795 );
10796 assert_eq!(
10797 item_to_resolve.data,
10798 Some(json!({ "very": "special"})),
10799 "First item should bring its own data for resolving"
10800 );
10801 assert_eq!(
10802 item_to_resolve.commit_characters,
10803 Some(default_commit_characters),
10804 "First item had no own commit characters and should inherit the default ones"
10805 );
10806 assert!(
10807 matches!(
10808 item_to_resolve.text_edit,
10809 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10810 ),
10811 "First item should bring its own edit range for resolving"
10812 );
10813 assert_eq!(
10814 item_to_resolve.insert_text_format,
10815 Some(default_insert_text_format),
10816 "First item had no own insert text format and should inherit the default one"
10817 );
10818 assert_eq!(
10819 item_to_resolve.insert_text_mode,
10820 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10821 "First item should bring its own insert text mode for resolving"
10822 );
10823 Ok(item_to_resolve)
10824 } else {
10825 assert_eq!(
10826 item_to_resolve.label, "vec![2]",
10827 "Should have selected the last item"
10828 );
10829 assert_eq!(
10830 item_to_resolve.data,
10831 Some(default_data),
10832 "Last item has no own resolve data and should inherit the default one"
10833 );
10834 assert_eq!(
10835 item_to_resolve.commit_characters,
10836 Some(default_commit_characters),
10837 "Last item had no own commit characters and should inherit the default ones"
10838 );
10839 assert_eq!(
10840 item_to_resolve.text_edit,
10841 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10842 range: default_edit_range,
10843 new_text: "vec![2]".to_string()
10844 })),
10845 "Last item had no own edit range and should inherit the default one"
10846 );
10847 assert_eq!(
10848 item_to_resolve.insert_text_format,
10849 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10850 "Last item should bring its own insert text format for resolving"
10851 );
10852 assert_eq!(
10853 item_to_resolve.insert_text_mode,
10854 Some(default_insert_text_mode),
10855 "Last item had no own insert text mode and should inherit the default one"
10856 );
10857
10858 Ok(item_to_resolve)
10859 }
10860 }
10861 }
10862 }).detach();
10863
10864 let completion_data = default_data.clone();
10865 let completion_characters = default_commit_characters.clone();
10866 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10867 let default_data = completion_data.clone();
10868 let default_commit_characters = completion_characters.clone();
10869 async move {
10870 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10871 items: vec![
10872 lsp::CompletionItem {
10873 label: "Some(2)".into(),
10874 insert_text: Some("Some(2)".into()),
10875 data: Some(json!({ "very": "special"})),
10876 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10877 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10878 lsp::InsertReplaceEdit {
10879 new_text: "Some(2)".to_string(),
10880 insert: lsp::Range::default(),
10881 replace: lsp::Range::default(),
10882 },
10883 )),
10884 ..lsp::CompletionItem::default()
10885 },
10886 lsp::CompletionItem {
10887 label: "vec![2]".into(),
10888 insert_text: Some("vec![2]".into()),
10889 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10890 ..lsp::CompletionItem::default()
10891 },
10892 ],
10893 item_defaults: Some(lsp::CompletionListItemDefaults {
10894 data: Some(default_data.clone()),
10895 commit_characters: Some(default_commit_characters.clone()),
10896 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10897 default_edit_range,
10898 )),
10899 insert_text_format: Some(default_insert_text_format),
10900 insert_text_mode: Some(default_insert_text_mode),
10901 }),
10902 ..lsp::CompletionList::default()
10903 })))
10904 }
10905 })
10906 .next()
10907 .await;
10908
10909 cx.condition(|editor, _| editor.context_menu_visible())
10910 .await;
10911 cx.run_until_parked();
10912 cx.update_editor(|editor, _| {
10913 let menu = editor.context_menu.read();
10914 match menu.as_ref().expect("should have the completions menu") {
10915 CodeContextMenu::Completions(completions_menu) => {
10916 assert_eq!(
10917 completions_menu
10918 .matches
10919 .iter()
10920 .map(|c| c.string.as_str())
10921 .collect::<Vec<_>>(),
10922 vec!["Some(2)", "vec![2]"]
10923 );
10924 }
10925 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10926 }
10927 });
10928 assert_eq!(
10929 resolve_requests_number.load(atomic::Ordering::Acquire),
10930 1,
10931 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10932 );
10933
10934 cx.update_editor(|editor, cx| {
10935 editor.context_menu_first(&ContextMenuFirst, cx);
10936 });
10937 cx.run_until_parked();
10938 assert_eq!(
10939 resolve_requests_number.load(atomic::Ordering::Acquire),
10940 2,
10941 "After re-selecting the first item, another resolve request should have been sent"
10942 );
10943
10944 expect_first_item.store(false, atomic::Ordering::Release);
10945 cx.update_editor(|editor, cx| {
10946 editor.context_menu_last(&ContextMenuLast, cx);
10947 });
10948 cx.run_until_parked();
10949 assert_eq!(
10950 resolve_requests_number.load(atomic::Ordering::Acquire),
10951 3,
10952 "After selecting the other item, another resolve request should have been sent"
10953 );
10954}
10955
10956#[gpui::test]
10957async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10958 init_test(cx, |_| {});
10959
10960 let mut cx = EditorLspTestContext::new(
10961 Language::new(
10962 LanguageConfig {
10963 matcher: LanguageMatcher {
10964 path_suffixes: vec!["jsx".into()],
10965 ..Default::default()
10966 },
10967 overrides: [(
10968 "element".into(),
10969 LanguageConfigOverride {
10970 word_characters: Override::Set(['-'].into_iter().collect()),
10971 ..Default::default()
10972 },
10973 )]
10974 .into_iter()
10975 .collect(),
10976 ..Default::default()
10977 },
10978 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10979 )
10980 .with_override_query("(jsx_self_closing_element) @element")
10981 .unwrap(),
10982 lsp::ServerCapabilities {
10983 completion_provider: Some(lsp::CompletionOptions {
10984 trigger_characters: Some(vec![":".to_string()]),
10985 ..Default::default()
10986 }),
10987 ..Default::default()
10988 },
10989 cx,
10990 )
10991 .await;
10992
10993 cx.lsp
10994 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10995 Ok(Some(lsp::CompletionResponse::Array(vec![
10996 lsp::CompletionItem {
10997 label: "bg-blue".into(),
10998 ..Default::default()
10999 },
11000 lsp::CompletionItem {
11001 label: "bg-red".into(),
11002 ..Default::default()
11003 },
11004 lsp::CompletionItem {
11005 label: "bg-yellow".into(),
11006 ..Default::default()
11007 },
11008 ])))
11009 });
11010
11011 cx.set_state(r#"<p class="bgˇ" />"#);
11012
11013 // Trigger completion when typing a dash, because the dash is an extra
11014 // word character in the 'element' scope, which contains the cursor.
11015 cx.simulate_keystroke("-");
11016 cx.executor().run_until_parked();
11017 cx.update_editor(|editor, _| {
11018 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11019 assert_eq!(
11020 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11021 &["bg-red", "bg-blue", "bg-yellow"]
11022 );
11023 } else {
11024 panic!("expected completion menu to be open");
11025 }
11026 });
11027
11028 cx.simulate_keystroke("l");
11029 cx.executor().run_until_parked();
11030 cx.update_editor(|editor, _| {
11031 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11032 assert_eq!(
11033 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11034 &["bg-blue", "bg-yellow"]
11035 );
11036 } else {
11037 panic!("expected completion menu to be open");
11038 }
11039 });
11040
11041 // When filtering completions, consider the character after the '-' to
11042 // be the start of a subword.
11043 cx.set_state(r#"<p class="yelˇ" />"#);
11044 cx.simulate_keystroke("l");
11045 cx.executor().run_until_parked();
11046 cx.update_editor(|editor, _| {
11047 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11048 assert_eq!(
11049 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11050 &["bg-yellow"]
11051 );
11052 } else {
11053 panic!("expected completion menu to be open");
11054 }
11055 });
11056}
11057
11058#[gpui::test]
11059async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11060 init_test(cx, |settings| {
11061 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11062 FormatterList(vec![Formatter::Prettier].into()),
11063 ))
11064 });
11065
11066 let fs = FakeFs::new(cx.executor());
11067 fs.insert_file("/file.ts", Default::default()).await;
11068
11069 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11070 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11071
11072 language_registry.add(Arc::new(Language::new(
11073 LanguageConfig {
11074 name: "TypeScript".into(),
11075 matcher: LanguageMatcher {
11076 path_suffixes: vec!["ts".to_string()],
11077 ..Default::default()
11078 },
11079 ..Default::default()
11080 },
11081 Some(tree_sitter_rust::LANGUAGE.into()),
11082 )));
11083 update_test_language_settings(cx, |settings| {
11084 settings.defaults.prettier = Some(PrettierSettings {
11085 allowed: true,
11086 ..PrettierSettings::default()
11087 });
11088 });
11089
11090 let test_plugin = "test_plugin";
11091 let _ = language_registry.register_fake_lsp(
11092 "TypeScript",
11093 FakeLspAdapter {
11094 prettier_plugins: vec![test_plugin],
11095 ..Default::default()
11096 },
11097 );
11098
11099 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11100 let buffer = project
11101 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11102 .await
11103 .unwrap();
11104
11105 let buffer_text = "one\ntwo\nthree\n";
11106 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11107 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11108 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11109
11110 editor
11111 .update(cx, |editor, cx| {
11112 editor.perform_format(
11113 project.clone(),
11114 FormatTrigger::Manual,
11115 FormatTarget::Buffer,
11116 cx,
11117 )
11118 })
11119 .unwrap()
11120 .await;
11121 assert_eq!(
11122 editor.update(cx, |editor, cx| editor.text(cx)),
11123 buffer_text.to_string() + prettier_format_suffix,
11124 "Test prettier formatting was not applied to the original buffer text",
11125 );
11126
11127 update_test_language_settings(cx, |settings| {
11128 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11129 });
11130 let format = editor.update(cx, |editor, cx| {
11131 editor.perform_format(
11132 project.clone(),
11133 FormatTrigger::Manual,
11134 FormatTarget::Buffer,
11135 cx,
11136 )
11137 });
11138 format.await.unwrap();
11139 assert_eq!(
11140 editor.update(cx, |editor, cx| editor.text(cx)),
11141 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11142 "Autoformatting (via test prettier) was not applied to the original buffer text",
11143 );
11144}
11145
11146#[gpui::test]
11147async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11148 init_test(cx, |_| {});
11149 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11150 let base_text = indoc! {r#"
11151 struct Row;
11152 struct Row1;
11153 struct Row2;
11154
11155 struct Row4;
11156 struct Row5;
11157 struct Row6;
11158
11159 struct Row8;
11160 struct Row9;
11161 struct Row10;"#};
11162
11163 // When addition hunks are not adjacent to carets, no hunk revert is performed
11164 assert_hunk_revert(
11165 indoc! {r#"struct Row;
11166 struct Row1;
11167 struct Row1.1;
11168 struct Row1.2;
11169 struct Row2;ˇ
11170
11171 struct Row4;
11172 struct Row5;
11173 struct Row6;
11174
11175 struct Row8;
11176 ˇstruct Row9;
11177 struct Row9.1;
11178 struct Row9.2;
11179 struct Row9.3;
11180 struct Row10;"#},
11181 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11182 indoc! {r#"struct Row;
11183 struct Row1;
11184 struct Row1.1;
11185 struct Row1.2;
11186 struct Row2;ˇ
11187
11188 struct Row4;
11189 struct Row5;
11190 struct Row6;
11191
11192 struct Row8;
11193 ˇstruct Row9;
11194 struct Row9.1;
11195 struct Row9.2;
11196 struct Row9.3;
11197 struct Row10;"#},
11198 base_text,
11199 &mut cx,
11200 );
11201 // Same for selections
11202 assert_hunk_revert(
11203 indoc! {r#"struct Row;
11204 struct Row1;
11205 struct Row2;
11206 struct Row2.1;
11207 struct Row2.2;
11208 «ˇ
11209 struct Row4;
11210 struct» Row5;
11211 «struct Row6;
11212 ˇ»
11213 struct Row9.1;
11214 struct Row9.2;
11215 struct Row9.3;
11216 struct Row8;
11217 struct Row9;
11218 struct Row10;"#},
11219 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11220 indoc! {r#"struct Row;
11221 struct Row1;
11222 struct Row2;
11223 struct Row2.1;
11224 struct Row2.2;
11225 «ˇ
11226 struct Row4;
11227 struct» Row5;
11228 «struct Row6;
11229 ˇ»
11230 struct Row9.1;
11231 struct Row9.2;
11232 struct Row9.3;
11233 struct Row8;
11234 struct Row9;
11235 struct Row10;"#},
11236 base_text,
11237 &mut cx,
11238 );
11239
11240 // When carets and selections intersect the addition hunks, those are reverted.
11241 // Adjacent carets got merged.
11242 assert_hunk_revert(
11243 indoc! {r#"struct Row;
11244 ˇ// something on the top
11245 struct Row1;
11246 struct Row2;
11247 struct Roˇw3.1;
11248 struct Row2.2;
11249 struct Row2.3;ˇ
11250
11251 struct Row4;
11252 struct ˇRow5.1;
11253 struct Row5.2;
11254 struct «Rowˇ»5.3;
11255 struct Row5;
11256 struct Row6;
11257 ˇ
11258 struct Row9.1;
11259 struct «Rowˇ»9.2;
11260 struct «ˇRow»9.3;
11261 struct Row8;
11262 struct Row9;
11263 «ˇ// something on bottom»
11264 struct Row10;"#},
11265 vec![
11266 DiffHunkStatus::Added,
11267 DiffHunkStatus::Added,
11268 DiffHunkStatus::Added,
11269 DiffHunkStatus::Added,
11270 DiffHunkStatus::Added,
11271 ],
11272 indoc! {r#"struct Row;
11273 ˇstruct Row1;
11274 struct Row2;
11275 ˇ
11276 struct Row4;
11277 ˇstruct Row5;
11278 struct Row6;
11279 ˇ
11280 ˇstruct Row8;
11281 struct Row9;
11282 ˇstruct Row10;"#},
11283 base_text,
11284 &mut cx,
11285 );
11286}
11287
11288#[gpui::test]
11289async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11290 init_test(cx, |_| {});
11291 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11292 let base_text = indoc! {r#"
11293 struct Row;
11294 struct Row1;
11295 struct Row2;
11296
11297 struct Row4;
11298 struct Row5;
11299 struct Row6;
11300
11301 struct Row8;
11302 struct Row9;
11303 struct Row10;"#};
11304
11305 // Modification hunks behave the same as the addition ones.
11306 assert_hunk_revert(
11307 indoc! {r#"struct Row;
11308 struct Row1;
11309 struct Row33;
11310 ˇ
11311 struct Row4;
11312 struct Row5;
11313 struct Row6;
11314 ˇ
11315 struct Row99;
11316 struct Row9;
11317 struct Row10;"#},
11318 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11319 indoc! {r#"struct Row;
11320 struct Row1;
11321 struct Row33;
11322 ˇ
11323 struct Row4;
11324 struct Row5;
11325 struct Row6;
11326 ˇ
11327 struct Row99;
11328 struct Row9;
11329 struct Row10;"#},
11330 base_text,
11331 &mut cx,
11332 );
11333 assert_hunk_revert(
11334 indoc! {r#"struct Row;
11335 struct Row1;
11336 struct Row33;
11337 «ˇ
11338 struct Row4;
11339 struct» Row5;
11340 «struct Row6;
11341 ˇ»
11342 struct Row99;
11343 struct Row9;
11344 struct Row10;"#},
11345 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11346 indoc! {r#"struct Row;
11347 struct Row1;
11348 struct Row33;
11349 «ˇ
11350 struct Row4;
11351 struct» Row5;
11352 «struct Row6;
11353 ˇ»
11354 struct Row99;
11355 struct Row9;
11356 struct Row10;"#},
11357 base_text,
11358 &mut cx,
11359 );
11360
11361 assert_hunk_revert(
11362 indoc! {r#"ˇstruct Row1.1;
11363 struct Row1;
11364 «ˇstr»uct Row22;
11365
11366 struct ˇRow44;
11367 struct Row5;
11368 struct «Rˇ»ow66;ˇ
11369
11370 «struˇ»ct Row88;
11371 struct Row9;
11372 struct Row1011;ˇ"#},
11373 vec![
11374 DiffHunkStatus::Modified,
11375 DiffHunkStatus::Modified,
11376 DiffHunkStatus::Modified,
11377 DiffHunkStatus::Modified,
11378 DiffHunkStatus::Modified,
11379 DiffHunkStatus::Modified,
11380 ],
11381 indoc! {r#"struct Row;
11382 ˇstruct Row1;
11383 struct Row2;
11384 ˇ
11385 struct Row4;
11386 ˇstruct Row5;
11387 struct Row6;
11388 ˇ
11389 struct Row8;
11390 ˇstruct Row9;
11391 struct Row10;ˇ"#},
11392 base_text,
11393 &mut cx,
11394 );
11395}
11396
11397#[gpui::test]
11398async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11399 init_test(cx, |_| {});
11400 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11401 let base_text = indoc! {r#"struct Row;
11402struct Row1;
11403struct Row2;
11404
11405struct Row4;
11406struct Row5;
11407struct Row6;
11408
11409struct Row8;
11410struct Row9;
11411struct Row10;"#};
11412
11413 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11414 assert_hunk_revert(
11415 indoc! {r#"struct Row;
11416 struct Row2;
11417
11418 ˇstruct Row4;
11419 struct Row5;
11420 struct Row6;
11421 ˇ
11422 struct Row8;
11423 struct Row10;"#},
11424 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11425 indoc! {r#"struct Row;
11426 struct Row2;
11427
11428 ˇstruct Row4;
11429 struct Row5;
11430 struct Row6;
11431 ˇ
11432 struct Row8;
11433 struct Row10;"#},
11434 base_text,
11435 &mut cx,
11436 );
11437 assert_hunk_revert(
11438 indoc! {r#"struct Row;
11439 struct Row2;
11440
11441 «ˇstruct Row4;
11442 struct» Row5;
11443 «struct Row6;
11444 ˇ»
11445 struct Row8;
11446 struct Row10;"#},
11447 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11448 indoc! {r#"struct Row;
11449 struct Row2;
11450
11451 «ˇstruct Row4;
11452 struct» Row5;
11453 «struct Row6;
11454 ˇ»
11455 struct Row8;
11456 struct Row10;"#},
11457 base_text,
11458 &mut cx,
11459 );
11460
11461 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11462 assert_hunk_revert(
11463 indoc! {r#"struct Row;
11464 ˇstruct Row2;
11465
11466 struct Row4;
11467 struct Row5;
11468 struct Row6;
11469
11470 struct Row8;ˇ
11471 struct Row10;"#},
11472 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11473 indoc! {r#"struct Row;
11474 struct Row1;
11475 ˇstruct Row2;
11476
11477 struct Row4;
11478 struct Row5;
11479 struct Row6;
11480
11481 struct Row8;ˇ
11482 struct Row9;
11483 struct Row10;"#},
11484 base_text,
11485 &mut cx,
11486 );
11487 assert_hunk_revert(
11488 indoc! {r#"struct Row;
11489 struct Row2«ˇ;
11490 struct Row4;
11491 struct» Row5;
11492 «struct Row6;
11493
11494 struct Row8;ˇ»
11495 struct Row10;"#},
11496 vec![
11497 DiffHunkStatus::Removed,
11498 DiffHunkStatus::Removed,
11499 DiffHunkStatus::Removed,
11500 ],
11501 indoc! {r#"struct Row;
11502 struct Row1;
11503 struct Row2«ˇ;
11504
11505 struct Row4;
11506 struct» Row5;
11507 «struct Row6;
11508
11509 struct Row8;ˇ»
11510 struct Row9;
11511 struct Row10;"#},
11512 base_text,
11513 &mut cx,
11514 );
11515}
11516
11517#[gpui::test]
11518async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11519 init_test(cx, |_| {});
11520
11521 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11522 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11523 let base_text_3 =
11524 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11525
11526 let text_1 = edit_first_char_of_every_line(base_text_1);
11527 let text_2 = edit_first_char_of_every_line(base_text_2);
11528 let text_3 = edit_first_char_of_every_line(base_text_3);
11529
11530 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11531 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11532 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11533
11534 let multibuffer = cx.new_model(|cx| {
11535 let mut multibuffer = MultiBuffer::new(ReadWrite);
11536 multibuffer.push_excerpts(
11537 buffer_1.clone(),
11538 [
11539 ExcerptRange {
11540 context: Point::new(0, 0)..Point::new(3, 0),
11541 primary: None,
11542 },
11543 ExcerptRange {
11544 context: Point::new(5, 0)..Point::new(7, 0),
11545 primary: None,
11546 },
11547 ExcerptRange {
11548 context: Point::new(9, 0)..Point::new(10, 4),
11549 primary: None,
11550 },
11551 ],
11552 cx,
11553 );
11554 multibuffer.push_excerpts(
11555 buffer_2.clone(),
11556 [
11557 ExcerptRange {
11558 context: Point::new(0, 0)..Point::new(3, 0),
11559 primary: None,
11560 },
11561 ExcerptRange {
11562 context: Point::new(5, 0)..Point::new(7, 0),
11563 primary: None,
11564 },
11565 ExcerptRange {
11566 context: Point::new(9, 0)..Point::new(10, 4),
11567 primary: None,
11568 },
11569 ],
11570 cx,
11571 );
11572 multibuffer.push_excerpts(
11573 buffer_3.clone(),
11574 [
11575 ExcerptRange {
11576 context: Point::new(0, 0)..Point::new(3, 0),
11577 primary: None,
11578 },
11579 ExcerptRange {
11580 context: Point::new(5, 0)..Point::new(7, 0),
11581 primary: None,
11582 },
11583 ExcerptRange {
11584 context: Point::new(9, 0)..Point::new(10, 4),
11585 primary: None,
11586 },
11587 ],
11588 cx,
11589 );
11590 multibuffer
11591 });
11592
11593 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11594 editor.update(cx, |editor, cx| {
11595 for (buffer, diff_base) in [
11596 (buffer_1.clone(), base_text_1),
11597 (buffer_2.clone(), base_text_2),
11598 (buffer_3.clone(), base_text_3),
11599 ] {
11600 let change_set = cx.new_model(|cx| {
11601 BufferChangeSet::new_with_base_text(
11602 diff_base.to_string(),
11603 buffer.read(cx).text_snapshot(),
11604 cx,
11605 )
11606 });
11607 editor.diff_map.add_change_set(change_set, cx)
11608 }
11609 });
11610 cx.executor().run_until_parked();
11611
11612 editor.update(cx, |editor, cx| {
11613 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}");
11614 editor.select_all(&SelectAll, cx);
11615 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11616 });
11617 cx.executor().run_until_parked();
11618
11619 // When all ranges are selected, all buffer hunks are reverted.
11620 editor.update(cx, |editor, cx| {
11621 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");
11622 });
11623 buffer_1.update(cx, |buffer, _| {
11624 assert_eq!(buffer.text(), base_text_1);
11625 });
11626 buffer_2.update(cx, |buffer, _| {
11627 assert_eq!(buffer.text(), base_text_2);
11628 });
11629 buffer_3.update(cx, |buffer, _| {
11630 assert_eq!(buffer.text(), base_text_3);
11631 });
11632
11633 editor.update(cx, |editor, cx| {
11634 editor.undo(&Default::default(), cx);
11635 });
11636
11637 editor.update(cx, |editor, cx| {
11638 editor.change_selections(None, cx, |s| {
11639 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11640 });
11641 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11642 });
11643
11644 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11645 // but not affect buffer_2 and its related excerpts.
11646 editor.update(cx, |editor, cx| {
11647 assert_eq!(
11648 editor.text(cx),
11649 "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}"
11650 );
11651 });
11652 buffer_1.update(cx, |buffer, _| {
11653 assert_eq!(buffer.text(), base_text_1);
11654 });
11655 buffer_2.update(cx, |buffer, _| {
11656 assert_eq!(
11657 buffer.text(),
11658 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11659 );
11660 });
11661 buffer_3.update(cx, |buffer, _| {
11662 assert_eq!(
11663 buffer.text(),
11664 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11665 );
11666 });
11667
11668 fn edit_first_char_of_every_line(text: &str) -> String {
11669 text.split('\n')
11670 .map(|line| format!("X{}", &line[1..]))
11671 .collect::<Vec<_>>()
11672 .join("\n")
11673 }
11674}
11675
11676#[gpui::test]
11677async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11678 init_test(cx, |_| {});
11679
11680 let cols = 4;
11681 let rows = 10;
11682 let sample_text_1 = sample_text(rows, cols, 'a');
11683 assert_eq!(
11684 sample_text_1,
11685 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11686 );
11687 let sample_text_2 = sample_text(rows, cols, 'l');
11688 assert_eq!(
11689 sample_text_2,
11690 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11691 );
11692 let sample_text_3 = sample_text(rows, cols, 'v');
11693 assert_eq!(
11694 sample_text_3,
11695 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11696 );
11697
11698 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11699 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11700 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11701
11702 let multi_buffer = cx.new_model(|cx| {
11703 let mut multibuffer = MultiBuffer::new(ReadWrite);
11704 multibuffer.push_excerpts(
11705 buffer_1.clone(),
11706 [
11707 ExcerptRange {
11708 context: Point::new(0, 0)..Point::new(3, 0),
11709 primary: None,
11710 },
11711 ExcerptRange {
11712 context: Point::new(5, 0)..Point::new(7, 0),
11713 primary: None,
11714 },
11715 ExcerptRange {
11716 context: Point::new(9, 0)..Point::new(10, 4),
11717 primary: None,
11718 },
11719 ],
11720 cx,
11721 );
11722 multibuffer.push_excerpts(
11723 buffer_2.clone(),
11724 [
11725 ExcerptRange {
11726 context: Point::new(0, 0)..Point::new(3, 0),
11727 primary: None,
11728 },
11729 ExcerptRange {
11730 context: Point::new(5, 0)..Point::new(7, 0),
11731 primary: None,
11732 },
11733 ExcerptRange {
11734 context: Point::new(9, 0)..Point::new(10, 4),
11735 primary: None,
11736 },
11737 ],
11738 cx,
11739 );
11740 multibuffer.push_excerpts(
11741 buffer_3.clone(),
11742 [
11743 ExcerptRange {
11744 context: Point::new(0, 0)..Point::new(3, 0),
11745 primary: None,
11746 },
11747 ExcerptRange {
11748 context: Point::new(5, 0)..Point::new(7, 0),
11749 primary: None,
11750 },
11751 ExcerptRange {
11752 context: Point::new(9, 0)..Point::new(10, 4),
11753 primary: None,
11754 },
11755 ],
11756 cx,
11757 );
11758 multibuffer
11759 });
11760
11761 let fs = FakeFs::new(cx.executor());
11762 fs.insert_tree(
11763 "/a",
11764 json!({
11765 "main.rs": sample_text_1,
11766 "other.rs": sample_text_2,
11767 "lib.rs": sample_text_3,
11768 }),
11769 )
11770 .await;
11771 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11772 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11773 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11774 let multi_buffer_editor = cx.new_view(|cx| {
11775 Editor::new(
11776 EditorMode::Full,
11777 multi_buffer,
11778 Some(project.clone()),
11779 true,
11780 cx,
11781 )
11782 });
11783 let multibuffer_item_id = workspace
11784 .update(cx, |workspace, cx| {
11785 assert!(
11786 workspace.active_item(cx).is_none(),
11787 "active item should be None before the first item is added"
11788 );
11789 workspace.add_item_to_active_pane(
11790 Box::new(multi_buffer_editor.clone()),
11791 None,
11792 true,
11793 cx,
11794 );
11795 let active_item = workspace
11796 .active_item(cx)
11797 .expect("should have an active item after adding the multi buffer");
11798 assert!(
11799 !active_item.is_singleton(cx),
11800 "A multi buffer was expected to active after adding"
11801 );
11802 active_item.item_id()
11803 })
11804 .unwrap();
11805 cx.executor().run_until_parked();
11806
11807 multi_buffer_editor.update(cx, |editor, cx| {
11808 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11809 editor.open_excerpts(&OpenExcerpts, cx);
11810 });
11811 cx.executor().run_until_parked();
11812 let first_item_id = workspace
11813 .update(cx, |workspace, cx| {
11814 let active_item = workspace
11815 .active_item(cx)
11816 .expect("should have an active item after navigating into the 1st buffer");
11817 let first_item_id = active_item.item_id();
11818 assert_ne!(
11819 first_item_id, multibuffer_item_id,
11820 "Should navigate into the 1st buffer and activate it"
11821 );
11822 assert!(
11823 active_item.is_singleton(cx),
11824 "New active item should be a singleton buffer"
11825 );
11826 assert_eq!(
11827 active_item
11828 .act_as::<Editor>(cx)
11829 .expect("should have navigated into an editor for the 1st buffer")
11830 .read(cx)
11831 .text(cx),
11832 sample_text_1
11833 );
11834
11835 workspace
11836 .go_back(workspace.active_pane().downgrade(), cx)
11837 .detach_and_log_err(cx);
11838
11839 first_item_id
11840 })
11841 .unwrap();
11842 cx.executor().run_until_parked();
11843 workspace
11844 .update(cx, |workspace, cx| {
11845 let active_item = workspace
11846 .active_item(cx)
11847 .expect("should have an active item after navigating back");
11848 assert_eq!(
11849 active_item.item_id(),
11850 multibuffer_item_id,
11851 "Should navigate back to the multi buffer"
11852 );
11853 assert!(!active_item.is_singleton(cx));
11854 })
11855 .unwrap();
11856
11857 multi_buffer_editor.update(cx, |editor, cx| {
11858 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11859 s.select_ranges(Some(39..40))
11860 });
11861 editor.open_excerpts(&OpenExcerpts, cx);
11862 });
11863 cx.executor().run_until_parked();
11864 let second_item_id = workspace
11865 .update(cx, |workspace, cx| {
11866 let active_item = workspace
11867 .active_item(cx)
11868 .expect("should have an active item after navigating into the 2nd buffer");
11869 let second_item_id = active_item.item_id();
11870 assert_ne!(
11871 second_item_id, multibuffer_item_id,
11872 "Should navigate away from the multibuffer"
11873 );
11874 assert_ne!(
11875 second_item_id, first_item_id,
11876 "Should navigate into the 2nd buffer and activate it"
11877 );
11878 assert!(
11879 active_item.is_singleton(cx),
11880 "New active item should be a singleton buffer"
11881 );
11882 assert_eq!(
11883 active_item
11884 .act_as::<Editor>(cx)
11885 .expect("should have navigated into an editor")
11886 .read(cx)
11887 .text(cx),
11888 sample_text_2
11889 );
11890
11891 workspace
11892 .go_back(workspace.active_pane().downgrade(), cx)
11893 .detach_and_log_err(cx);
11894
11895 second_item_id
11896 })
11897 .unwrap();
11898 cx.executor().run_until_parked();
11899 workspace
11900 .update(cx, |workspace, cx| {
11901 let active_item = workspace
11902 .active_item(cx)
11903 .expect("should have an active item after navigating back from the 2nd buffer");
11904 assert_eq!(
11905 active_item.item_id(),
11906 multibuffer_item_id,
11907 "Should navigate back from the 2nd buffer to the multi buffer"
11908 );
11909 assert!(!active_item.is_singleton(cx));
11910 })
11911 .unwrap();
11912
11913 multi_buffer_editor.update(cx, |editor, cx| {
11914 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11915 s.select_ranges(Some(70..70))
11916 });
11917 editor.open_excerpts(&OpenExcerpts, cx);
11918 });
11919 cx.executor().run_until_parked();
11920 workspace
11921 .update(cx, |workspace, cx| {
11922 let active_item = workspace
11923 .active_item(cx)
11924 .expect("should have an active item after navigating into the 3rd buffer");
11925 let third_item_id = active_item.item_id();
11926 assert_ne!(
11927 third_item_id, multibuffer_item_id,
11928 "Should navigate into the 3rd buffer and activate it"
11929 );
11930 assert_ne!(third_item_id, first_item_id);
11931 assert_ne!(third_item_id, second_item_id);
11932 assert!(
11933 active_item.is_singleton(cx),
11934 "New active item should be a singleton buffer"
11935 );
11936 assert_eq!(
11937 active_item
11938 .act_as::<Editor>(cx)
11939 .expect("should have navigated into an editor")
11940 .read(cx)
11941 .text(cx),
11942 sample_text_3
11943 );
11944
11945 workspace
11946 .go_back(workspace.active_pane().downgrade(), cx)
11947 .detach_and_log_err(cx);
11948 })
11949 .unwrap();
11950 cx.executor().run_until_parked();
11951 workspace
11952 .update(cx, |workspace, cx| {
11953 let active_item = workspace
11954 .active_item(cx)
11955 .expect("should have an active item after navigating back from the 3rd buffer");
11956 assert_eq!(
11957 active_item.item_id(),
11958 multibuffer_item_id,
11959 "Should navigate back from the 3rd buffer to the multi buffer"
11960 );
11961 assert!(!active_item.is_singleton(cx));
11962 })
11963 .unwrap();
11964}
11965
11966#[gpui::test]
11967async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11968 init_test(cx, |_| {});
11969
11970 let mut cx = EditorTestContext::new(cx).await;
11971
11972 let diff_base = r#"
11973 use some::mod;
11974
11975 const A: u32 = 42;
11976
11977 fn main() {
11978 println!("hello");
11979
11980 println!("world");
11981 }
11982 "#
11983 .unindent();
11984
11985 cx.set_state(
11986 &r#"
11987 use some::modified;
11988
11989 ˇ
11990 fn main() {
11991 println!("hello there");
11992
11993 println!("around the");
11994 println!("world");
11995 }
11996 "#
11997 .unindent(),
11998 );
11999
12000 cx.set_diff_base(&diff_base);
12001 executor.run_until_parked();
12002
12003 cx.update_editor(|editor, cx| {
12004 editor.go_to_next_hunk(&GoToHunk, cx);
12005 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12006 });
12007 executor.run_until_parked();
12008 cx.assert_state_with_diff(
12009 r#"
12010 use some::modified;
12011
12012
12013 fn main() {
12014 - println!("hello");
12015 + ˇ println!("hello there");
12016
12017 println!("around the");
12018 println!("world");
12019 }
12020 "#
12021 .unindent(),
12022 );
12023
12024 cx.update_editor(|editor, cx| {
12025 for _ in 0..3 {
12026 editor.go_to_next_hunk(&GoToHunk, cx);
12027 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12028 }
12029 });
12030 executor.run_until_parked();
12031 cx.assert_state_with_diff(
12032 r#"
12033 - use some::mod;
12034 + use some::modified;
12035
12036 - const A: u32 = 42;
12037 ˇ
12038 fn main() {
12039 - println!("hello");
12040 + println!("hello there");
12041
12042 + println!("around the");
12043 println!("world");
12044 }
12045 "#
12046 .unindent(),
12047 );
12048
12049 cx.update_editor(|editor, cx| {
12050 editor.cancel(&Cancel, cx);
12051 });
12052
12053 cx.assert_state_with_diff(
12054 r#"
12055 use some::modified;
12056
12057 ˇ
12058 fn main() {
12059 println!("hello there");
12060
12061 println!("around the");
12062 println!("world");
12063 }
12064 "#
12065 .unindent(),
12066 );
12067}
12068
12069#[gpui::test]
12070async fn test_diff_base_change_with_expanded_diff_hunks(
12071 executor: BackgroundExecutor,
12072 cx: &mut gpui::TestAppContext,
12073) {
12074 init_test(cx, |_| {});
12075
12076 let mut cx = EditorTestContext::new(cx).await;
12077
12078 let diff_base = r#"
12079 use some::mod1;
12080 use some::mod2;
12081
12082 const A: u32 = 42;
12083 const B: u32 = 42;
12084 const C: u32 = 42;
12085
12086 fn main() {
12087 println!("hello");
12088
12089 println!("world");
12090 }
12091 "#
12092 .unindent();
12093
12094 cx.set_state(
12095 &r#"
12096 use some::mod2;
12097
12098 const A: u32 = 42;
12099 const C: u32 = 42;
12100
12101 fn main(ˇ) {
12102 //println!("hello");
12103
12104 println!("world");
12105 //
12106 //
12107 }
12108 "#
12109 .unindent(),
12110 );
12111
12112 cx.set_diff_base(&diff_base);
12113 executor.run_until_parked();
12114
12115 cx.update_editor(|editor, cx| {
12116 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12117 });
12118 executor.run_until_parked();
12119 cx.assert_state_with_diff(
12120 r#"
12121 - use some::mod1;
12122 use some::mod2;
12123
12124 const A: u32 = 42;
12125 - const B: u32 = 42;
12126 const C: u32 = 42;
12127
12128 fn main(ˇ) {
12129 - println!("hello");
12130 + //println!("hello");
12131
12132 println!("world");
12133 + //
12134 + //
12135 }
12136 "#
12137 .unindent(),
12138 );
12139
12140 cx.set_diff_base("new diff base!");
12141 executor.run_until_parked();
12142 cx.assert_state_with_diff(
12143 r#"
12144 use some::mod2;
12145
12146 const A: u32 = 42;
12147 const C: u32 = 42;
12148
12149 fn main(ˇ) {
12150 //println!("hello");
12151
12152 println!("world");
12153 //
12154 //
12155 }
12156 "#
12157 .unindent(),
12158 );
12159
12160 cx.update_editor(|editor, cx| {
12161 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12162 });
12163 executor.run_until_parked();
12164 cx.assert_state_with_diff(
12165 r#"
12166 - new diff base!
12167 + use some::mod2;
12168 +
12169 + const A: u32 = 42;
12170 + const C: u32 = 42;
12171 +
12172 + fn main(ˇ) {
12173 + //println!("hello");
12174 +
12175 + println!("world");
12176 + //
12177 + //
12178 + }
12179 "#
12180 .unindent(),
12181 );
12182}
12183
12184#[gpui::test]
12185async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12186 init_test(cx, |_| {});
12187
12188 let mut cx = EditorTestContext::new(cx).await;
12189
12190 let diff_base = r#"
12191 use some::mod1;
12192 use some::mod2;
12193
12194 const A: u32 = 42;
12195 const B: u32 = 42;
12196 const C: u32 = 42;
12197
12198 fn main() {
12199 println!("hello");
12200
12201 println!("world");
12202 }
12203
12204 fn another() {
12205 println!("another");
12206 }
12207
12208 fn another2() {
12209 println!("another2");
12210 }
12211 "#
12212 .unindent();
12213
12214 cx.set_state(
12215 &r#"
12216 «use some::mod2;
12217
12218 const A: u32 = 42;
12219 const C: u32 = 42;
12220
12221 fn main() {
12222 //println!("hello");
12223
12224 println!("world");
12225 //
12226 //ˇ»
12227 }
12228
12229 fn another() {
12230 println!("another");
12231 println!("another");
12232 }
12233
12234 println!("another2");
12235 }
12236 "#
12237 .unindent(),
12238 );
12239
12240 cx.set_diff_base(&diff_base);
12241 executor.run_until_parked();
12242
12243 cx.update_editor(|editor, cx| {
12244 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12245 });
12246 executor.run_until_parked();
12247
12248 cx.assert_state_with_diff(
12249 r#"
12250 - use some::mod1;
12251 «use some::mod2;
12252
12253 const A: u32 = 42;
12254 - const B: u32 = 42;
12255 const C: u32 = 42;
12256
12257 fn main() {
12258 - println!("hello");
12259 + //println!("hello");
12260
12261 println!("world");
12262 + //
12263 + //ˇ»
12264 }
12265
12266 fn another() {
12267 println!("another");
12268 + println!("another");
12269 }
12270
12271 - fn another2() {
12272 println!("another2");
12273 }
12274 "#
12275 .unindent(),
12276 );
12277
12278 // Fold across some of the diff hunks. They should no longer appear expanded.
12279 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12280 cx.executor().run_until_parked();
12281
12282 // Hunks are not shown if their position is within a fold
12283 cx.assert_state_with_diff(
12284 r#"
12285 «use some::mod2;
12286
12287 const A: u32 = 42;
12288 const C: u32 = 42;
12289
12290 fn main() {
12291 //println!("hello");
12292
12293 println!("world");
12294 //
12295 //ˇ»
12296 }
12297
12298 fn another() {
12299 println!("another");
12300 + println!("another");
12301 }
12302
12303 - fn another2() {
12304 println!("another2");
12305 }
12306 "#
12307 .unindent(),
12308 );
12309
12310 cx.update_editor(|editor, cx| {
12311 editor.select_all(&SelectAll, cx);
12312 editor.unfold_lines(&UnfoldLines, cx);
12313 });
12314 cx.executor().run_until_parked();
12315
12316 // The deletions reappear when unfolding.
12317 cx.assert_state_with_diff(
12318 r#"
12319 - use some::mod1;
12320 «use some::mod2;
12321
12322 const A: u32 = 42;
12323 - const B: u32 = 42;
12324 const C: u32 = 42;
12325
12326 fn main() {
12327 - println!("hello");
12328 + //println!("hello");
12329
12330 println!("world");
12331 + //
12332 + //
12333 }
12334
12335 fn another() {
12336 println!("another");
12337 + println!("another");
12338 }
12339
12340 - fn another2() {
12341 println!("another2");
12342 }
12343 ˇ»"#
12344 .unindent(),
12345 );
12346}
12347
12348#[gpui::test]
12349async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12350 init_test(cx, |_| {});
12351
12352 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12353 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12354 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12355 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12356 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12357 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12358
12359 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12360 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12361 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12362
12363 let multi_buffer = cx.new_model(|cx| {
12364 let mut multibuffer = MultiBuffer::new(ReadWrite);
12365 multibuffer.push_excerpts(
12366 buffer_1.clone(),
12367 [
12368 ExcerptRange {
12369 context: Point::new(0, 0)..Point::new(3, 0),
12370 primary: None,
12371 },
12372 ExcerptRange {
12373 context: Point::new(5, 0)..Point::new(7, 0),
12374 primary: None,
12375 },
12376 ExcerptRange {
12377 context: Point::new(9, 0)..Point::new(10, 3),
12378 primary: None,
12379 },
12380 ],
12381 cx,
12382 );
12383 multibuffer.push_excerpts(
12384 buffer_2.clone(),
12385 [
12386 ExcerptRange {
12387 context: Point::new(0, 0)..Point::new(3, 0),
12388 primary: None,
12389 },
12390 ExcerptRange {
12391 context: Point::new(5, 0)..Point::new(7, 0),
12392 primary: None,
12393 },
12394 ExcerptRange {
12395 context: Point::new(9, 0)..Point::new(10, 3),
12396 primary: None,
12397 },
12398 ],
12399 cx,
12400 );
12401 multibuffer.push_excerpts(
12402 buffer_3.clone(),
12403 [
12404 ExcerptRange {
12405 context: Point::new(0, 0)..Point::new(3, 0),
12406 primary: None,
12407 },
12408 ExcerptRange {
12409 context: Point::new(5, 0)..Point::new(7, 0),
12410 primary: None,
12411 },
12412 ExcerptRange {
12413 context: Point::new(9, 0)..Point::new(10, 3),
12414 primary: None,
12415 },
12416 ],
12417 cx,
12418 );
12419 multibuffer
12420 });
12421
12422 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12423 editor
12424 .update(cx, |editor, cx| {
12425 for (buffer, diff_base) in [
12426 (buffer_1.clone(), file_1_old),
12427 (buffer_2.clone(), file_2_old),
12428 (buffer_3.clone(), file_3_old),
12429 ] {
12430 let change_set = cx.new_model(|cx| {
12431 BufferChangeSet::new_with_base_text(
12432 diff_base.to_string(),
12433 buffer.read(cx).text_snapshot(),
12434 cx,
12435 )
12436 });
12437 editor.diff_map.add_change_set(change_set, cx)
12438 }
12439 })
12440 .unwrap();
12441
12442 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12443 cx.run_until_parked();
12444
12445 cx.assert_editor_state(
12446 &"
12447 ˇaaa
12448 ccc
12449 ddd
12450
12451 ggg
12452 hhh
12453
12454
12455 lll
12456 mmm
12457 NNN
12458
12459 qqq
12460 rrr
12461
12462 uuu
12463 111
12464 222
12465 333
12466
12467 666
12468 777
12469
12470 000
12471 !!!"
12472 .unindent(),
12473 );
12474
12475 cx.update_editor(|editor, cx| {
12476 editor.select_all(&SelectAll, cx);
12477 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12478 });
12479 cx.executor().run_until_parked();
12480
12481 cx.assert_state_with_diff(
12482 "
12483 «aaa
12484 - bbb
12485 ccc
12486 ddd
12487
12488 ggg
12489 hhh
12490
12491
12492 lll
12493 mmm
12494 - nnn
12495 + NNN
12496
12497 qqq
12498 rrr
12499
12500 uuu
12501 111
12502 222
12503 333
12504
12505 + 666
12506 777
12507
12508 000
12509 !!!ˇ»"
12510 .unindent(),
12511 );
12512}
12513
12514#[gpui::test]
12515async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12516 init_test(cx, |_| {});
12517
12518 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12519 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12520
12521 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12522 let multi_buffer = cx.new_model(|cx| {
12523 let mut multibuffer = MultiBuffer::new(ReadWrite);
12524 multibuffer.push_excerpts(
12525 buffer.clone(),
12526 [
12527 ExcerptRange {
12528 context: Point::new(0, 0)..Point::new(2, 0),
12529 primary: None,
12530 },
12531 ExcerptRange {
12532 context: Point::new(5, 0)..Point::new(7, 0),
12533 primary: None,
12534 },
12535 ],
12536 cx,
12537 );
12538 multibuffer
12539 });
12540
12541 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12542 editor
12543 .update(cx, |editor, cx| {
12544 let buffer = buffer.read(cx).text_snapshot();
12545 let change_set = cx
12546 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12547 editor.diff_map.add_change_set(change_set, cx)
12548 })
12549 .unwrap();
12550
12551 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12552 cx.run_until_parked();
12553
12554 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12555 cx.executor().run_until_parked();
12556
12557 cx.assert_state_with_diff(
12558 "
12559 ˇaaa
12560 - bbb
12561 + BBB
12562
12563 - ddd
12564 - eee
12565 + EEE
12566 fff
12567 "
12568 .unindent(),
12569 );
12570}
12571
12572#[gpui::test]
12573async fn test_edits_around_expanded_insertion_hunks(
12574 executor: BackgroundExecutor,
12575 cx: &mut gpui::TestAppContext,
12576) {
12577 init_test(cx, |_| {});
12578
12579 let mut cx = EditorTestContext::new(cx).await;
12580
12581 let diff_base = r#"
12582 use some::mod1;
12583 use some::mod2;
12584
12585 const A: u32 = 42;
12586
12587 fn main() {
12588 println!("hello");
12589
12590 println!("world");
12591 }
12592 "#
12593 .unindent();
12594 executor.run_until_parked();
12595 cx.set_state(
12596 &r#"
12597 use some::mod1;
12598 use some::mod2;
12599
12600 const A: u32 = 42;
12601 const B: u32 = 42;
12602 const C: u32 = 42;
12603 ˇ
12604
12605 fn main() {
12606 println!("hello");
12607
12608 println!("world");
12609 }
12610 "#
12611 .unindent(),
12612 );
12613
12614 cx.set_diff_base(&diff_base);
12615 executor.run_until_parked();
12616
12617 cx.update_editor(|editor, cx| {
12618 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12619 });
12620 executor.run_until_parked();
12621
12622 cx.assert_state_with_diff(
12623 r#"
12624 use some::mod1;
12625 use some::mod2;
12626
12627 const A: u32 = 42;
12628 + const B: u32 = 42;
12629 + const C: u32 = 42;
12630 + ˇ
12631
12632 fn main() {
12633 println!("hello");
12634
12635 println!("world");
12636 }
12637 "#
12638 .unindent(),
12639 );
12640
12641 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12642 executor.run_until_parked();
12643
12644 cx.assert_state_with_diff(
12645 r#"
12646 use some::mod1;
12647 use some::mod2;
12648
12649 const A: u32 = 42;
12650 + const B: u32 = 42;
12651 + const C: u32 = 42;
12652 + const D: u32 = 42;
12653 + ˇ
12654
12655 fn main() {
12656 println!("hello");
12657
12658 println!("world");
12659 }
12660 "#
12661 .unindent(),
12662 );
12663
12664 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12665 executor.run_until_parked();
12666
12667 cx.assert_state_with_diff(
12668 r#"
12669 use some::mod1;
12670 use some::mod2;
12671
12672 const A: u32 = 42;
12673 + const B: u32 = 42;
12674 + const C: u32 = 42;
12675 + const D: u32 = 42;
12676 + const E: u32 = 42;
12677 + ˇ
12678
12679 fn main() {
12680 println!("hello");
12681
12682 println!("world");
12683 }
12684 "#
12685 .unindent(),
12686 );
12687
12688 cx.update_editor(|editor, cx| {
12689 editor.delete_line(&DeleteLine, cx);
12690 });
12691 executor.run_until_parked();
12692
12693 cx.assert_state_with_diff(
12694 r#"
12695 use some::mod1;
12696 use some::mod2;
12697
12698 const A: u32 = 42;
12699 + const B: u32 = 42;
12700 + const C: u32 = 42;
12701 + const D: u32 = 42;
12702 + const E: u32 = 42;
12703 ˇ
12704 fn main() {
12705 println!("hello");
12706
12707 println!("world");
12708 }
12709 "#
12710 .unindent(),
12711 );
12712
12713 cx.update_editor(|editor, cx| {
12714 editor.move_up(&MoveUp, cx);
12715 editor.delete_line(&DeleteLine, cx);
12716 editor.move_up(&MoveUp, cx);
12717 editor.delete_line(&DeleteLine, cx);
12718 editor.move_up(&MoveUp, cx);
12719 editor.delete_line(&DeleteLine, cx);
12720 });
12721 executor.run_until_parked();
12722 cx.assert_state_with_diff(
12723 r#"
12724 use some::mod1;
12725 use some::mod2;
12726
12727 const A: u32 = 42;
12728 + const B: u32 = 42;
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.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12741 editor.delete_line(&DeleteLine, cx);
12742 });
12743 executor.run_until_parked();
12744 cx.assert_state_with_diff(
12745 r#"
12746 use some::mod1;
12747 - use some::mod2;
12748 -
12749 - const A: u32 = 42;
12750 ˇ
12751 fn main() {
12752 println!("hello");
12753
12754 println!("world");
12755 }
12756 "#
12757 .unindent(),
12758 );
12759}
12760
12761#[gpui::test]
12762async fn test_edits_around_expanded_deletion_hunks(
12763 executor: BackgroundExecutor,
12764 cx: &mut gpui::TestAppContext,
12765) {
12766 init_test(cx, |_| {});
12767
12768 let mut cx = EditorTestContext::new(cx).await;
12769
12770 let diff_base = r#"
12771 use some::mod1;
12772 use some::mod2;
12773
12774 const A: u32 = 42;
12775 const B: u32 = 42;
12776 const C: u32 = 42;
12777
12778
12779 fn main() {
12780 println!("hello");
12781
12782 println!("world");
12783 }
12784 "#
12785 .unindent();
12786 executor.run_until_parked();
12787 cx.set_state(
12788 &r#"
12789 use some::mod1;
12790 use some::mod2;
12791
12792 ˇconst B: u32 = 42;
12793 const C: u32 = 42;
12794
12795
12796 fn main() {
12797 println!("hello");
12798
12799 println!("world");
12800 }
12801 "#
12802 .unindent(),
12803 );
12804
12805 cx.set_diff_base(&diff_base);
12806 executor.run_until_parked();
12807
12808 cx.update_editor(|editor, cx| {
12809 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12810 });
12811 executor.run_until_parked();
12812
12813 cx.assert_state_with_diff(
12814 r#"
12815 use some::mod1;
12816 use some::mod2;
12817
12818 - const A: u32 = 42;
12819 ˇconst B: u32 = 42;
12820 const C: u32 = 42;
12821
12822
12823 fn main() {
12824 println!("hello");
12825
12826 println!("world");
12827 }
12828 "#
12829 .unindent(),
12830 );
12831
12832 cx.update_editor(|editor, cx| {
12833 editor.delete_line(&DeleteLine, cx);
12834 });
12835 executor.run_until_parked();
12836 cx.assert_state_with_diff(
12837 r#"
12838 use some::mod1;
12839 use some::mod2;
12840
12841 - const A: u32 = 42;
12842 - const B: u32 = 42;
12843 ˇconst C: u32 = 42;
12844
12845
12846 fn main() {
12847 println!("hello");
12848
12849 println!("world");
12850 }
12851 "#
12852 .unindent(),
12853 );
12854
12855 cx.update_editor(|editor, cx| {
12856 editor.delete_line(&DeleteLine, cx);
12857 });
12858 executor.run_until_parked();
12859 cx.assert_state_with_diff(
12860 r#"
12861 use some::mod1;
12862 use some::mod2;
12863
12864 - const A: u32 = 42;
12865 - const B: u32 = 42;
12866 - const C: u32 = 42;
12867 ˇ
12868
12869 fn main() {
12870 println!("hello");
12871
12872 println!("world");
12873 }
12874 "#
12875 .unindent(),
12876 );
12877
12878 cx.update_editor(|editor, cx| {
12879 editor.handle_input("replacement", cx);
12880 });
12881 executor.run_until_parked();
12882 cx.assert_state_with_diff(
12883 r#"
12884 use some::mod1;
12885 use some::mod2;
12886
12887 - const A: u32 = 42;
12888 - const B: u32 = 42;
12889 - const C: u32 = 42;
12890 -
12891 + replacementˇ
12892
12893 fn main() {
12894 println!("hello");
12895
12896 println!("world");
12897 }
12898 "#
12899 .unindent(),
12900 );
12901}
12902
12903#[gpui::test]
12904async fn test_edit_after_expanded_modification_hunk(
12905 executor: BackgroundExecutor,
12906 cx: &mut gpui::TestAppContext,
12907) {
12908 init_test(cx, |_| {});
12909
12910 let mut cx = EditorTestContext::new(cx).await;
12911
12912 let diff_base = r#"
12913 use some::mod1;
12914 use some::mod2;
12915
12916 const A: u32 = 42;
12917 const B: u32 = 42;
12918 const C: u32 = 42;
12919 const D: u32 = 42;
12920
12921
12922 fn main() {
12923 println!("hello");
12924
12925 println!("world");
12926 }"#
12927 .unindent();
12928
12929 cx.set_state(
12930 &r#"
12931 use some::mod1;
12932 use some::mod2;
12933
12934 const A: u32 = 42;
12935 const B: u32 = 42;
12936 const C: u32 = 43ˇ
12937 const D: u32 = 42;
12938
12939
12940 fn main() {
12941 println!("hello");
12942
12943 println!("world");
12944 }"#
12945 .unindent(),
12946 );
12947
12948 cx.set_diff_base(&diff_base);
12949 executor.run_until_parked();
12950 cx.update_editor(|editor, cx| {
12951 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12952 });
12953 executor.run_until_parked();
12954
12955 cx.assert_state_with_diff(
12956 r#"
12957 use some::mod1;
12958 use some::mod2;
12959
12960 const A: u32 = 42;
12961 const B: u32 = 42;
12962 - const C: u32 = 42;
12963 + const C: u32 = 43ˇ
12964 const D: u32 = 42;
12965
12966
12967 fn main() {
12968 println!("hello");
12969
12970 println!("world");
12971 }"#
12972 .unindent(),
12973 );
12974
12975 cx.update_editor(|editor, cx| {
12976 editor.handle_input("\nnew_line\n", cx);
12977 });
12978 executor.run_until_parked();
12979
12980 cx.assert_state_with_diff(
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 = 42;
12988 + const C: u32 = 43
12989 + new_line
12990 + ˇ
12991 const D: u32 = 42;
12992
12993
12994 fn main() {
12995 println!("hello");
12996
12997 println!("world");
12998 }"#
12999 .unindent(),
13000 );
13001}
13002
13003async fn setup_indent_guides_editor(
13004 text: &str,
13005 cx: &mut gpui::TestAppContext,
13006) -> (BufferId, EditorTestContext) {
13007 init_test(cx, |_| {});
13008
13009 let mut cx = EditorTestContext::new(cx).await;
13010
13011 let buffer_id = cx.update_editor(|editor, cx| {
13012 editor.set_text(text, cx);
13013 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13014
13015 buffer_ids[0]
13016 });
13017
13018 (buffer_id, cx)
13019}
13020
13021fn assert_indent_guides(
13022 range: Range<u32>,
13023 expected: Vec<IndentGuide>,
13024 active_indices: Option<Vec<usize>>,
13025 cx: &mut EditorTestContext,
13026) {
13027 let indent_guides = cx.update_editor(|editor, cx| {
13028 let snapshot = editor.snapshot(cx).display_snapshot;
13029 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13030 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13031 true,
13032 &snapshot,
13033 cx,
13034 );
13035
13036 indent_guides.sort_by(|a, b| {
13037 a.depth.cmp(&b.depth).then(
13038 a.start_row
13039 .cmp(&b.start_row)
13040 .then(a.end_row.cmp(&b.end_row)),
13041 )
13042 });
13043 indent_guides
13044 });
13045
13046 if let Some(expected) = active_indices {
13047 let active_indices = cx.update_editor(|editor, cx| {
13048 let snapshot = editor.snapshot(cx).display_snapshot;
13049 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13050 });
13051
13052 assert_eq!(
13053 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13054 expected,
13055 "Active indent guide indices do not match"
13056 );
13057 }
13058
13059 let expected: Vec<_> = expected
13060 .into_iter()
13061 .map(|guide| MultiBufferIndentGuide {
13062 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13063 buffer: guide,
13064 })
13065 .collect();
13066
13067 assert_eq!(indent_guides, expected, "Indent guides do not match");
13068}
13069
13070fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13071 IndentGuide {
13072 buffer_id,
13073 start_row,
13074 end_row,
13075 depth,
13076 tab_size: 4,
13077 settings: IndentGuideSettings {
13078 enabled: true,
13079 line_width: 1,
13080 active_line_width: 1,
13081 ..Default::default()
13082 },
13083 }
13084}
13085
13086#[gpui::test]
13087async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13088 let (buffer_id, mut cx) = setup_indent_guides_editor(
13089 &"
13090 fn main() {
13091 let a = 1;
13092 }"
13093 .unindent(),
13094 cx,
13095 )
13096 .await;
13097
13098 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13099}
13100
13101#[gpui::test]
13102async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13103 let (buffer_id, mut cx) = setup_indent_guides_editor(
13104 &"
13105 fn main() {
13106 let a = 1;
13107 let b = 2;
13108 }"
13109 .unindent(),
13110 cx,
13111 )
13112 .await;
13113
13114 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13115}
13116
13117#[gpui::test]
13118async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13119 let (buffer_id, mut cx) = setup_indent_guides_editor(
13120 &"
13121 fn main() {
13122 let a = 1;
13123 if a == 3 {
13124 let b = 2;
13125 } else {
13126 let c = 3;
13127 }
13128 }"
13129 .unindent(),
13130 cx,
13131 )
13132 .await;
13133
13134 assert_indent_guides(
13135 0..8,
13136 vec![
13137 indent_guide(buffer_id, 1, 6, 0),
13138 indent_guide(buffer_id, 3, 3, 1),
13139 indent_guide(buffer_id, 5, 5, 1),
13140 ],
13141 None,
13142 &mut cx,
13143 );
13144}
13145
13146#[gpui::test]
13147async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13148 let (buffer_id, mut cx) = setup_indent_guides_editor(
13149 &"
13150 fn main() {
13151 let a = 1;
13152 let b = 2;
13153 let c = 3;
13154 }"
13155 .unindent(),
13156 cx,
13157 )
13158 .await;
13159
13160 assert_indent_guides(
13161 0..5,
13162 vec![
13163 indent_guide(buffer_id, 1, 3, 0),
13164 indent_guide(buffer_id, 2, 2, 1),
13165 ],
13166 None,
13167 &mut cx,
13168 );
13169}
13170
13171#[gpui::test]
13172async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13173 let (buffer_id, mut cx) = setup_indent_guides_editor(
13174 &"
13175 fn main() {
13176 let a = 1;
13177
13178 let c = 3;
13179 }"
13180 .unindent(),
13181 cx,
13182 )
13183 .await;
13184
13185 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13186}
13187
13188#[gpui::test]
13189async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13190 let (buffer_id, mut cx) = setup_indent_guides_editor(
13191 &"
13192 fn main() {
13193 let a = 1;
13194
13195 let c = 3;
13196
13197 if a == 3 {
13198 let b = 2;
13199 } else {
13200 let c = 3;
13201 }
13202 }"
13203 .unindent(),
13204 cx,
13205 )
13206 .await;
13207
13208 assert_indent_guides(
13209 0..11,
13210 vec![
13211 indent_guide(buffer_id, 1, 9, 0),
13212 indent_guide(buffer_id, 6, 6, 1),
13213 indent_guide(buffer_id, 8, 8, 1),
13214 ],
13215 None,
13216 &mut cx,
13217 );
13218}
13219
13220#[gpui::test]
13221async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13222 let (buffer_id, mut cx) = setup_indent_guides_editor(
13223 &"
13224 fn main() {
13225 let a = 1;
13226
13227 let c = 3;
13228
13229 if a == 3 {
13230 let b = 2;
13231 } else {
13232 let c = 3;
13233 }
13234 }"
13235 .unindent(),
13236 cx,
13237 )
13238 .await;
13239
13240 assert_indent_guides(
13241 1..11,
13242 vec![
13243 indent_guide(buffer_id, 1, 9, 0),
13244 indent_guide(buffer_id, 6, 6, 1),
13245 indent_guide(buffer_id, 8, 8, 1),
13246 ],
13247 None,
13248 &mut cx,
13249 );
13250}
13251
13252#[gpui::test]
13253async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13254 let (buffer_id, mut cx) = setup_indent_guides_editor(
13255 &"
13256 fn main() {
13257 let a = 1;
13258
13259 let c = 3;
13260
13261 if a == 3 {
13262 let b = 2;
13263 } else {
13264 let c = 3;
13265 }
13266 }"
13267 .unindent(),
13268 cx,
13269 )
13270 .await;
13271
13272 assert_indent_guides(
13273 1..10,
13274 vec![
13275 indent_guide(buffer_id, 1, 9, 0),
13276 indent_guide(buffer_id, 6, 6, 1),
13277 indent_guide(buffer_id, 8, 8, 1),
13278 ],
13279 None,
13280 &mut cx,
13281 );
13282}
13283
13284#[gpui::test]
13285async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13286 let (buffer_id, mut cx) = setup_indent_guides_editor(
13287 &"
13288 block1
13289 block2
13290 block3
13291 block4
13292 block2
13293 block1
13294 block1"
13295 .unindent(),
13296 cx,
13297 )
13298 .await;
13299
13300 assert_indent_guides(
13301 1..10,
13302 vec![
13303 indent_guide(buffer_id, 1, 4, 0),
13304 indent_guide(buffer_id, 2, 3, 1),
13305 indent_guide(buffer_id, 3, 3, 2),
13306 ],
13307 None,
13308 &mut cx,
13309 );
13310}
13311
13312#[gpui::test]
13313async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13314 let (buffer_id, mut cx) = setup_indent_guides_editor(
13315 &"
13316 block1
13317 block2
13318 block3
13319
13320 block1
13321 block1"
13322 .unindent(),
13323 cx,
13324 )
13325 .await;
13326
13327 assert_indent_guides(
13328 0..6,
13329 vec![
13330 indent_guide(buffer_id, 1, 2, 0),
13331 indent_guide(buffer_id, 2, 2, 1),
13332 ],
13333 None,
13334 &mut cx,
13335 );
13336}
13337
13338#[gpui::test]
13339async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13340 let (buffer_id, mut cx) = setup_indent_guides_editor(
13341 &"
13342 block1
13343
13344
13345
13346 block2
13347 "
13348 .unindent(),
13349 cx,
13350 )
13351 .await;
13352
13353 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13354}
13355
13356#[gpui::test]
13357async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13358 let (buffer_id, mut cx) = setup_indent_guides_editor(
13359 &"
13360 def a:
13361 \tb = 3
13362 \tif True:
13363 \t\tc = 4
13364 \t\td = 5
13365 \tprint(b)
13366 "
13367 .unindent(),
13368 cx,
13369 )
13370 .await;
13371
13372 assert_indent_guides(
13373 0..6,
13374 vec![
13375 indent_guide(buffer_id, 1, 6, 0),
13376 indent_guide(buffer_id, 3, 4, 1),
13377 ],
13378 None,
13379 &mut cx,
13380 );
13381}
13382
13383#[gpui::test]
13384async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13385 let (buffer_id, mut cx) = setup_indent_guides_editor(
13386 &"
13387 fn main() {
13388 let a = 1;
13389 }"
13390 .unindent(),
13391 cx,
13392 )
13393 .await;
13394
13395 cx.update_editor(|editor, cx| {
13396 editor.change_selections(None, cx, |s| {
13397 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13398 });
13399 });
13400
13401 assert_indent_guides(
13402 0..3,
13403 vec![indent_guide(buffer_id, 1, 1, 0)],
13404 Some(vec![0]),
13405 &mut cx,
13406 );
13407}
13408
13409#[gpui::test]
13410async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13411 let (buffer_id, mut cx) = setup_indent_guides_editor(
13412 &"
13413 fn main() {
13414 if 1 == 2 {
13415 let a = 1;
13416 }
13417 }"
13418 .unindent(),
13419 cx,
13420 )
13421 .await;
13422
13423 cx.update_editor(|editor, cx| {
13424 editor.change_selections(None, cx, |s| {
13425 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13426 });
13427 });
13428
13429 assert_indent_guides(
13430 0..4,
13431 vec![
13432 indent_guide(buffer_id, 1, 3, 0),
13433 indent_guide(buffer_id, 2, 2, 1),
13434 ],
13435 Some(vec![1]),
13436 &mut cx,
13437 );
13438
13439 cx.update_editor(|editor, cx| {
13440 editor.change_selections(None, cx, |s| {
13441 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13442 });
13443 });
13444
13445 assert_indent_guides(
13446 0..4,
13447 vec![
13448 indent_guide(buffer_id, 1, 3, 0),
13449 indent_guide(buffer_id, 2, 2, 1),
13450 ],
13451 Some(vec![1]),
13452 &mut cx,
13453 );
13454
13455 cx.update_editor(|editor, cx| {
13456 editor.change_selections(None, cx, |s| {
13457 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13458 });
13459 });
13460
13461 assert_indent_guides(
13462 0..4,
13463 vec![
13464 indent_guide(buffer_id, 1, 3, 0),
13465 indent_guide(buffer_id, 2, 2, 1),
13466 ],
13467 Some(vec![0]),
13468 &mut cx,
13469 );
13470}
13471
13472#[gpui::test]
13473async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13474 let (buffer_id, mut cx) = setup_indent_guides_editor(
13475 &"
13476 fn main() {
13477 let a = 1;
13478
13479 let b = 2;
13480 }"
13481 .unindent(),
13482 cx,
13483 )
13484 .await;
13485
13486 cx.update_editor(|editor, cx| {
13487 editor.change_selections(None, cx, |s| {
13488 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13489 });
13490 });
13491
13492 assert_indent_guides(
13493 0..5,
13494 vec![indent_guide(buffer_id, 1, 3, 0)],
13495 Some(vec![0]),
13496 &mut cx,
13497 );
13498}
13499
13500#[gpui::test]
13501async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13502 let (buffer_id, mut cx) = setup_indent_guides_editor(
13503 &"
13504 def m:
13505 a = 1
13506 pass"
13507 .unindent(),
13508 cx,
13509 )
13510 .await;
13511
13512 cx.update_editor(|editor, cx| {
13513 editor.change_selections(None, cx, |s| {
13514 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13515 });
13516 });
13517
13518 assert_indent_guides(
13519 0..3,
13520 vec![indent_guide(buffer_id, 1, 2, 0)],
13521 Some(vec![0]),
13522 &mut cx,
13523 );
13524}
13525
13526#[gpui::test]
13527fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13528 init_test(cx, |_| {});
13529
13530 let editor = cx.add_window(|cx| {
13531 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13532 build_editor(buffer, cx)
13533 });
13534
13535 let render_args = Arc::new(Mutex::new(None));
13536 let snapshot = editor
13537 .update(cx, |editor, cx| {
13538 let snapshot = editor.buffer().read(cx).snapshot(cx);
13539 let range =
13540 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13541
13542 struct RenderArgs {
13543 row: MultiBufferRow,
13544 folded: bool,
13545 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13546 }
13547
13548 let crease = Crease::inline(
13549 range,
13550 FoldPlaceholder::test(),
13551 {
13552 let toggle_callback = render_args.clone();
13553 move |row, folded, callback, _cx| {
13554 *toggle_callback.lock() = Some(RenderArgs {
13555 row,
13556 folded,
13557 callback,
13558 });
13559 div()
13560 }
13561 },
13562 |_row, _folded, _cx| div(),
13563 );
13564
13565 editor.insert_creases(Some(crease), cx);
13566 let snapshot = editor.snapshot(cx);
13567 let _div =
13568 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13569 snapshot
13570 })
13571 .unwrap();
13572
13573 let render_args = render_args.lock().take().unwrap();
13574 assert_eq!(render_args.row, MultiBufferRow(1));
13575 assert!(!render_args.folded);
13576 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13577
13578 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13579 .unwrap();
13580 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13581 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13582
13583 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13584 .unwrap();
13585 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13586 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13587}
13588
13589#[gpui::test]
13590async fn test_input_text(cx: &mut gpui::TestAppContext) {
13591 init_test(cx, |_| {});
13592 let mut cx = EditorTestContext::new(cx).await;
13593
13594 cx.set_state(
13595 &r#"ˇone
13596 two
13597
13598 three
13599 fourˇ
13600 five
13601
13602 siˇx"#
13603 .unindent(),
13604 );
13605
13606 cx.dispatch_action(HandleInput(String::new()));
13607 cx.assert_editor_state(
13608 &r#"ˇone
13609 two
13610
13611 three
13612 fourˇ
13613 five
13614
13615 siˇx"#
13616 .unindent(),
13617 );
13618
13619 cx.dispatch_action(HandleInput("AAAA".to_string()));
13620 cx.assert_editor_state(
13621 &r#"AAAAˇone
13622 two
13623
13624 three
13625 fourAAAAˇ
13626 five
13627
13628 siAAAAˇx"#
13629 .unindent(),
13630 );
13631}
13632
13633#[gpui::test]
13634async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13635 init_test(cx, |_| {});
13636
13637 let mut cx = EditorTestContext::new(cx).await;
13638 cx.set_state(
13639 r#"let foo = 1;
13640let foo = 2;
13641let foo = 3;
13642let fooˇ = 4;
13643let foo = 5;
13644let foo = 6;
13645let foo = 7;
13646let foo = 8;
13647let foo = 9;
13648let foo = 10;
13649let foo = 11;
13650let foo = 12;
13651let foo = 13;
13652let foo = 14;
13653let foo = 15;"#,
13654 );
13655
13656 cx.update_editor(|e, cx| {
13657 assert_eq!(
13658 e.next_scroll_position,
13659 NextScrollCursorCenterTopBottom::Center,
13660 "Default next scroll direction is center",
13661 );
13662
13663 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13664 assert_eq!(
13665 e.next_scroll_position,
13666 NextScrollCursorCenterTopBottom::Top,
13667 "After center, next scroll direction should be top",
13668 );
13669
13670 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13671 assert_eq!(
13672 e.next_scroll_position,
13673 NextScrollCursorCenterTopBottom::Bottom,
13674 "After top, next scroll direction should be bottom",
13675 );
13676
13677 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13678 assert_eq!(
13679 e.next_scroll_position,
13680 NextScrollCursorCenterTopBottom::Center,
13681 "After bottom, scrolling should start over",
13682 );
13683
13684 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13685 assert_eq!(
13686 e.next_scroll_position,
13687 NextScrollCursorCenterTopBottom::Top,
13688 "Scrolling continues if retriggered fast enough"
13689 );
13690 });
13691
13692 cx.executor()
13693 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13694 cx.executor().run_until_parked();
13695 cx.update_editor(|e, _| {
13696 assert_eq!(
13697 e.next_scroll_position,
13698 NextScrollCursorCenterTopBottom::Center,
13699 "If scrolling is not triggered fast enough, it should reset"
13700 );
13701 });
13702}
13703
13704#[gpui::test]
13705async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13706 init_test(cx, |_| {});
13707 let mut cx = EditorLspTestContext::new_rust(
13708 lsp::ServerCapabilities {
13709 definition_provider: Some(lsp::OneOf::Left(true)),
13710 references_provider: Some(lsp::OneOf::Left(true)),
13711 ..lsp::ServerCapabilities::default()
13712 },
13713 cx,
13714 )
13715 .await;
13716
13717 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13718 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13719 move |params, _| async move {
13720 if empty_go_to_definition {
13721 Ok(None)
13722 } else {
13723 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13724 uri: params.text_document_position_params.text_document.uri,
13725 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13726 })))
13727 }
13728 },
13729 );
13730 let references =
13731 cx.lsp
13732 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13733 Ok(Some(vec![lsp::Location {
13734 uri: params.text_document_position.text_document.uri,
13735 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13736 }]))
13737 });
13738 (go_to_definition, references)
13739 };
13740
13741 cx.set_state(
13742 &r#"fn one() {
13743 let mut a = ˇtwo();
13744 }
13745
13746 fn two() {}"#
13747 .unindent(),
13748 );
13749 set_up_lsp_handlers(false, &mut cx);
13750 let navigated = cx
13751 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13752 .await
13753 .expect("Failed to navigate to definition");
13754 assert_eq!(
13755 navigated,
13756 Navigated::Yes,
13757 "Should have navigated to definition from the GetDefinition response"
13758 );
13759 cx.assert_editor_state(
13760 &r#"fn one() {
13761 let mut a = two();
13762 }
13763
13764 fn «twoˇ»() {}"#
13765 .unindent(),
13766 );
13767
13768 let editors = cx.update_workspace(|workspace, cx| {
13769 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13770 });
13771 cx.update_editor(|_, test_editor_cx| {
13772 assert_eq!(
13773 editors.len(),
13774 1,
13775 "Initially, only one, test, editor should be open in the workspace"
13776 );
13777 assert_eq!(
13778 test_editor_cx.view(),
13779 editors.last().expect("Asserted len is 1")
13780 );
13781 });
13782
13783 set_up_lsp_handlers(true, &mut cx);
13784 let navigated = cx
13785 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13786 .await
13787 .expect("Failed to navigate to lookup references");
13788 assert_eq!(
13789 navigated,
13790 Navigated::Yes,
13791 "Should have navigated to references as a fallback after empty GoToDefinition response"
13792 );
13793 // We should not change the selections in the existing file,
13794 // if opening another milti buffer with the references
13795 cx.assert_editor_state(
13796 &r#"fn one() {
13797 let mut a = two();
13798 }
13799
13800 fn «twoˇ»() {}"#
13801 .unindent(),
13802 );
13803 let editors = cx.update_workspace(|workspace, cx| {
13804 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13805 });
13806 cx.update_editor(|_, test_editor_cx| {
13807 assert_eq!(
13808 editors.len(),
13809 2,
13810 "After falling back to references search, we open a new editor with the results"
13811 );
13812 let references_fallback_text = editors
13813 .into_iter()
13814 .find(|new_editor| new_editor != test_editor_cx.view())
13815 .expect("Should have one non-test editor now")
13816 .read(test_editor_cx)
13817 .text(test_editor_cx);
13818 assert_eq!(
13819 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13820 "Should use the range from the references response and not the GoToDefinition one"
13821 );
13822 });
13823}
13824
13825#[gpui::test]
13826async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13827 init_test(cx, |_| {});
13828
13829 let language = Arc::new(Language::new(
13830 LanguageConfig::default(),
13831 Some(tree_sitter_rust::LANGUAGE.into()),
13832 ));
13833
13834 let text = r#"
13835 #[cfg(test)]
13836 mod tests() {
13837 #[test]
13838 fn runnable_1() {
13839 let a = 1;
13840 }
13841
13842 #[test]
13843 fn runnable_2() {
13844 let a = 1;
13845 let b = 2;
13846 }
13847 }
13848 "#
13849 .unindent();
13850
13851 let fs = FakeFs::new(cx.executor());
13852 fs.insert_file("/file.rs", Default::default()).await;
13853
13854 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13855 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13856 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13857 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13858 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13859
13860 let editor = cx.new_view(|cx| {
13861 Editor::new(
13862 EditorMode::Full,
13863 multi_buffer,
13864 Some(project.clone()),
13865 true,
13866 cx,
13867 )
13868 });
13869
13870 editor.update(cx, |editor, cx| {
13871 editor.tasks.insert(
13872 (buffer.read(cx).remote_id(), 3),
13873 RunnableTasks {
13874 templates: vec![],
13875 offset: MultiBufferOffset(43),
13876 column: 0,
13877 extra_variables: HashMap::default(),
13878 context_range: BufferOffset(43)..BufferOffset(85),
13879 },
13880 );
13881 editor.tasks.insert(
13882 (buffer.read(cx).remote_id(), 8),
13883 RunnableTasks {
13884 templates: vec![],
13885 offset: MultiBufferOffset(86),
13886 column: 0,
13887 extra_variables: HashMap::default(),
13888 context_range: BufferOffset(86)..BufferOffset(191),
13889 },
13890 );
13891
13892 // Test finding task when cursor is inside function body
13893 editor.change_selections(None, cx, |s| {
13894 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13895 });
13896 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13897 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13898
13899 // Test finding task when cursor is on function name
13900 editor.change_selections(None, cx, |s| {
13901 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13902 });
13903 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13904 assert_eq!(row, 8, "Should find task when cursor is on function name");
13905 });
13906}
13907
13908fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13909 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13910 point..point
13911}
13912
13913fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13914 let (text, ranges) = marked_text_ranges(marked_text, true);
13915 assert_eq!(view.text(cx), text);
13916 assert_eq!(
13917 view.selections.ranges(cx),
13918 ranges,
13919 "Assert selections are {}",
13920 marked_text
13921 );
13922}
13923
13924pub fn handle_signature_help_request(
13925 cx: &mut EditorLspTestContext,
13926 mocked_response: lsp::SignatureHelp,
13927) -> impl Future<Output = ()> {
13928 let mut request =
13929 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13930 let mocked_response = mocked_response.clone();
13931 async move { Ok(Some(mocked_response)) }
13932 });
13933
13934 async move {
13935 request.next().await;
13936 }
13937}
13938
13939/// Handle completion request passing a marked string specifying where the completion
13940/// should be triggered from using '|' character, what range should be replaced, and what completions
13941/// should be returned using '<' and '>' to delimit the range
13942pub fn handle_completion_request(
13943 cx: &mut EditorLspTestContext,
13944 marked_string: &str,
13945 completions: Vec<&'static str>,
13946 counter: Arc<AtomicUsize>,
13947) -> impl Future<Output = ()> {
13948 let complete_from_marker: TextRangeMarker = '|'.into();
13949 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13950 let (_, mut marked_ranges) = marked_text_ranges_by(
13951 marked_string,
13952 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13953 );
13954
13955 let complete_from_position =
13956 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13957 let replace_range =
13958 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13959
13960 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13961 let completions = completions.clone();
13962 counter.fetch_add(1, atomic::Ordering::Release);
13963 async move {
13964 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13965 assert_eq!(
13966 params.text_document_position.position,
13967 complete_from_position
13968 );
13969 Ok(Some(lsp::CompletionResponse::Array(
13970 completions
13971 .iter()
13972 .map(|completion_text| lsp::CompletionItem {
13973 label: completion_text.to_string(),
13974 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13975 range: replace_range,
13976 new_text: completion_text.to_string(),
13977 })),
13978 ..Default::default()
13979 })
13980 .collect(),
13981 )))
13982 }
13983 });
13984
13985 async move {
13986 request.next().await;
13987 }
13988}
13989
13990fn handle_resolve_completion_request(
13991 cx: &mut EditorLspTestContext,
13992 edits: Option<Vec<(&'static str, &'static str)>>,
13993) -> impl Future<Output = ()> {
13994 let edits = edits.map(|edits| {
13995 edits
13996 .iter()
13997 .map(|(marked_string, new_text)| {
13998 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13999 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14000 lsp::TextEdit::new(replace_range, new_text.to_string())
14001 })
14002 .collect::<Vec<_>>()
14003 });
14004
14005 let mut request =
14006 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14007 let edits = edits.clone();
14008 async move {
14009 Ok(lsp::CompletionItem {
14010 additional_text_edits: edits,
14011 ..Default::default()
14012 })
14013 }
14014 });
14015
14016 async move {
14017 request.next().await;
14018 }
14019}
14020
14021pub(crate) fn update_test_language_settings(
14022 cx: &mut TestAppContext,
14023 f: impl Fn(&mut AllLanguageSettingsContent),
14024) {
14025 cx.update(|cx| {
14026 SettingsStore::update_global(cx, |store, cx| {
14027 store.update_user_settings::<AllLanguageSettings>(cx, f);
14028 });
14029 });
14030}
14031
14032pub(crate) fn update_test_project_settings(
14033 cx: &mut TestAppContext,
14034 f: impl Fn(&mut ProjectSettings),
14035) {
14036 cx.update(|cx| {
14037 SettingsStore::update_global(cx, |store, cx| {
14038 store.update_user_settings::<ProjectSettings>(cx, f);
14039 });
14040 });
14041}
14042
14043pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14044 cx.update(|cx| {
14045 assets::Assets.load_test_fonts(cx);
14046 let store = SettingsStore::test(cx);
14047 cx.set_global(store);
14048 theme::init(theme::LoadThemes::JustBase, cx);
14049 release_channel::init(SemanticVersion::default(), cx);
14050 client::init_settings(cx);
14051 language::init(cx);
14052 Project::init_settings(cx);
14053 workspace::init_settings(cx);
14054 crate::init(cx);
14055 });
14056
14057 update_test_language_settings(cx, f);
14058}
14059
14060#[track_caller]
14061fn assert_hunk_revert(
14062 not_reverted_text_with_selections: &str,
14063 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14064 expected_reverted_text_with_selections: &str,
14065 base_text: &str,
14066 cx: &mut EditorLspTestContext,
14067) {
14068 cx.set_state(not_reverted_text_with_selections);
14069 cx.set_diff_base(base_text);
14070 cx.executor().run_until_parked();
14071
14072 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14073 let snapshot = editor.snapshot(cx);
14074 let reverted_hunk_statuses = snapshot
14075 .diff_map
14076 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14077 .map(|hunk| hunk_status(&hunk))
14078 .collect::<Vec<_>>();
14079
14080 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14081 reverted_hunk_statuses
14082 });
14083 cx.executor().run_until_parked();
14084 cx.assert_editor_state(expected_reverted_text_with_selections);
14085 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14086}