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 pretty_assertions::{assert_eq, assert_ne};
29use project::{buffer_store::BufferChangeSet, FakeFs};
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic::{self, AtomicUsize};
36use std::{cell::RefCell, future::Future, iter, rc::Rc, time::Instant};
37use test::editor_lsp_test_context::rust_lang;
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let group_interval = Duration::from_millis(1);
174 let buffer = cx.new_model(|cx| {
175 let mut buf = language::Buffer::local("123456", cx);
176 buf.set_group_interval(group_interval);
177 buf
178 });
179 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
180 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
181
182 _ = editor.update(cx, |editor, cx| {
183 editor.start_transaction_at(now, cx);
184 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
185
186 editor.insert("cd", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cd56");
189 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
190
191 editor.start_transaction_at(now, cx);
192 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
193 editor.insert("e", cx);
194 editor.end_transaction_at(now, cx);
195 assert_eq!(editor.text(cx), "12cde6");
196 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
197
198 now += group_interval + Duration::from_millis(1);
199 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
200
201 // Simulate an edit in another editor
202 buffer.update(cx, |buffer, cx| {
203 buffer.start_transaction_at(now, cx);
204 buffer.edit([(0..1, "a")], None, cx);
205 buffer.edit([(1..1, "b")], None, cx);
206 buffer.end_transaction_at(now, cx);
207 });
208
209 assert_eq!(editor.text(cx), "ab2cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
211
212 // Last transaction happened past the group interval in a different editor.
213 // Undo it individually and don't restore selections.
214 editor.undo(&Undo, cx);
215 assert_eq!(editor.text(cx), "12cde6");
216 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
217
218 // First two transactions happened within the group interval in this editor.
219 // Undo them together and restore selections.
220 editor.undo(&Undo, cx);
221 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
222 assert_eq!(editor.text(cx), "123456");
223 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
224
225 // Redo the first two transactions together.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "12cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
229
230 // Redo the last transaction on its own.
231 editor.redo(&Redo, cx);
232 assert_eq!(editor.text(cx), "ab2cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
234
235 // Test empty transactions.
236 editor.start_transaction_at(now, cx);
237 editor.end_transaction_at(now, cx);
238 editor.undo(&Undo, cx);
239 assert_eq!(editor.text(cx), "12cde6");
240 });
241}
242
243#[gpui::test]
244fn test_ime_composition(cx: &mut TestAppContext) {
245 init_test(cx, |_| {});
246
247 let buffer = cx.new_model(|cx| {
248 let mut buffer = language::Buffer::local("abcde", cx);
249 // Ensure automatic grouping doesn't occur.
250 buffer.set_group_interval(Duration::ZERO);
251 buffer
252 });
253
254 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
255 cx.add_window(|cx| {
256 let mut editor = build_editor(buffer.clone(), cx);
257
258 // Start a new IME composition.
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 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
262 assert_eq!(editor.text(cx), "äbcde");
263 assert_eq!(
264 editor.marked_text_ranges(cx),
265 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
266 );
267
268 // Finalize IME composition.
269 editor.replace_text_in_range(None, "ā", cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // IME composition edits are grouped and are undone/redone at once.
274 editor.undo(&Default::default(), cx);
275 assert_eq!(editor.text(cx), "abcde");
276 assert_eq!(editor.marked_text_ranges(cx), None);
277 editor.redo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "ābcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280
281 // Start a new IME composition.
282 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
283 assert_eq!(
284 editor.marked_text_ranges(cx),
285 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
286 );
287
288 // Undoing during an IME composition cancels it.
289 editor.undo(&Default::default(), cx);
290 assert_eq!(editor.text(cx), "ābcde");
291 assert_eq!(editor.marked_text_ranges(cx), None);
292
293 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
294 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
295 assert_eq!(editor.text(cx), "ābcdè");
296 assert_eq!(
297 editor.marked_text_ranges(cx),
298 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
299 );
300
301 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
302 editor.replace_text_in_range(Some(4..999), "ę", cx);
303 assert_eq!(editor.text(cx), "ābcdę");
304 assert_eq!(editor.marked_text_ranges(cx), None);
305
306 // Start a new IME composition with multiple cursors.
307 editor.change_selections(None, cx, |s| {
308 s.select_ranges([
309 OffsetUtf16(1)..OffsetUtf16(1),
310 OffsetUtf16(3)..OffsetUtf16(3),
311 OffsetUtf16(5)..OffsetUtf16(5),
312 ])
313 });
314 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
315 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
316 assert_eq!(
317 editor.marked_text_ranges(cx),
318 Some(vec![
319 OffsetUtf16(0)..OffsetUtf16(3),
320 OffsetUtf16(4)..OffsetUtf16(7),
321 OffsetUtf16(8)..OffsetUtf16(11)
322 ])
323 );
324
325 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
326 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
327 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
328 assert_eq!(
329 editor.marked_text_ranges(cx),
330 Some(vec![
331 OffsetUtf16(1)..OffsetUtf16(2),
332 OffsetUtf16(5)..OffsetUtf16(6),
333 OffsetUtf16(9)..OffsetUtf16(10)
334 ])
335 );
336
337 // Finalize IME composition with multiple cursors.
338 editor.replace_text_in_range(Some(9..10), "2", cx);
339 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
340 assert_eq!(editor.marked_text_ranges(cx), None);
341
342 editor
343 });
344}
345
346#[gpui::test]
347fn test_selection_with_mouse(cx: &mut TestAppContext) {
348 init_test(cx, |_| {});
349
350 let editor = cx.add_window(|cx| {
351 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
352 build_editor(buffer, cx)
353 });
354
355 _ = editor.update(cx, |view, cx| {
356 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
357 });
358 assert_eq!(
359 editor
360 .update(cx, |view, cx| view.selections.display_ranges(cx))
361 .unwrap(),
362 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
363 );
364
365 _ = editor.update(cx, |view, cx| {
366 view.update_selection(
367 DisplayPoint::new(DisplayRow(3), 3),
368 0,
369 gpui::Point::<f32>::default(),
370 cx,
371 );
372 });
373
374 assert_eq!(
375 editor
376 .update(cx, |view, cx| view.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
379 );
380
381 _ = editor.update(cx, |view, cx| {
382 view.update_selection(
383 DisplayPoint::new(DisplayRow(1), 1),
384 0,
385 gpui::Point::<f32>::default(),
386 cx,
387 );
388 });
389
390 assert_eq!(
391 editor
392 .update(cx, |view, cx| view.selections.display_ranges(cx))
393 .unwrap(),
394 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
395 );
396
397 _ = editor.update(cx, |view, cx| {
398 view.end_selection(cx);
399 view.update_selection(
400 DisplayPoint::new(DisplayRow(3), 3),
401 0,
402 gpui::Point::<f32>::default(),
403 cx,
404 );
405 });
406
407 assert_eq!(
408 editor
409 .update(cx, |view, cx| view.selections.display_ranges(cx))
410 .unwrap(),
411 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
412 );
413
414 _ = editor.update(cx, |view, cx| {
415 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
416 view.update_selection(
417 DisplayPoint::new(DisplayRow(0), 0),
418 0,
419 gpui::Point::<f32>::default(),
420 cx,
421 );
422 });
423
424 assert_eq!(
425 editor
426 .update(cx, |view, cx| view.selections.display_ranges(cx))
427 .unwrap(),
428 [
429 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
430 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
431 ]
432 );
433
434 _ = editor.update(cx, |view, cx| {
435 view.end_selection(cx);
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |view, cx| view.selections.display_ranges(cx))
441 .unwrap(),
442 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
443 );
444}
445
446#[gpui::test]
447fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
448 init_test(cx, |_| {});
449
450 let editor = cx.add_window(|cx| {
451 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
452 build_editor(buffer, cx)
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.end_selection(cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
465 });
466
467 _ = editor.update(cx, |view, cx| {
468 view.end_selection(cx);
469 });
470
471 assert_eq!(
472 editor
473 .update(cx, |view, cx| view.selections.display_ranges(cx))
474 .unwrap(),
475 [
476 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
477 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
478 ]
479 );
480
481 _ = editor.update(cx, |view, cx| {
482 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
483 });
484
485 _ = editor.update(cx, |view, cx| {
486 view.end_selection(cx);
487 });
488
489 assert_eq!(
490 editor
491 .update(cx, |view, cx| view.selections.display_ranges(cx))
492 .unwrap(),
493 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
494 );
495}
496
497#[gpui::test]
498fn test_canceling_pending_selection(cx: &mut TestAppContext) {
499 init_test(cx, |_| {});
500
501 let view = cx.add_window(|cx| {
502 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
503 build_editor(buffer, cx)
504 });
505
506 _ = view.update(cx, |view, cx| {
507 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
508 assert_eq!(
509 view.selections.display_ranges(cx),
510 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
511 );
512 });
513
514 _ = view.update(cx, |view, cx| {
515 view.update_selection(
516 DisplayPoint::new(DisplayRow(3), 3),
517 0,
518 gpui::Point::<f32>::default(),
519 cx,
520 );
521 assert_eq!(
522 view.selections.display_ranges(cx),
523 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
524 );
525 });
526
527 _ = view.update(cx, |view, cx| {
528 view.cancel(&Cancel, cx);
529 view.update_selection(
530 DisplayPoint::new(DisplayRow(1), 1),
531 0,
532 gpui::Point::<f32>::default(),
533 cx,
534 );
535 assert_eq!(
536 view.selections.display_ranges(cx),
537 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
538 );
539 });
540}
541
542#[gpui::test]
543fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
544 init_test(cx, |_| {});
545
546 let view = cx.add_window(|cx| {
547 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
548 build_editor(buffer, cx)
549 });
550
551 _ = view.update(cx, |view, cx| {
552 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
553 assert_eq!(
554 view.selections.display_ranges(cx),
555 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
556 );
557
558 view.move_down(&Default::default(), cx);
559 assert_eq!(
560 view.selections.display_ranges(cx),
561 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
562 );
563
564 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
565 assert_eq!(
566 view.selections.display_ranges(cx),
567 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
568 );
569
570 view.move_up(&Default::default(), cx);
571 assert_eq!(
572 view.selections.display_ranges(cx),
573 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
574 );
575 });
576}
577
578#[gpui::test]
579fn test_clone(cx: &mut TestAppContext) {
580 init_test(cx, |_| {});
581
582 let (text, selection_ranges) = marked_text_ranges(
583 indoc! {"
584 one
585 two
586 threeˇ
587 four
588 fiveˇ
589 "},
590 true,
591 );
592
593 let editor = cx.add_window(|cx| {
594 let buffer = MultiBuffer::build_simple(&text, cx);
595 build_editor(buffer, cx)
596 });
597
598 _ = editor.update(cx, |editor, cx| {
599 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
600 editor.fold_creases(
601 vec![
602 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
603 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
604 ],
605 true,
606 cx,
607 );
608 });
609
610 let cloned_editor = editor
611 .update(cx, |editor, cx| {
612 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
613 })
614 .unwrap()
615 .unwrap();
616
617 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
619
620 assert_eq!(
621 cloned_editor
622 .update(cx, |e, cx| e.display_text(cx))
623 .unwrap(),
624 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
625 );
626 assert_eq!(
627 cloned_snapshot
628 .folds_in_range(0..text.len())
629 .collect::<Vec<_>>(),
630 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
631 );
632 assert_set_eq!(
633 cloned_editor
634 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
635 .unwrap(),
636 editor
637 .update(cx, |editor, cx| editor.selections.ranges(cx))
638 .unwrap()
639 );
640 assert_set_eq!(
641 cloned_editor
642 .update(cx, |e, cx| e.selections.display_ranges(cx))
643 .unwrap(),
644 editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap()
647 );
648}
649
650#[gpui::test]
651async fn test_navigation_history(cx: &mut TestAppContext) {
652 init_test(cx, |_| {});
653
654 use workspace::item::Item;
655
656 let fs = FakeFs::new(cx.executor());
657 let project = Project::test(fs, [], cx).await;
658 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
659 let pane = workspace
660 .update(cx, |workspace, _| workspace.active_pane().clone())
661 .unwrap();
662
663 _ = workspace.update(cx, |_v, cx| {
664 cx.new_view(|cx| {
665 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
666 let mut editor = build_editor(buffer.clone(), cx);
667 let handle = cx.view();
668 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
669
670 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
671 editor.nav_history.as_mut().unwrap().pop_backward(cx)
672 }
673
674 // Move the cursor a small distance.
675 // Nothing is added to the navigation history.
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
679 ])
680 });
681 editor.change_selections(None, cx, |s| {
682 s.select_display_ranges([
683 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
684 ])
685 });
686 assert!(pop_history(&mut editor, cx).is_none());
687
688 // Move the cursor a large distance.
689 // The history can jump back to the previous position.
690 editor.change_selections(None, cx, |s| {
691 s.select_display_ranges([
692 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
693 ])
694 });
695 let nav_entry = pop_history(&mut editor, cx).unwrap();
696 editor.navigate(nav_entry.data.unwrap(), cx);
697 assert_eq!(nav_entry.item.id(), cx.entity_id());
698 assert_eq!(
699 editor.selections.display_ranges(cx),
700 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
701 );
702 assert!(pop_history(&mut editor, cx).is_none());
703
704 // Move the cursor a small distance via the mouse.
705 // Nothing is added to the navigation history.
706 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
707 editor.end_selection(cx);
708 assert_eq!(
709 editor.selections.display_ranges(cx),
710 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
711 );
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance via the mouse.
715 // The history can jump back to the previous position.
716 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
717 editor.end_selection(cx);
718 assert_eq!(
719 editor.selections.display_ranges(cx),
720 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
721 );
722 let nav_entry = pop_history(&mut editor, cx).unwrap();
723 editor.navigate(nav_entry.data.unwrap(), cx);
724 assert_eq!(nav_entry.item.id(), cx.entity_id());
725 assert_eq!(
726 editor.selections.display_ranges(cx),
727 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
728 );
729 assert!(pop_history(&mut editor, cx).is_none());
730
731 // Set scroll position to check later
732 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
733 let original_scroll_position = editor.scroll_manager.anchor();
734
735 // Jump to the end of the document and adjust scroll
736 editor.move_to_end(&MoveToEnd, cx);
737 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
738 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 let nav_entry = pop_history(&mut editor, cx).unwrap();
741 editor.navigate(nav_entry.data.unwrap(), cx);
742 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
743
744 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
745 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
746 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
747 let invalid_point = Point::new(9999, 0);
748 editor.navigate(
749 Box::new(NavigationData {
750 cursor_anchor: invalid_anchor,
751 cursor_position: invalid_point,
752 scroll_anchor: ScrollAnchor {
753 anchor: invalid_anchor,
754 offset: Default::default(),
755 },
756 scroll_top_row: invalid_point.row,
757 }),
758 cx,
759 );
760 assert_eq!(
761 editor.selections.display_ranges(cx),
762 &[editor.max_point(cx)..editor.max_point(cx)]
763 );
764 assert_eq!(
765 editor.scroll_position(cx),
766 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
767 );
768
769 editor
770 })
771 });
772}
773
774#[gpui::test]
775fn test_cancel(cx: &mut TestAppContext) {
776 init_test(cx, |_| {});
777
778 let view = cx.add_window(|cx| {
779 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
780 build_editor(buffer, cx)
781 });
782
783 _ = view.update(cx, |view, cx| {
784 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
785 view.update_selection(
786 DisplayPoint::new(DisplayRow(1), 1),
787 0,
788 gpui::Point::<f32>::default(),
789 cx,
790 );
791 view.end_selection(cx);
792
793 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
794 view.update_selection(
795 DisplayPoint::new(DisplayRow(0), 3),
796 0,
797 gpui::Point::<f32>::default(),
798 cx,
799 );
800 view.end_selection(cx);
801 assert_eq!(
802 view.selections.display_ranges(cx),
803 [
804 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
805 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
806 ]
807 );
808 });
809
810 _ = view.update(cx, |view, cx| {
811 view.cancel(&Cancel, cx);
812 assert_eq!(
813 view.selections.display_ranges(cx),
814 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
815 );
816 });
817
818 _ = view.update(cx, |view, cx| {
819 view.cancel(&Cancel, cx);
820 assert_eq!(
821 view.selections.display_ranges(cx),
822 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
823 );
824 });
825}
826
827#[gpui::test]
828fn test_fold_action(cx: &mut TestAppContext) {
829 init_test(cx, |_| {});
830
831 let view = cx.add_window(|cx| {
832 let buffer = MultiBuffer::build_simple(
833 &"
834 impl Foo {
835 // Hello!
836
837 fn a() {
838 1
839 }
840
841 fn b() {
842 2
843 }
844
845 fn c() {
846 3
847 }
848 }
849 "
850 .unindent(),
851 cx,
852 );
853 build_editor(buffer.clone(), cx)
854 });
855
856 _ = view.update(cx, |view, cx| {
857 view.change_selections(None, cx, |s| {
858 s.select_display_ranges([
859 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
860 ]);
861 });
862 view.fold(&Fold, cx);
863 assert_eq!(
864 view.display_text(cx),
865 "
866 impl Foo {
867 // Hello!
868
869 fn a() {
870 1
871 }
872
873 fn b() {⋯
874 }
875
876 fn c() {⋯
877 }
878 }
879 "
880 .unindent(),
881 );
882
883 view.fold(&Fold, cx);
884 assert_eq!(
885 view.display_text(cx),
886 "
887 impl Foo {⋯
888 }
889 "
890 .unindent(),
891 );
892
893 view.unfold_lines(&UnfoldLines, cx);
894 assert_eq!(
895 view.display_text(cx),
896 "
897 impl Foo {
898 // Hello!
899
900 fn a() {
901 1
902 }
903
904 fn b() {⋯
905 }
906
907 fn c() {⋯
908 }
909 }
910 "
911 .unindent(),
912 );
913
914 view.unfold_lines(&UnfoldLines, cx);
915 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
916 });
917}
918
919#[gpui::test]
920fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
921 init_test(cx, |_| {});
922
923 let view = cx.add_window(|cx| {
924 let buffer = MultiBuffer::build_simple(
925 &"
926 class Foo:
927 # Hello!
928
929 def a():
930 print(1)
931
932 def b():
933 print(2)
934
935 def c():
936 print(3)
937 "
938 .unindent(),
939 cx,
940 );
941 build_editor(buffer.clone(), cx)
942 });
943
944 _ = view.update(cx, |view, cx| {
945 view.change_selections(None, cx, |s| {
946 s.select_display_ranges([
947 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
948 ]);
949 });
950 view.fold(&Fold, cx);
951 assert_eq!(
952 view.display_text(cx),
953 "
954 class Foo:
955 # Hello!
956
957 def a():
958 print(1)
959
960 def b():⋯
961
962 def c():⋯
963 "
964 .unindent(),
965 );
966
967 view.fold(&Fold, cx);
968 assert_eq!(
969 view.display_text(cx),
970 "
971 class Foo:⋯
972 "
973 .unindent(),
974 );
975
976 view.unfold_lines(&UnfoldLines, cx);
977 assert_eq!(
978 view.display_text(cx),
979 "
980 class Foo:
981 # Hello!
982
983 def a():
984 print(1)
985
986 def b():⋯
987
988 def c():⋯
989 "
990 .unindent(),
991 );
992
993 view.unfold_lines(&UnfoldLines, cx);
994 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
995 });
996}
997
998#[gpui::test]
999fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1000 init_test(cx, |_| {});
1001
1002 let view = cx.add_window(|cx| {
1003 let buffer = MultiBuffer::build_simple(
1004 &"
1005 class Foo:
1006 # Hello!
1007
1008 def a():
1009 print(1)
1010
1011 def b():
1012 print(2)
1013
1014
1015 def c():
1016 print(3)
1017
1018
1019 "
1020 .unindent(),
1021 cx,
1022 );
1023 build_editor(buffer.clone(), cx)
1024 });
1025
1026 _ = view.update(cx, |view, cx| {
1027 view.change_selections(None, cx, |s| {
1028 s.select_display_ranges([
1029 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1030 ]);
1031 });
1032 view.fold(&Fold, cx);
1033 assert_eq!(
1034 view.display_text(cx),
1035 "
1036 class Foo:
1037 # Hello!
1038
1039 def a():
1040 print(1)
1041
1042 def b():⋯
1043
1044
1045 def c():⋯
1046
1047
1048 "
1049 .unindent(),
1050 );
1051
1052 view.fold(&Fold, cx);
1053 assert_eq!(
1054 view.display_text(cx),
1055 "
1056 class Foo:⋯
1057
1058
1059 "
1060 .unindent(),
1061 );
1062
1063 view.unfold_lines(&UnfoldLines, cx);
1064 assert_eq!(
1065 view.display_text(cx),
1066 "
1067 class Foo:
1068 # Hello!
1069
1070 def a():
1071 print(1)
1072
1073 def b():⋯
1074
1075
1076 def c():⋯
1077
1078
1079 "
1080 .unindent(),
1081 );
1082
1083 view.unfold_lines(&UnfoldLines, cx);
1084 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1085 });
1086}
1087
1088#[gpui::test]
1089fn test_fold_at_level(cx: &mut TestAppContext) {
1090 init_test(cx, |_| {});
1091
1092 let view = cx.add_window(|cx| {
1093 let buffer = MultiBuffer::build_simple(
1094 &"
1095 class Foo:
1096 # Hello!
1097
1098 def a():
1099 print(1)
1100
1101 def b():
1102 print(2)
1103
1104
1105 class Bar:
1106 # World!
1107
1108 def a():
1109 print(1)
1110
1111 def b():
1112 print(2)
1113
1114
1115 "
1116 .unindent(),
1117 cx,
1118 );
1119 build_editor(buffer.clone(), cx)
1120 });
1121
1122 _ = view.update(cx, |view, cx| {
1123 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1124 assert_eq!(
1125 view.display_text(cx),
1126 "
1127 class Foo:
1128 # Hello!
1129
1130 def a():⋯
1131
1132 def b():⋯
1133
1134
1135 class Bar:
1136 # World!
1137
1138 def a():⋯
1139
1140 def b():⋯
1141
1142
1143 "
1144 .unindent(),
1145 );
1146
1147 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1148 assert_eq!(
1149 view.display_text(cx),
1150 "
1151 class Foo:⋯
1152
1153
1154 class Bar:⋯
1155
1156
1157 "
1158 .unindent(),
1159 );
1160
1161 view.unfold_all(&UnfoldAll, cx);
1162 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1163 assert_eq!(
1164 view.display_text(cx),
1165 "
1166 class Foo:
1167 # Hello!
1168
1169 def a():
1170 print(1)
1171
1172 def b():
1173 print(2)
1174
1175
1176 class Bar:
1177 # World!
1178
1179 def a():
1180 print(1)
1181
1182 def b():
1183 print(2)
1184
1185
1186 "
1187 .unindent(),
1188 );
1189
1190 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1191 });
1192}
1193
1194#[gpui::test]
1195fn test_move_cursor(cx: &mut TestAppContext) {
1196 init_test(cx, |_| {});
1197
1198 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1199 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1200
1201 buffer.update(cx, |buffer, cx| {
1202 buffer.edit(
1203 vec![
1204 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1205 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1206 ],
1207 None,
1208 cx,
1209 );
1210 });
1211 _ = view.update(cx, |view, cx| {
1212 assert_eq!(
1213 view.selections.display_ranges(cx),
1214 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1215 );
1216
1217 view.move_down(&MoveDown, cx);
1218 assert_eq!(
1219 view.selections.display_ranges(cx),
1220 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1221 );
1222
1223 view.move_right(&MoveRight, cx);
1224 assert_eq!(
1225 view.selections.display_ranges(cx),
1226 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1227 );
1228
1229 view.move_left(&MoveLeft, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1233 );
1234
1235 view.move_up(&MoveUp, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1239 );
1240
1241 view.move_to_end(&MoveToEnd, cx);
1242 assert_eq!(
1243 view.selections.display_ranges(cx),
1244 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1245 );
1246
1247 view.move_to_beginning(&MoveToBeginning, cx);
1248 assert_eq!(
1249 view.selections.display_ranges(cx),
1250 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1251 );
1252
1253 view.change_selections(None, cx, |s| {
1254 s.select_display_ranges([
1255 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1256 ]);
1257 });
1258 view.select_to_beginning(&SelectToBeginning, cx);
1259 assert_eq!(
1260 view.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 view.select_to_end(&SelectToEnd, cx);
1265 assert_eq!(
1266 view.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1268 );
1269 });
1270}
1271
1272// TODO: Re-enable this test
1273#[cfg(target_os = "macos")]
1274#[gpui::test]
1275fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1276 init_test(cx, |_| {});
1277
1278 let view = cx.add_window(|cx| {
1279 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1280 build_editor(buffer.clone(), cx)
1281 });
1282
1283 assert_eq!('ⓐ'.len_utf8(), 3);
1284 assert_eq!('α'.len_utf8(), 2);
1285
1286 _ = view.update(cx, |view, cx| {
1287 view.fold_creases(
1288 vec![
1289 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1291 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1292 ],
1293 true,
1294 cx,
1295 );
1296 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1297
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ".len())]
1307 );
1308 view.move_right(&MoveRight, cx);
1309 assert_eq!(
1310 view.selections.display_ranges(cx),
1311 &[empty_range(0, "ⓐⓑ⋯".len())]
1312 );
1313
1314 view.move_down(&MoveDown, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯e".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab⋯".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "ab".len())]
1328 );
1329 view.move_left(&MoveLeft, cx);
1330 assert_eq!(
1331 view.selections.display_ranges(cx),
1332 &[empty_range(1, "a".len())]
1333 );
1334
1335 view.move_down(&MoveDown, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "α".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯".len())]
1349 );
1350 view.move_right(&MoveRight, cx);
1351 assert_eq!(
1352 view.selections.display_ranges(cx),
1353 &[empty_range(2, "αβ⋯ε".len())]
1354 );
1355
1356 view.move_up(&MoveUp, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 view.move_down(&MoveDown, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(2, "αβ⋯ε".len())]
1365 );
1366 view.move_up(&MoveUp, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[empty_range(1, "ab⋯e".len())]
1370 );
1371
1372 view.move_up(&MoveUp, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐⓑ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "ⓐ".len())]
1381 );
1382 view.move_left(&MoveLeft, cx);
1383 assert_eq!(
1384 view.selections.display_ranges(cx),
1385 &[empty_range(0, "".len())]
1386 );
1387 });
1388}
1389
1390#[gpui::test]
1391fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1392 init_test(cx, |_| {});
1393
1394 let view = cx.add_window(|cx| {
1395 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1396 build_editor(buffer.clone(), cx)
1397 });
1398 _ = view.update(cx, |view, cx| {
1399 view.change_selections(None, cx, |s| {
1400 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1401 });
1402
1403 // moving above start of document should move selection to start of document,
1404 // but the next move down should still be at the original goal_x
1405 view.move_up(&MoveUp, cx);
1406 assert_eq!(
1407 view.selections.display_ranges(cx),
1408 &[empty_range(0, "".len())]
1409 );
1410
1411 view.move_down(&MoveDown, cx);
1412 assert_eq!(
1413 view.selections.display_ranges(cx),
1414 &[empty_range(1, "abcd".len())]
1415 );
1416
1417 view.move_down(&MoveDown, cx);
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[empty_range(2, "αβγ".len())]
1421 );
1422
1423 view.move_down(&MoveDown, cx);
1424 assert_eq!(
1425 view.selections.display_ranges(cx),
1426 &[empty_range(3, "abcd".len())]
1427 );
1428
1429 view.move_down(&MoveDown, cx);
1430 assert_eq!(
1431 view.selections.display_ranges(cx),
1432 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1433 );
1434
1435 // moving past end of document should not change goal_x
1436 view.move_down(&MoveDown, cx);
1437 assert_eq!(
1438 view.selections.display_ranges(cx),
1439 &[empty_range(5, "".len())]
1440 );
1441
1442 view.move_down(&MoveDown, cx);
1443 assert_eq!(
1444 view.selections.display_ranges(cx),
1445 &[empty_range(5, "".len())]
1446 );
1447
1448 view.move_up(&MoveUp, cx);
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1452 );
1453
1454 view.move_up(&MoveUp, cx);
1455 assert_eq!(
1456 view.selections.display_ranges(cx),
1457 &[empty_range(3, "abcd".len())]
1458 );
1459
1460 view.move_up(&MoveUp, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[empty_range(2, "αβγ".len())]
1464 );
1465 });
1466}
1467
1468#[gpui::test]
1469fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1470 init_test(cx, |_| {});
1471 let move_to_beg = MoveToBeginningOfLine {
1472 stop_at_soft_wraps: true,
1473 };
1474
1475 let move_to_end = MoveToEndOfLine {
1476 stop_at_soft_wraps: true,
1477 };
1478
1479 let view = cx.add_window(|cx| {
1480 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1481 build_editor(buffer, cx)
1482 });
1483 _ = view.update(cx, |view, cx| {
1484 view.change_selections(None, cx, |s| {
1485 s.select_display_ranges([
1486 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1487 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1488 ]);
1489 });
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_beginning_of_line(&move_to_beg, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1498 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1499 ]
1500 );
1501 });
1502
1503 _ = view.update(cx, |view, cx| {
1504 view.move_to_beginning_of_line(&move_to_beg, cx);
1505 assert_eq!(
1506 view.selections.display_ranges(cx),
1507 &[
1508 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1509 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1510 ]
1511 );
1512 });
1513
1514 _ = view.update(cx, |view, cx| {
1515 view.move_to_beginning_of_line(&move_to_beg, cx);
1516 assert_eq!(
1517 view.selections.display_ranges(cx),
1518 &[
1519 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1520 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1521 ]
1522 );
1523 });
1524
1525 _ = view.update(cx, |view, cx| {
1526 view.move_to_end_of_line(&move_to_end, cx);
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1531 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1532 ]
1533 );
1534 });
1535
1536 // Moving to the end of line again is a no-op.
1537 _ = view.update(cx, |view, cx| {
1538 view.move_to_end_of_line(&move_to_end, cx);
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1543 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.move_left(&MoveLeft, cx);
1550 view.select_to_beginning_of_line(
1551 &SelectToBeginningOfLine {
1552 stop_at_soft_wraps: true,
1553 },
1554 cx,
1555 );
1556 assert_eq!(
1557 view.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1561 ]
1562 );
1563 });
1564
1565 _ = view.update(cx, |view, cx| {
1566 view.select_to_beginning_of_line(
1567 &SelectToBeginningOfLine {
1568 stop_at_soft_wraps: true,
1569 },
1570 cx,
1571 );
1572 assert_eq!(
1573 view.selections.display_ranges(cx),
1574 &[
1575 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1576 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1577 ]
1578 );
1579 });
1580
1581 _ = view.update(cx, |view, cx| {
1582 view.select_to_beginning_of_line(
1583 &SelectToBeginningOfLine {
1584 stop_at_soft_wraps: true,
1585 },
1586 cx,
1587 );
1588 assert_eq!(
1589 view.selections.display_ranges(cx),
1590 &[
1591 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1592 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1593 ]
1594 );
1595 });
1596
1597 _ = view.update(cx, |view, cx| {
1598 view.select_to_end_of_line(
1599 &SelectToEndOfLine {
1600 stop_at_soft_wraps: true,
1601 },
1602 cx,
1603 );
1604 assert_eq!(
1605 view.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1609 ]
1610 );
1611 });
1612
1613 _ = view.update(cx, |view, cx| {
1614 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1615 assert_eq!(view.display_text(cx), "ab\n de");
1616 assert_eq!(
1617 view.selections.display_ranges(cx),
1618 &[
1619 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1620 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1621 ]
1622 );
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1627 assert_eq!(view.display_text(cx), "\n");
1628 assert_eq!(
1629 view.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636}
1637
1638#[gpui::test]
1639fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1640 init_test(cx, |_| {});
1641 let move_to_beg = MoveToBeginningOfLine {
1642 stop_at_soft_wraps: false,
1643 };
1644
1645 let move_to_end = MoveToEndOfLine {
1646 stop_at_soft_wraps: false,
1647 };
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656
1657 // We expect the following lines after wrapping
1658 // ```
1659 // thequickbrownfox
1660 // jumpedoverthelazydo
1661 // gs
1662 // ```
1663 // The final `gs` was soft-wrapped onto a new line.
1664 assert_eq!(
1665 "thequickbrownfox\njumpedoverthelaz\nydogs",
1666 view.display_text(cx),
1667 );
1668
1669 // First, let's assert behavior on the first line, that was not soft-wrapped.
1670 // Start the cursor at the `k` on the first line
1671 view.change_selections(None, cx, |s| {
1672 s.select_display_ranges([
1673 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1674 ]);
1675 });
1676
1677 // Moving to the beginning of the line should put us at the beginning of the line.
1678 view.move_to_beginning_of_line(&move_to_beg, cx);
1679 assert_eq!(
1680 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1681 view.selections.display_ranges(cx)
1682 );
1683
1684 // Moving to the end of the line should put us at the end of the line.
1685 view.move_to_end_of_line(&move_to_end, cx);
1686 assert_eq!(
1687 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1688 view.selections.display_ranges(cx)
1689 );
1690
1691 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1692 // Start the cursor at the last line (`y` that was wrapped to a new line)
1693 view.change_selections(None, cx, |s| {
1694 s.select_display_ranges([
1695 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1696 ]);
1697 });
1698
1699 // Moving to the beginning of the line should put us at the start of the second line of
1700 // display text, i.e., the `j`.
1701 view.move_to_beginning_of_line(&move_to_beg, cx);
1702 assert_eq!(
1703 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1704 view.selections.display_ranges(cx)
1705 );
1706
1707 // Moving to the beginning of the line again should be a no-op.
1708 view.move_to_beginning_of_line(&move_to_beg, cx);
1709 assert_eq!(
1710 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1711 view.selections.display_ranges(cx)
1712 );
1713
1714 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1715 // next display line.
1716 view.move_to_end_of_line(&move_to_end, cx);
1717 assert_eq!(
1718 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1719 view.selections.display_ranges(cx)
1720 );
1721
1722 // Moving to the end of the line again should be a no-op.
1723 view.move_to_end_of_line(&move_to_end, cx);
1724 assert_eq!(
1725 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1726 view.selections.display_ranges(cx)
1727 );
1728 });
1729}
1730
1731#[gpui::test]
1732fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1733 init_test(cx, |_| {});
1734
1735 let view = cx.add_window(|cx| {
1736 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1737 build_editor(buffer, cx)
1738 });
1739 _ = view.update(cx, |view, cx| {
1740 view.change_selections(None, cx, |s| {
1741 s.select_display_ranges([
1742 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1743 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1744 ])
1745 });
1746
1747 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1748 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1749
1750 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1751 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1752
1753 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1754 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1755
1756 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1757 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1758
1759 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1760 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1761
1762 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1763 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1764
1765 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1766 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1767
1768 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1769 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1770
1771 view.move_right(&MoveRight, cx);
1772 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1773 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1774
1775 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1776 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1777
1778 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1779 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1780 });
1781}
1782
1783#[gpui::test]
1784fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1785 init_test(cx, |_| {});
1786
1787 let view = cx.add_window(|cx| {
1788 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1789 build_editor(buffer, cx)
1790 });
1791
1792 _ = view.update(cx, |view, cx| {
1793 view.set_wrap_width(Some(140.0.into()), cx);
1794 assert_eq!(
1795 view.display_text(cx),
1796 "use one::{\n two::three::\n four::five\n};"
1797 );
1798
1799 view.change_selections(None, cx, |s| {
1800 s.select_display_ranges([
1801 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1802 ]);
1803 });
1804
1805 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1806 assert_eq!(
1807 view.selections.display_ranges(cx),
1808 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1809 );
1810
1811 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1812 assert_eq!(
1813 view.selections.display_ranges(cx),
1814 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1815 );
1816
1817 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1818 assert_eq!(
1819 view.selections.display_ranges(cx),
1820 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1821 );
1822
1823 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1824 assert_eq!(
1825 view.selections.display_ranges(cx),
1826 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1827 );
1828
1829 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1830 assert_eq!(
1831 view.selections.display_ranges(cx),
1832 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1833 );
1834
1835 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1836 assert_eq!(
1837 view.selections.display_ranges(cx),
1838 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1839 );
1840 });
1841}
1842
1843#[gpui::test]
1844async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1845 init_test(cx, |_| {});
1846 let mut cx = EditorTestContext::new(cx).await;
1847
1848 let line_height = cx.editor(|editor, cx| {
1849 editor
1850 .style()
1851 .unwrap()
1852 .text
1853 .line_height_in_pixels(cx.rem_size())
1854 });
1855 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1856
1857 cx.set_state(
1858 &r#"ˇone
1859 two
1860
1861 three
1862 fourˇ
1863 five
1864
1865 six"#
1866 .unindent(),
1867 );
1868
1869 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1870 cx.assert_editor_state(
1871 &r#"one
1872 two
1873 ˇ
1874 three
1875 four
1876 five
1877 ˇ
1878 six"#
1879 .unindent(),
1880 );
1881
1882 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1883 cx.assert_editor_state(
1884 &r#"one
1885 two
1886
1887 three
1888 four
1889 five
1890 ˇ
1891 sixˇ"#
1892 .unindent(),
1893 );
1894
1895 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1896 cx.assert_editor_state(
1897 &r#"one
1898 two
1899
1900 three
1901 four
1902 five
1903
1904 sixˇ"#
1905 .unindent(),
1906 );
1907
1908 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1909 cx.assert_editor_state(
1910 &r#"one
1911 two
1912
1913 three
1914 four
1915 five
1916 ˇ
1917 six"#
1918 .unindent(),
1919 );
1920
1921 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1922 cx.assert_editor_state(
1923 &r#"one
1924 two
1925 ˇ
1926 three
1927 four
1928 five
1929
1930 six"#
1931 .unindent(),
1932 );
1933
1934 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1935 cx.assert_editor_state(
1936 &r#"ˇone
1937 two
1938
1939 three
1940 four
1941 five
1942
1943 six"#
1944 .unindent(),
1945 );
1946}
1947
1948#[gpui::test]
1949async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1950 init_test(cx, |_| {});
1951 let mut cx = EditorTestContext::new(cx).await;
1952 let line_height = cx.editor(|editor, cx| {
1953 editor
1954 .style()
1955 .unwrap()
1956 .text
1957 .line_height_in_pixels(cx.rem_size())
1958 });
1959 let window = cx.window;
1960 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1961
1962 cx.set_state(
1963 r#"ˇone
1964 two
1965 three
1966 four
1967 five
1968 six
1969 seven
1970 eight
1971 nine
1972 ten
1973 "#,
1974 );
1975
1976 cx.update_editor(|editor, cx| {
1977 assert_eq!(
1978 editor.snapshot(cx).scroll_position(),
1979 gpui::Point::new(0., 0.)
1980 );
1981 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1982 assert_eq!(
1983 editor.snapshot(cx).scroll_position(),
1984 gpui::Point::new(0., 3.)
1985 );
1986 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1987 assert_eq!(
1988 editor.snapshot(cx).scroll_position(),
1989 gpui::Point::new(0., 6.)
1990 );
1991 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1992 assert_eq!(
1993 editor.snapshot(cx).scroll_position(),
1994 gpui::Point::new(0., 3.)
1995 );
1996
1997 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1998 assert_eq!(
1999 editor.snapshot(cx).scroll_position(),
2000 gpui::Point::new(0., 1.)
2001 );
2002 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2003 assert_eq!(
2004 editor.snapshot(cx).scroll_position(),
2005 gpui::Point::new(0., 3.)
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.update_editor(|editor, cx| {
2016 editor.set_vertical_scroll_margin(2, cx);
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(cx.rem_size())
2022 });
2023 let window = cx.window;
2024 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2025
2026 cx.set_state(
2027 r#"ˇone
2028 two
2029 three
2030 four
2031 five
2032 six
2033 seven
2034 eight
2035 nine
2036 ten
2037 "#,
2038 );
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 0.0)
2043 );
2044 });
2045
2046 // Add a cursor below the visible area. Since both cursors cannot fit
2047 // on screen, the editor autoscrolls to reveal the newest cursor, and
2048 // allows the vertical scroll margin below that cursor.
2049 cx.update_editor(|editor, cx| {
2050 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2051 selections.select_ranges([
2052 Point::new(0, 0)..Point::new(0, 0),
2053 Point::new(6, 0)..Point::new(6, 0),
2054 ]);
2055 })
2056 });
2057 cx.update_editor(|editor, cx| {
2058 assert_eq!(
2059 editor.snapshot(cx).scroll_position(),
2060 gpui::Point::new(0., 3.0)
2061 );
2062 });
2063
2064 // Move down. The editor cursor scrolls down to track the newest cursor.
2065 cx.update_editor(|editor, cx| {
2066 editor.move_down(&Default::default(), cx);
2067 });
2068 cx.update_editor(|editor, cx| {
2069 assert_eq!(
2070 editor.snapshot(cx).scroll_position(),
2071 gpui::Point::new(0., 4.0)
2072 );
2073 });
2074
2075 // Add a cursor above the visible area. Since both cursors fit on screen,
2076 // the editor scrolls to show both.
2077 cx.update_editor(|editor, cx| {
2078 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2079 selections.select_ranges([
2080 Point::new(1, 0)..Point::new(1, 0),
2081 Point::new(6, 0)..Point::new(6, 0),
2082 ]);
2083 })
2084 });
2085 cx.update_editor(|editor, cx| {
2086 assert_eq!(
2087 editor.snapshot(cx).scroll_position(),
2088 gpui::Point::new(0., 1.0)
2089 );
2090 });
2091}
2092
2093#[gpui::test]
2094async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2095 init_test(cx, |_| {});
2096 let mut cx = EditorTestContext::new(cx).await;
2097
2098 let line_height = cx.editor(|editor, cx| {
2099 editor
2100 .style()
2101 .unwrap()
2102 .text
2103 .line_height_in_pixels(cx.rem_size())
2104 });
2105 let window = cx.window;
2106 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2107 cx.set_state(
2108 &r#"
2109 ˇone
2110 two
2111 threeˇ
2112 four
2113 five
2114 six
2115 seven
2116 eight
2117 nine
2118 ten
2119 "#
2120 .unindent(),
2121 );
2122
2123 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2124 cx.assert_editor_state(
2125 &r#"
2126 one
2127 two
2128 three
2129 ˇfour
2130 five
2131 sixˇ
2132 seven
2133 eight
2134 nine
2135 ten
2136 "#
2137 .unindent(),
2138 );
2139
2140 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2141 cx.assert_editor_state(
2142 &r#"
2143 one
2144 two
2145 three
2146 four
2147 five
2148 six
2149 ˇseven
2150 eight
2151 nineˇ
2152 ten
2153 "#
2154 .unindent(),
2155 );
2156
2157 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2158 cx.assert_editor_state(
2159 &r#"
2160 one
2161 two
2162 three
2163 ˇfour
2164 five
2165 sixˇ
2166 seven
2167 eight
2168 nine
2169 ten
2170 "#
2171 .unindent(),
2172 );
2173
2174 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2175 cx.assert_editor_state(
2176 &r#"
2177 ˇone
2178 two
2179 threeˇ
2180 four
2181 five
2182 six
2183 seven
2184 eight
2185 nine
2186 ten
2187 "#
2188 .unindent(),
2189 );
2190
2191 // Test select collapsing
2192 cx.update_editor(|editor, cx| {
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 editor.move_page_down(&MovePageDown::default(), cx);
2196 });
2197 cx.assert_editor_state(
2198 &r#"
2199 one
2200 two
2201 three
2202 four
2203 five
2204 six
2205 seven
2206 eight
2207 nine
2208 ˇten
2209 ˇ"#
2210 .unindent(),
2211 );
2212}
2213
2214#[gpui::test]
2215async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2216 init_test(cx, |_| {});
2217 let mut cx = EditorTestContext::new(cx).await;
2218 cx.set_state("one «two threeˇ» four");
2219 cx.update_editor(|editor, cx| {
2220 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2221 assert_eq!(editor.text(cx), " four");
2222 });
2223}
2224
2225#[gpui::test]
2226fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2227 init_test(cx, |_| {});
2228
2229 let view = cx.add_window(|cx| {
2230 let buffer = MultiBuffer::build_simple("one two three four", cx);
2231 build_editor(buffer.clone(), cx)
2232 });
2233
2234 _ = view.update(cx, |view, cx| {
2235 view.change_selections(None, cx, |s| {
2236 s.select_display_ranges([
2237 // an empty selection - the preceding word fragment is deleted
2238 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2239 // characters selected - they are deleted
2240 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2241 ])
2242 });
2243 view.delete_to_previous_word_start(
2244 &DeleteToPreviousWordStart {
2245 ignore_newlines: false,
2246 },
2247 cx,
2248 );
2249 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2250 });
2251
2252 _ = view.update(cx, |view, cx| {
2253 view.change_selections(None, cx, |s| {
2254 s.select_display_ranges([
2255 // an empty selection - the following word fragment is deleted
2256 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2257 // characters selected - they are deleted
2258 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2259 ])
2260 });
2261 view.delete_to_next_word_end(
2262 &DeleteToNextWordEnd {
2263 ignore_newlines: false,
2264 },
2265 cx,
2266 );
2267 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2268 });
2269}
2270
2271#[gpui::test]
2272fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2273 init_test(cx, |_| {});
2274
2275 let view = cx.add_window(|cx| {
2276 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2277 build_editor(buffer.clone(), cx)
2278 });
2279 let del_to_prev_word_start = DeleteToPreviousWordStart {
2280 ignore_newlines: false,
2281 };
2282 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2283 ignore_newlines: true,
2284 };
2285
2286 _ = view.update(cx, |view, cx| {
2287 view.change_selections(None, cx, |s| {
2288 s.select_display_ranges([
2289 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2290 ])
2291 });
2292 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2293 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2294 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2295 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2296 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2297 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2298 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2299 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2300 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2301 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2302 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2303 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2304 });
2305}
2306
2307#[gpui::test]
2308fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2309 init_test(cx, |_| {});
2310
2311 let view = cx.add_window(|cx| {
2312 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2313 build_editor(buffer.clone(), cx)
2314 });
2315 let del_to_next_word_end = DeleteToNextWordEnd {
2316 ignore_newlines: false,
2317 };
2318 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2319 ignore_newlines: true,
2320 };
2321
2322 _ = view.update(cx, |view, cx| {
2323 view.change_selections(None, cx, |s| {
2324 s.select_display_ranges([
2325 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2326 ])
2327 });
2328 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2329 assert_eq!(
2330 view.buffer.read(cx).read(cx).text(),
2331 "one\n two\nthree\n four"
2332 );
2333 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2334 assert_eq!(
2335 view.buffer.read(cx).read(cx).text(),
2336 "\n two\nthree\n four"
2337 );
2338 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2339 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2340 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2341 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2342 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2343 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2344 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2345 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2346 });
2347}
2348
2349#[gpui::test]
2350fn test_newline(cx: &mut TestAppContext) {
2351 init_test(cx, |_| {});
2352
2353 let view = cx.add_window(|cx| {
2354 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2355 build_editor(buffer.clone(), cx)
2356 });
2357
2358 _ = view.update(cx, |view, cx| {
2359 view.change_selections(None, cx, |s| {
2360 s.select_display_ranges([
2361 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2362 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2363 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2364 ])
2365 });
2366
2367 view.newline(&Newline, cx);
2368 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2369 });
2370}
2371
2372#[gpui::test]
2373fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 let editor = cx.add_window(|cx| {
2377 let buffer = MultiBuffer::build_simple(
2378 "
2379 a
2380 b(
2381 X
2382 )
2383 c(
2384 X
2385 )
2386 "
2387 .unindent()
2388 .as_str(),
2389 cx,
2390 );
2391 let mut editor = build_editor(buffer.clone(), cx);
2392 editor.change_selections(None, cx, |s| {
2393 s.select_ranges([
2394 Point::new(2, 4)..Point::new(2, 5),
2395 Point::new(5, 4)..Point::new(5, 5),
2396 ])
2397 });
2398 editor
2399 });
2400
2401 _ = editor.update(cx, |editor, cx| {
2402 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2403 editor.buffer.update(cx, |buffer, cx| {
2404 buffer.edit(
2405 [
2406 (Point::new(1, 2)..Point::new(3, 0), ""),
2407 (Point::new(4, 2)..Point::new(6, 0), ""),
2408 ],
2409 None,
2410 cx,
2411 );
2412 assert_eq!(
2413 buffer.read(cx).text(),
2414 "
2415 a
2416 b()
2417 c()
2418 "
2419 .unindent()
2420 );
2421 });
2422 assert_eq!(
2423 editor.selections.ranges(cx),
2424 &[
2425 Point::new(1, 2)..Point::new(1, 2),
2426 Point::new(2, 2)..Point::new(2, 2),
2427 ],
2428 );
2429
2430 editor.newline(&Newline, cx);
2431 assert_eq!(
2432 editor.text(cx),
2433 "
2434 a
2435 b(
2436 )
2437 c(
2438 )
2439 "
2440 .unindent()
2441 );
2442
2443 // The selections are moved after the inserted newlines
2444 assert_eq!(
2445 editor.selections.ranges(cx),
2446 &[
2447 Point::new(2, 0)..Point::new(2, 0),
2448 Point::new(4, 0)..Point::new(4, 0),
2449 ],
2450 );
2451 });
2452}
2453
2454#[gpui::test]
2455async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2456 init_test(cx, |settings| {
2457 settings.defaults.tab_size = NonZeroU32::new(4)
2458 });
2459
2460 let language = Arc::new(
2461 Language::new(
2462 LanguageConfig::default(),
2463 Some(tree_sitter_rust::LANGUAGE.into()),
2464 )
2465 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2466 .unwrap(),
2467 );
2468
2469 let mut cx = EditorTestContext::new(cx).await;
2470 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2471 cx.set_state(indoc! {"
2472 const a: ˇA = (
2473 (ˇ
2474 «const_functionˇ»(ˇ),
2475 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2476 )ˇ
2477 ˇ);ˇ
2478 "});
2479
2480 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2481 cx.assert_editor_state(indoc! {"
2482 ˇ
2483 const a: A = (
2484 ˇ
2485 (
2486 ˇ
2487 ˇ
2488 const_function(),
2489 ˇ
2490 ˇ
2491 ˇ
2492 ˇ
2493 something_else,
2494 ˇ
2495 )
2496 ˇ
2497 ˇ
2498 );
2499 "});
2500}
2501
2502#[gpui::test]
2503async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2504 init_test(cx, |settings| {
2505 settings.defaults.tab_size = NonZeroU32::new(4)
2506 });
2507
2508 let language = Arc::new(
2509 Language::new(
2510 LanguageConfig::default(),
2511 Some(tree_sitter_rust::LANGUAGE.into()),
2512 )
2513 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2514 .unwrap(),
2515 );
2516
2517 let mut cx = EditorTestContext::new(cx).await;
2518 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2519 cx.set_state(indoc! {"
2520 const a: ˇA = (
2521 (ˇ
2522 «const_functionˇ»(ˇ),
2523 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2524 )ˇ
2525 ˇ);ˇ
2526 "});
2527
2528 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2529 cx.assert_editor_state(indoc! {"
2530 const a: A = (
2531 ˇ
2532 (
2533 ˇ
2534 const_function(),
2535 ˇ
2536 ˇ
2537 something_else,
2538 ˇ
2539 ˇ
2540 ˇ
2541 ˇ
2542 )
2543 ˇ
2544 );
2545 ˇ
2546 ˇ
2547 "});
2548}
2549
2550#[gpui::test]
2551async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2552 init_test(cx, |settings| {
2553 settings.defaults.tab_size = NonZeroU32::new(4)
2554 });
2555
2556 let language = Arc::new(Language::new(
2557 LanguageConfig {
2558 line_comments: vec!["//".into()],
2559 ..LanguageConfig::default()
2560 },
2561 None,
2562 ));
2563 {
2564 let mut cx = EditorTestContext::new(cx).await;
2565 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2566 cx.set_state(indoc! {"
2567 // Fooˇ
2568 "});
2569
2570 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2571 cx.assert_editor_state(indoc! {"
2572 // Foo
2573 //ˇ
2574 "});
2575 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2576 cx.set_state(indoc! {"
2577 ˇ// Foo
2578 "});
2579 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2580 cx.assert_editor_state(indoc! {"
2581
2582 ˇ// Foo
2583 "});
2584 }
2585 // Ensure that comment continuations can be disabled.
2586 update_test_language_settings(cx, |settings| {
2587 settings.defaults.extend_comment_on_newline = Some(false);
2588 });
2589 let mut cx = EditorTestContext::new(cx).await;
2590 cx.set_state(indoc! {"
2591 // Fooˇ
2592 "});
2593 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2594 cx.assert_editor_state(indoc! {"
2595 // Foo
2596 ˇ
2597 "});
2598}
2599
2600#[gpui::test]
2601fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2602 init_test(cx, |_| {});
2603
2604 let editor = cx.add_window(|cx| {
2605 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2606 let mut editor = build_editor(buffer.clone(), cx);
2607 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2608 editor
2609 });
2610
2611 _ = editor.update(cx, |editor, cx| {
2612 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2613 editor.buffer.update(cx, |buffer, cx| {
2614 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2615 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2616 });
2617 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2618
2619 editor.insert("Z", cx);
2620 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2621
2622 // The selections are moved after the inserted characters
2623 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2624 });
2625}
2626
2627#[gpui::test]
2628async fn test_tab(cx: &mut gpui::TestAppContext) {
2629 init_test(cx, |settings| {
2630 settings.defaults.tab_size = NonZeroU32::new(3)
2631 });
2632
2633 let mut cx = EditorTestContext::new(cx).await;
2634 cx.set_state(indoc! {"
2635 ˇabˇc
2636 ˇ🏀ˇ🏀ˇefg
2637 dˇ
2638 "});
2639 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2640 cx.assert_editor_state(indoc! {"
2641 ˇab ˇc
2642 ˇ🏀 ˇ🏀 ˇefg
2643 d ˇ
2644 "});
2645
2646 cx.set_state(indoc! {"
2647 a
2648 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 a
2653 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2654 "});
2655}
2656
2657#[gpui::test]
2658async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2659 init_test(cx, |_| {});
2660
2661 let mut cx = EditorTestContext::new(cx).await;
2662 let language = Arc::new(
2663 Language::new(
2664 LanguageConfig::default(),
2665 Some(tree_sitter_rust::LANGUAGE.into()),
2666 )
2667 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2668 .unwrap(),
2669 );
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671
2672 // cursors that are already at the suggested indent level insert
2673 // a soft tab. cursors that are to the left of the suggested indent
2674 // auto-indent their line.
2675 cx.set_state(indoc! {"
2676 ˇ
2677 const a: B = (
2678 c(
2679 d(
2680 ˇ
2681 )
2682 ˇ
2683 ˇ )
2684 );
2685 "});
2686 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2687 cx.assert_editor_state(indoc! {"
2688 ˇ
2689 const a: B = (
2690 c(
2691 d(
2692 ˇ
2693 )
2694 ˇ
2695 ˇ)
2696 );
2697 "});
2698
2699 // handle auto-indent when there are multiple cursors on the same line
2700 cx.set_state(indoc! {"
2701 const a: B = (
2702 c(
2703 ˇ ˇ
2704 ˇ )
2705 );
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 const a: B = (
2710 c(
2711 ˇ
2712 ˇ)
2713 );
2714 "});
2715}
2716
2717#[gpui::test]
2718async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2719 init_test(cx, |settings| {
2720 settings.defaults.tab_size = NonZeroU32::new(4)
2721 });
2722
2723 let language = Arc::new(
2724 Language::new(
2725 LanguageConfig::default(),
2726 Some(tree_sitter_rust::LANGUAGE.into()),
2727 )
2728 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2729 .unwrap(),
2730 );
2731
2732 let mut cx = EditorTestContext::new(cx).await;
2733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2734 cx.set_state(indoc! {"
2735 fn a() {
2736 if b {
2737 \t ˇc
2738 }
2739 }
2740 "});
2741
2742 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2743 cx.assert_editor_state(indoc! {"
2744 fn a() {
2745 if b {
2746 ˇc
2747 }
2748 }
2749 "});
2750}
2751
2752#[gpui::test]
2753async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2754 init_test(cx, |settings| {
2755 settings.defaults.tab_size = NonZeroU32::new(4);
2756 });
2757
2758 let mut cx = EditorTestContext::new(cx).await;
2759
2760 cx.set_state(indoc! {"
2761 «oneˇ» «twoˇ»
2762 three
2763 four
2764 "});
2765 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2766 cx.assert_editor_state(indoc! {"
2767 «oneˇ» «twoˇ»
2768 three
2769 four
2770 "});
2771
2772 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2773 cx.assert_editor_state(indoc! {"
2774 «oneˇ» «twoˇ»
2775 three
2776 four
2777 "});
2778
2779 // select across line ending
2780 cx.set_state(indoc! {"
2781 one two
2782 t«hree
2783 ˇ» four
2784 "});
2785 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2786 cx.assert_editor_state(indoc! {"
2787 one two
2788 t«hree
2789 ˇ» four
2790 "});
2791
2792 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2793 cx.assert_editor_state(indoc! {"
2794 one two
2795 t«hree
2796 ˇ» four
2797 "});
2798
2799 // Ensure that indenting/outdenting works when the cursor is at column 0.
2800 cx.set_state(indoc! {"
2801 one two
2802 ˇthree
2803 four
2804 "});
2805 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2806 cx.assert_editor_state(indoc! {"
2807 one two
2808 ˇthree
2809 four
2810 "});
2811
2812 cx.set_state(indoc! {"
2813 one two
2814 ˇ three
2815 four
2816 "});
2817 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2818 cx.assert_editor_state(indoc! {"
2819 one two
2820 ˇthree
2821 four
2822 "});
2823}
2824
2825#[gpui::test]
2826async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2827 init_test(cx, |settings| {
2828 settings.defaults.hard_tabs = Some(true);
2829 });
2830
2831 let mut cx = EditorTestContext::new(cx).await;
2832
2833 // select two ranges on one line
2834 cx.set_state(indoc! {"
2835 «oneˇ» «twoˇ»
2836 three
2837 four
2838 "});
2839 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2840 cx.assert_editor_state(indoc! {"
2841 \t«oneˇ» «twoˇ»
2842 three
2843 four
2844 "});
2845 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2846 cx.assert_editor_state(indoc! {"
2847 \t\t«oneˇ» «twoˇ»
2848 three
2849 four
2850 "});
2851 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2852 cx.assert_editor_state(indoc! {"
2853 \t«oneˇ» «twoˇ»
2854 three
2855 four
2856 "});
2857 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across a line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ»four
2869 "});
2870 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 \tt«hree
2874 ˇ»four
2875 "});
2876 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2877 cx.assert_editor_state(indoc! {"
2878 one two
2879 \t\tt«hree
2880 ˇ»four
2881 "});
2882 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2883 cx.assert_editor_state(indoc! {"
2884 one two
2885 \tt«hree
2886 ˇ»four
2887 "});
2888 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2889 cx.assert_editor_state(indoc! {"
2890 one two
2891 t«hree
2892 ˇ»four
2893 "});
2894
2895 // Ensure that indenting/outdenting works when the cursor is at column 0.
2896 cx.set_state(indoc! {"
2897 one two
2898 ˇthree
2899 four
2900 "});
2901 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2902 cx.assert_editor_state(indoc! {"
2903 one two
2904 ˇthree
2905 four
2906 "});
2907 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2908 cx.assert_editor_state(indoc! {"
2909 one two
2910 \tˇthree
2911 four
2912 "});
2913 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2914 cx.assert_editor_state(indoc! {"
2915 one two
2916 ˇthree
2917 four
2918 "});
2919}
2920
2921#[gpui::test]
2922fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2923 init_test(cx, |settings| {
2924 settings.languages.extend([
2925 (
2926 "TOML".into(),
2927 LanguageSettingsContent {
2928 tab_size: NonZeroU32::new(2),
2929 ..Default::default()
2930 },
2931 ),
2932 (
2933 "Rust".into(),
2934 LanguageSettingsContent {
2935 tab_size: NonZeroU32::new(4),
2936 ..Default::default()
2937 },
2938 ),
2939 ]);
2940 });
2941
2942 let toml_language = Arc::new(Language::new(
2943 LanguageConfig {
2944 name: "TOML".into(),
2945 ..Default::default()
2946 },
2947 None,
2948 ));
2949 let rust_language = Arc::new(Language::new(
2950 LanguageConfig {
2951 name: "Rust".into(),
2952 ..Default::default()
2953 },
2954 None,
2955 ));
2956
2957 let toml_buffer =
2958 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2959 let rust_buffer = cx.new_model(|cx| {
2960 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2961 });
2962 let multibuffer = cx.new_model(|cx| {
2963 let mut multibuffer = MultiBuffer::new(ReadWrite);
2964 multibuffer.push_excerpts(
2965 toml_buffer.clone(),
2966 [ExcerptRange {
2967 context: Point::new(0, 0)..Point::new(2, 0),
2968 primary: None,
2969 }],
2970 cx,
2971 );
2972 multibuffer.push_excerpts(
2973 rust_buffer.clone(),
2974 [ExcerptRange {
2975 context: Point::new(0, 0)..Point::new(1, 0),
2976 primary: None,
2977 }],
2978 cx,
2979 );
2980 multibuffer
2981 });
2982
2983 cx.add_window(|cx| {
2984 let mut editor = build_editor(multibuffer, cx);
2985
2986 assert_eq!(
2987 editor.text(cx),
2988 indoc! {"
2989 a = 1
2990 b = 2
2991
2992 const c: usize = 3;
2993 "}
2994 );
2995
2996 select_ranges(
2997 &mut editor,
2998 indoc! {"
2999 «aˇ» = 1
3000 b = 2
3001
3002 «const c:ˇ» usize = 3;
3003 "},
3004 cx,
3005 );
3006
3007 editor.tab(&Tab, cx);
3008 assert_text_with_selections(
3009 &mut editor,
3010 indoc! {"
3011 «aˇ» = 1
3012 b = 2
3013
3014 «const c:ˇ» usize = 3;
3015 "},
3016 cx,
3017 );
3018 editor.tab_prev(&TabPrev, cx);
3019 assert_text_with_selections(
3020 &mut editor,
3021 indoc! {"
3022 «aˇ» = 1
3023 b = 2
3024
3025 «const c:ˇ» usize = 3;
3026 "},
3027 cx,
3028 );
3029
3030 editor
3031 });
3032}
3033
3034#[gpui::test]
3035async fn test_backspace(cx: &mut gpui::TestAppContext) {
3036 init_test(cx, |_| {});
3037
3038 let mut cx = EditorTestContext::new(cx).await;
3039
3040 // Basic backspace
3041 cx.set_state(indoc! {"
3042 onˇe two three
3043 fou«rˇ» five six
3044 seven «ˇeight nine
3045 »ten
3046 "});
3047 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3048 cx.assert_editor_state(indoc! {"
3049 oˇe two three
3050 fouˇ five six
3051 seven ˇten
3052 "});
3053
3054 // Test backspace inside and around indents
3055 cx.set_state(indoc! {"
3056 zero
3057 ˇone
3058 ˇtwo
3059 ˇ ˇ ˇ three
3060 ˇ ˇ four
3061 "});
3062 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3063 cx.assert_editor_state(indoc! {"
3064 zero
3065 ˇone
3066 ˇtwo
3067 ˇ threeˇ four
3068 "});
3069
3070 // Test backspace with line_mode set to true
3071 cx.update_editor(|e, _| e.selections.line_mode = true);
3072 cx.set_state(indoc! {"
3073 The ˇquick ˇbrown
3074 fox jumps over
3075 the lazy dog
3076 ˇThe qu«ick bˇ»rown"});
3077 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3078 cx.assert_editor_state(indoc! {"
3079 ˇfox jumps over
3080 the lazy dogˇ"});
3081}
3082
3083#[gpui::test]
3084async fn test_delete(cx: &mut gpui::TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let mut cx = EditorTestContext::new(cx).await;
3088 cx.set_state(indoc! {"
3089 onˇe two three
3090 fou«rˇ» five six
3091 seven «ˇeight nine
3092 »ten
3093 "});
3094 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3095 cx.assert_editor_state(indoc! {"
3096 onˇ two three
3097 fouˇ five six
3098 seven ˇten
3099 "});
3100
3101 // Test backspace with line_mode set to true
3102 cx.update_editor(|e, _| e.selections.line_mode = true);
3103 cx.set_state(indoc! {"
3104 The ˇquick ˇbrown
3105 fox «ˇjum»ps over
3106 the lazy dog
3107 ˇThe qu«ick bˇ»rown"});
3108 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3109 cx.assert_editor_state("ˇthe lazy dogˇ");
3110}
3111
3112#[gpui::test]
3113fn test_delete_line(cx: &mut TestAppContext) {
3114 init_test(cx, |_| {});
3115
3116 let view = cx.add_window(|cx| {
3117 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3118 build_editor(buffer, cx)
3119 });
3120 _ = view.update(cx, |view, cx| {
3121 view.change_selections(None, cx, |s| {
3122 s.select_display_ranges([
3123 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3125 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3126 ])
3127 });
3128 view.delete_line(&DeleteLine, cx);
3129 assert_eq!(view.display_text(cx), "ghi");
3130 assert_eq!(
3131 view.selections.display_ranges(cx),
3132 vec![
3133 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3134 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3135 ]
3136 );
3137 });
3138
3139 let view = cx.add_window(|cx| {
3140 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3141 build_editor(buffer, cx)
3142 });
3143 _ = view.update(cx, |view, cx| {
3144 view.change_selections(None, cx, |s| {
3145 s.select_display_ranges([
3146 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3147 ])
3148 });
3149 view.delete_line(&DeleteLine, cx);
3150 assert_eq!(view.display_text(cx), "ghi\n");
3151 assert_eq!(
3152 view.selections.display_ranges(cx),
3153 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3154 );
3155 });
3156}
3157
3158#[gpui::test]
3159fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3160 init_test(cx, |_| {});
3161
3162 cx.add_window(|cx| {
3163 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3164 let mut editor = build_editor(buffer.clone(), cx);
3165 let buffer = buffer.read(cx).as_singleton().unwrap();
3166
3167 assert_eq!(
3168 editor.selections.ranges::<Point>(cx),
3169 &[Point::new(0, 0)..Point::new(0, 0)]
3170 );
3171
3172 // When on single line, replace newline at end by space
3173 editor.join_lines(&JoinLines, cx);
3174 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3175 assert_eq!(
3176 editor.selections.ranges::<Point>(cx),
3177 &[Point::new(0, 3)..Point::new(0, 3)]
3178 );
3179
3180 // When multiple lines are selected, remove newlines that are spanned by the selection
3181 editor.change_selections(None, cx, |s| {
3182 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3183 });
3184 editor.join_lines(&JoinLines, cx);
3185 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3186 assert_eq!(
3187 editor.selections.ranges::<Point>(cx),
3188 &[Point::new(0, 11)..Point::new(0, 11)]
3189 );
3190
3191 // Undo should be transactional
3192 editor.undo(&Undo, cx);
3193 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3194 assert_eq!(
3195 editor.selections.ranges::<Point>(cx),
3196 &[Point::new(0, 5)..Point::new(2, 2)]
3197 );
3198
3199 // When joining an empty line don't insert a space
3200 editor.change_selections(None, cx, |s| {
3201 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3202 });
3203 editor.join_lines(&JoinLines, cx);
3204 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3205 assert_eq!(
3206 editor.selections.ranges::<Point>(cx),
3207 [Point::new(2, 3)..Point::new(2, 3)]
3208 );
3209
3210 // We can remove trailing newlines
3211 editor.join_lines(&JoinLines, cx);
3212 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3213 assert_eq!(
3214 editor.selections.ranges::<Point>(cx),
3215 [Point::new(2, 3)..Point::new(2, 3)]
3216 );
3217
3218 // We don't blow up on the last line
3219 editor.join_lines(&JoinLines, cx);
3220 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3221 assert_eq!(
3222 editor.selections.ranges::<Point>(cx),
3223 [Point::new(2, 3)..Point::new(2, 3)]
3224 );
3225
3226 // reset to test indentation
3227 editor.buffer.update(cx, |buffer, cx| {
3228 buffer.edit(
3229 [
3230 (Point::new(1, 0)..Point::new(1, 2), " "),
3231 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3232 ],
3233 None,
3234 cx,
3235 )
3236 });
3237
3238 // We remove any leading spaces
3239 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3240 editor.change_selections(None, cx, |s| {
3241 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3242 });
3243 editor.join_lines(&JoinLines, cx);
3244 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3245
3246 // We don't insert a space for a line containing only spaces
3247 editor.join_lines(&JoinLines, cx);
3248 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3249
3250 // We ignore any leading tabs
3251 editor.join_lines(&JoinLines, cx);
3252 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3253
3254 editor
3255 });
3256}
3257
3258#[gpui::test]
3259fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3260 init_test(cx, |_| {});
3261
3262 cx.add_window(|cx| {
3263 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3264 let mut editor = build_editor(buffer.clone(), cx);
3265 let buffer = buffer.read(cx).as_singleton().unwrap();
3266
3267 editor.change_selections(None, cx, |s| {
3268 s.select_ranges([
3269 Point::new(0, 2)..Point::new(1, 1),
3270 Point::new(1, 2)..Point::new(1, 2),
3271 Point::new(3, 1)..Point::new(3, 2),
3272 ])
3273 });
3274
3275 editor.join_lines(&JoinLines, cx);
3276 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3277
3278 assert_eq!(
3279 editor.selections.ranges::<Point>(cx),
3280 [
3281 Point::new(0, 7)..Point::new(0, 7),
3282 Point::new(1, 3)..Point::new(1, 3)
3283 ]
3284 );
3285 editor
3286 });
3287}
3288
3289#[gpui::test]
3290async fn test_join_lines_with_git_diff_base(
3291 executor: BackgroundExecutor,
3292 cx: &mut gpui::TestAppContext,
3293) {
3294 init_test(cx, |_| {});
3295
3296 let mut cx = EditorTestContext::new(cx).await;
3297
3298 let diff_base = r#"
3299 Line 0
3300 Line 1
3301 Line 2
3302 Line 3
3303 "#
3304 .unindent();
3305
3306 cx.set_state(
3307 &r#"
3308 ˇLine 0
3309 Line 1
3310 Line 2
3311 Line 3
3312 "#
3313 .unindent(),
3314 );
3315
3316 cx.set_diff_base(&diff_base);
3317 executor.run_until_parked();
3318
3319 // Join lines
3320 cx.update_editor(|editor, cx| {
3321 editor.join_lines(&JoinLines, cx);
3322 });
3323 executor.run_until_parked();
3324
3325 cx.assert_editor_state(
3326 &r#"
3327 Line 0ˇ Line 1
3328 Line 2
3329 Line 3
3330 "#
3331 .unindent(),
3332 );
3333 // Join again
3334 cx.update_editor(|editor, cx| {
3335 editor.join_lines(&JoinLines, cx);
3336 });
3337 executor.run_until_parked();
3338
3339 cx.assert_editor_state(
3340 &r#"
3341 Line 0 Line 1ˇ Line 2
3342 Line 3
3343 "#
3344 .unindent(),
3345 );
3346}
3347
3348#[gpui::test]
3349async fn test_custom_newlines_cause_no_false_positive_diffs(
3350 executor: BackgroundExecutor,
3351 cx: &mut gpui::TestAppContext,
3352) {
3353 init_test(cx, |_| {});
3354 let mut cx = EditorTestContext::new(cx).await;
3355 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3356 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3357 executor.run_until_parked();
3358
3359 cx.update_editor(|editor, cx| {
3360 let snapshot = editor.snapshot(cx);
3361 assert_eq!(
3362 snapshot
3363 .diff_map
3364 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3365 .collect::<Vec<_>>(),
3366 Vec::new(),
3367 "Should not have any diffs for files with custom newlines"
3368 );
3369 });
3370}
3371
3372#[gpui::test]
3373async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3374 init_test(cx, |_| {});
3375
3376 let mut cx = EditorTestContext::new(cx).await;
3377
3378 // Test sort_lines_case_insensitive()
3379 cx.set_state(indoc! {"
3380 «z
3381 y
3382 x
3383 Z
3384 Y
3385 Xˇ»
3386 "});
3387 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3388 cx.assert_editor_state(indoc! {"
3389 «x
3390 X
3391 y
3392 Y
3393 z
3394 Zˇ»
3395 "});
3396
3397 // Test reverse_lines()
3398 cx.set_state(indoc! {"
3399 «5
3400 4
3401 3
3402 2
3403 1ˇ»
3404 "});
3405 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3406 cx.assert_editor_state(indoc! {"
3407 «1
3408 2
3409 3
3410 4
3411 5ˇ»
3412 "});
3413
3414 // Skip testing shuffle_line()
3415
3416 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3417 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3418
3419 // Don't manipulate when cursor is on single line, but expand the selection
3420 cx.set_state(indoc! {"
3421 ddˇdd
3422 ccc
3423 bb
3424 a
3425 "});
3426 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3427 cx.assert_editor_state(indoc! {"
3428 «ddddˇ»
3429 ccc
3430 bb
3431 a
3432 "});
3433
3434 // Basic manipulate case
3435 // Start selection moves to column 0
3436 // End of selection shrinks to fit shorter line
3437 cx.set_state(indoc! {"
3438 dd«d
3439 ccc
3440 bb
3441 aaaaaˇ»
3442 "});
3443 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3444 cx.assert_editor_state(indoc! {"
3445 «aaaaa
3446 bb
3447 ccc
3448 dddˇ»
3449 "});
3450
3451 // Manipulate case with newlines
3452 cx.set_state(indoc! {"
3453 dd«d
3454 ccc
3455
3456 bb
3457 aaaaa
3458
3459 ˇ»
3460 "});
3461 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3462 cx.assert_editor_state(indoc! {"
3463 «
3464
3465 aaaaa
3466 bb
3467 ccc
3468 dddˇ»
3469
3470 "});
3471
3472 // Adding new line
3473 cx.set_state(indoc! {"
3474 aa«a
3475 bbˇ»b
3476 "});
3477 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3478 cx.assert_editor_state(indoc! {"
3479 «aaa
3480 bbb
3481 added_lineˇ»
3482 "});
3483
3484 // Removing line
3485 cx.set_state(indoc! {"
3486 aa«a
3487 bbbˇ»
3488 "});
3489 cx.update_editor(|e, cx| {
3490 e.manipulate_lines(cx, |lines| {
3491 lines.pop();
3492 })
3493 });
3494 cx.assert_editor_state(indoc! {"
3495 «aaaˇ»
3496 "});
3497
3498 // Removing all lines
3499 cx.set_state(indoc! {"
3500 aa«a
3501 bbbˇ»
3502 "});
3503 cx.update_editor(|e, cx| {
3504 e.manipulate_lines(cx, |lines| {
3505 lines.drain(..);
3506 })
3507 });
3508 cx.assert_editor_state(indoc! {"
3509 ˇ
3510 "});
3511}
3512
3513#[gpui::test]
3514async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3515 init_test(cx, |_| {});
3516
3517 let mut cx = EditorTestContext::new(cx).await;
3518
3519 // Consider continuous selection as single selection
3520 cx.set_state(indoc! {"
3521 Aaa«aa
3522 cˇ»c«c
3523 bb
3524 aaaˇ»aa
3525 "});
3526 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3527 cx.assert_editor_state(indoc! {"
3528 «Aaaaa
3529 ccc
3530 bb
3531 aaaaaˇ»
3532 "});
3533
3534 cx.set_state(indoc! {"
3535 Aaa«aa
3536 cˇ»c«c
3537 bb
3538 aaaˇ»aa
3539 "});
3540 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3541 cx.assert_editor_state(indoc! {"
3542 «Aaaaa
3543 ccc
3544 bbˇ»
3545 "});
3546
3547 // Consider non continuous selection as distinct dedup operations
3548 cx.set_state(indoc! {"
3549 «aaaaa
3550 bb
3551 aaaaa
3552 aaaaaˇ»
3553
3554 aaa«aaˇ»
3555 "});
3556 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3557 cx.assert_editor_state(indoc! {"
3558 «aaaaa
3559 bbˇ»
3560
3561 «aaaaaˇ»
3562 "});
3563}
3564
3565#[gpui::test]
3566async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 cx.set_state(indoc! {"
3572 «Aaa
3573 aAa
3574 Aaaˇ»
3575 "});
3576 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3577 cx.assert_editor_state(indoc! {"
3578 «Aaa
3579 aAaˇ»
3580 "});
3581
3582 cx.set_state(indoc! {"
3583 «Aaa
3584 aAa
3585 aaAˇ»
3586 "});
3587 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3588 cx.assert_editor_state(indoc! {"
3589 «Aaaˇ»
3590 "});
3591}
3592
3593#[gpui::test]
3594async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3595 init_test(cx, |_| {});
3596
3597 let mut cx = EditorTestContext::new(cx).await;
3598
3599 // Manipulate with multiple selections on a single line
3600 cx.set_state(indoc! {"
3601 dd«dd
3602 cˇ»c«c
3603 bb
3604 aaaˇ»aa
3605 "});
3606 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3607 cx.assert_editor_state(indoc! {"
3608 «aaaaa
3609 bb
3610 ccc
3611 ddddˇ»
3612 "});
3613
3614 // Manipulate with multiple disjoin selections
3615 cx.set_state(indoc! {"
3616 5«
3617 4
3618 3
3619 2
3620 1ˇ»
3621
3622 dd«dd
3623 ccc
3624 bb
3625 aaaˇ»aa
3626 "});
3627 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3628 cx.assert_editor_state(indoc! {"
3629 «1
3630 2
3631 3
3632 4
3633 5ˇ»
3634
3635 «aaaaa
3636 bb
3637 ccc
3638 ddddˇ»
3639 "});
3640
3641 // Adding lines on each selection
3642 cx.set_state(indoc! {"
3643 2«
3644 1ˇ»
3645
3646 bb«bb
3647 aaaˇ»aa
3648 "});
3649 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3650 cx.assert_editor_state(indoc! {"
3651 «2
3652 1
3653 added lineˇ»
3654
3655 «bbbb
3656 aaaaa
3657 added lineˇ»
3658 "});
3659
3660 // Removing lines on each selection
3661 cx.set_state(indoc! {"
3662 2«
3663 1ˇ»
3664
3665 bb«bb
3666 aaaˇ»aa
3667 "});
3668 cx.update_editor(|e, cx| {
3669 e.manipulate_lines(cx, |lines| {
3670 lines.pop();
3671 })
3672 });
3673 cx.assert_editor_state(indoc! {"
3674 «2ˇ»
3675
3676 «bbbbˇ»
3677 "});
3678}
3679
3680#[gpui::test]
3681async fn test_manipulate_text(cx: &mut TestAppContext) {
3682 init_test(cx, |_| {});
3683
3684 let mut cx = EditorTestContext::new(cx).await;
3685
3686 // Test convert_to_upper_case()
3687 cx.set_state(indoc! {"
3688 «hello worldˇ»
3689 "});
3690 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3691 cx.assert_editor_state(indoc! {"
3692 «HELLO WORLDˇ»
3693 "});
3694
3695 // Test convert_to_lower_case()
3696 cx.set_state(indoc! {"
3697 «HELLO WORLDˇ»
3698 "});
3699 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3700 cx.assert_editor_state(indoc! {"
3701 «hello worldˇ»
3702 "});
3703
3704 // Test multiple line, single selection case
3705 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3706 cx.set_state(indoc! {"
3707 «The quick brown
3708 fox jumps over
3709 the lazy dogˇ»
3710 "});
3711 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3712 cx.assert_editor_state(indoc! {"
3713 «The Quick Brown
3714 Fox Jumps Over
3715 The Lazy Dogˇ»
3716 "});
3717
3718 // Test multiple line, single selection case
3719 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3720 cx.set_state(indoc! {"
3721 «The quick brown
3722 fox jumps over
3723 the lazy dogˇ»
3724 "});
3725 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3726 cx.assert_editor_state(indoc! {"
3727 «TheQuickBrown
3728 FoxJumpsOver
3729 TheLazyDogˇ»
3730 "});
3731
3732 // From here on out, test more complex cases of manipulate_text()
3733
3734 // Test no selection case - should affect words cursors are in
3735 // Cursor at beginning, middle, and end of word
3736 cx.set_state(indoc! {"
3737 ˇhello big beauˇtiful worldˇ
3738 "});
3739 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3740 cx.assert_editor_state(indoc! {"
3741 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3742 "});
3743
3744 // Test multiple selections on a single line and across multiple lines
3745 cx.set_state(indoc! {"
3746 «Theˇ» quick «brown
3747 foxˇ» jumps «overˇ»
3748 the «lazyˇ» dog
3749 "});
3750 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3751 cx.assert_editor_state(indoc! {"
3752 «THEˇ» quick «BROWN
3753 FOXˇ» jumps «OVERˇ»
3754 the «LAZYˇ» dog
3755 "});
3756
3757 // Test case where text length grows
3758 cx.set_state(indoc! {"
3759 «tschüߡ»
3760 "});
3761 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3762 cx.assert_editor_state(indoc! {"
3763 «TSCHÜSSˇ»
3764 "});
3765
3766 // Test to make sure we don't crash when text shrinks
3767 cx.set_state(indoc! {"
3768 aaa_bbbˇ
3769 "});
3770 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3771 cx.assert_editor_state(indoc! {"
3772 «aaaBbbˇ»
3773 "});
3774
3775 // Test to make sure we all aware of the fact that each word can grow and shrink
3776 // Final selections should be aware of this fact
3777 cx.set_state(indoc! {"
3778 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3779 "});
3780 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3781 cx.assert_editor_state(indoc! {"
3782 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3783 "});
3784
3785 cx.set_state(indoc! {"
3786 «hElLo, WoRld!ˇ»
3787 "});
3788 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3789 cx.assert_editor_state(indoc! {"
3790 «HeLlO, wOrLD!ˇ»
3791 "});
3792}
3793
3794#[gpui::test]
3795fn test_duplicate_line(cx: &mut TestAppContext) {
3796 init_test(cx, |_| {});
3797
3798 let view = cx.add_window(|cx| {
3799 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3800 build_editor(buffer, cx)
3801 });
3802 _ = view.update(cx, |view, cx| {
3803 view.change_selections(None, cx, |s| {
3804 s.select_display_ranges([
3805 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3806 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3807 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3808 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3809 ])
3810 });
3811 view.duplicate_line_down(&DuplicateLineDown, cx);
3812 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3818 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3819 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3820 ]
3821 );
3822 });
3823
3824 let view = cx.add_window(|cx| {
3825 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3826 build_editor(buffer, cx)
3827 });
3828 _ = view.update(cx, |view, cx| {
3829 view.change_selections(None, cx, |s| {
3830 s.select_display_ranges([
3831 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3832 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3833 ])
3834 });
3835 view.duplicate_line_down(&DuplicateLineDown, cx);
3836 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3837 assert_eq!(
3838 view.selections.display_ranges(cx),
3839 vec![
3840 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3841 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3842 ]
3843 );
3844 });
3845
3846 // With `move_upwards` the selections stay in place, except for
3847 // the lines inserted above them
3848 let view = cx.add_window(|cx| {
3849 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3850 build_editor(buffer, cx)
3851 });
3852 _ = view.update(cx, |view, cx| {
3853 view.change_selections(None, cx, |s| {
3854 s.select_display_ranges([
3855 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3856 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3857 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3858 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3859 ])
3860 });
3861 view.duplicate_line_up(&DuplicateLineUp, cx);
3862 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3863 assert_eq!(
3864 view.selections.display_ranges(cx),
3865 vec![
3866 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3867 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3868 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3869 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3870 ]
3871 );
3872 });
3873
3874 let view = cx.add_window(|cx| {
3875 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3876 build_editor(buffer, cx)
3877 });
3878 _ = view.update(cx, |view, cx| {
3879 view.change_selections(None, cx, |s| {
3880 s.select_display_ranges([
3881 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3882 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3883 ])
3884 });
3885 view.duplicate_line_up(&DuplicateLineUp, cx);
3886 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3887 assert_eq!(
3888 view.selections.display_ranges(cx),
3889 vec![
3890 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3891 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3892 ]
3893 );
3894 });
3895}
3896
3897#[gpui::test]
3898fn test_move_line_up_down(cx: &mut TestAppContext) {
3899 init_test(cx, |_| {});
3900
3901 let view = cx.add_window(|cx| {
3902 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3903 build_editor(buffer, cx)
3904 });
3905 _ = view.update(cx, |view, cx| {
3906 view.fold_creases(
3907 vec![
3908 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3909 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3910 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3911 ],
3912 true,
3913 cx,
3914 );
3915 view.change_selections(None, cx, |s| {
3916 s.select_display_ranges([
3917 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3918 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3919 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3920 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3921 ])
3922 });
3923 assert_eq!(
3924 view.display_text(cx),
3925 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3926 );
3927
3928 view.move_line_up(&MoveLineUp, cx);
3929 assert_eq!(
3930 view.display_text(cx),
3931 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3932 );
3933 assert_eq!(
3934 view.selections.display_ranges(cx),
3935 vec![
3936 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3937 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3938 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3939 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3940 ]
3941 );
3942 });
3943
3944 _ = view.update(cx, |view, cx| {
3945 view.move_line_down(&MoveLineDown, cx);
3946 assert_eq!(
3947 view.display_text(cx),
3948 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950 assert_eq!(
3951 view.selections.display_ranges(cx),
3952 vec![
3953 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3954 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3955 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3956 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3957 ]
3958 );
3959 });
3960
3961 _ = view.update(cx, |view, cx| {
3962 view.move_line_down(&MoveLineDown, cx);
3963 assert_eq!(
3964 view.display_text(cx),
3965 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3966 );
3967 assert_eq!(
3968 view.selections.display_ranges(cx),
3969 vec![
3970 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3971 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3972 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3973 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3974 ]
3975 );
3976 });
3977
3978 _ = view.update(cx, |view, cx| {
3979 view.move_line_up(&MoveLineUp, cx);
3980 assert_eq!(
3981 view.display_text(cx),
3982 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3983 );
3984 assert_eq!(
3985 view.selections.display_ranges(cx),
3986 vec![
3987 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3988 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3989 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3990 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3991 ]
3992 );
3993 });
3994}
3995
3996#[gpui::test]
3997fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3998 init_test(cx, |_| {});
3999
4000 let editor = cx.add_window(|cx| {
4001 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4002 build_editor(buffer, cx)
4003 });
4004 _ = editor.update(cx, |editor, cx| {
4005 let snapshot = editor.buffer.read(cx).snapshot(cx);
4006 editor.insert_blocks(
4007 [BlockProperties {
4008 style: BlockStyle::Fixed,
4009 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4010 height: 1,
4011 render: Arc::new(|_| div().into_any()),
4012 priority: 0,
4013 }],
4014 Some(Autoscroll::fit()),
4015 cx,
4016 );
4017 editor.change_selections(None, cx, |s| {
4018 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4019 });
4020 editor.move_line_down(&MoveLineDown, cx);
4021 });
4022}
4023
4024#[gpui::test]
4025async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4026 init_test(cx, |_| {});
4027
4028 let mut cx = EditorTestContext::new(cx).await;
4029 cx.set_state(
4030 &"
4031 ˇzero
4032 one
4033 two
4034 three
4035 four
4036 five
4037 "
4038 .unindent(),
4039 );
4040
4041 // Create a four-line block that replaces three lines of text.
4042 cx.update_editor(|editor, cx| {
4043 let snapshot = editor.snapshot(cx);
4044 let snapshot = &snapshot.buffer_snapshot;
4045 let placement = BlockPlacement::Replace(
4046 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4047 );
4048 editor.insert_blocks(
4049 [BlockProperties {
4050 placement,
4051 height: 4,
4052 style: BlockStyle::Sticky,
4053 render: Arc::new(|_| gpui::div().into_any_element()),
4054 priority: 0,
4055 }],
4056 None,
4057 cx,
4058 );
4059 });
4060
4061 // Move down so that the cursor touches the block.
4062 cx.update_editor(|editor, cx| {
4063 editor.move_down(&Default::default(), cx);
4064 });
4065 cx.assert_editor_state(
4066 &"
4067 zero
4068 «one
4069 two
4070 threeˇ»
4071 four
4072 five
4073 "
4074 .unindent(),
4075 );
4076
4077 // Move down past the block.
4078 cx.update_editor(|editor, cx| {
4079 editor.move_down(&Default::default(), cx);
4080 });
4081 cx.assert_editor_state(
4082 &"
4083 zero
4084 one
4085 two
4086 three
4087 ˇfour
4088 five
4089 "
4090 .unindent(),
4091 );
4092}
4093
4094#[gpui::test]
4095fn test_transpose(cx: &mut TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 _ = cx.add_window(|cx| {
4099 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4100 editor.set_style(EditorStyle::default(), cx);
4101 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "bac");
4104 assert_eq!(editor.selections.ranges(cx), [2..2]);
4105
4106 editor.transpose(&Default::default(), cx);
4107 assert_eq!(editor.text(cx), "bca");
4108 assert_eq!(editor.selections.ranges(cx), [3..3]);
4109
4110 editor.transpose(&Default::default(), cx);
4111 assert_eq!(editor.text(cx), "bac");
4112 assert_eq!(editor.selections.ranges(cx), [3..3]);
4113
4114 editor
4115 });
4116
4117 _ = cx.add_window(|cx| {
4118 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4119 editor.set_style(EditorStyle::default(), cx);
4120 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "acb\nde");
4123 assert_eq!(editor.selections.ranges(cx), [3..3]);
4124
4125 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4126 editor.transpose(&Default::default(), cx);
4127 assert_eq!(editor.text(cx), "acbd\ne");
4128 assert_eq!(editor.selections.ranges(cx), [5..5]);
4129
4130 editor.transpose(&Default::default(), cx);
4131 assert_eq!(editor.text(cx), "acbde\n");
4132 assert_eq!(editor.selections.ranges(cx), [6..6]);
4133
4134 editor.transpose(&Default::default(), cx);
4135 assert_eq!(editor.text(cx), "acbd\ne");
4136 assert_eq!(editor.selections.ranges(cx), [6..6]);
4137
4138 editor
4139 });
4140
4141 _ = cx.add_window(|cx| {
4142 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4143 editor.set_style(EditorStyle::default(), cx);
4144 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4145 editor.transpose(&Default::default(), cx);
4146 assert_eq!(editor.text(cx), "bacd\ne");
4147 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4148
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "bcade\n");
4151 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "bcda\ne");
4155 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "bcade\n");
4159 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4160
4161 editor.transpose(&Default::default(), cx);
4162 assert_eq!(editor.text(cx), "bcaed\n");
4163 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4164
4165 editor
4166 });
4167
4168 _ = cx.add_window(|cx| {
4169 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4170 editor.set_style(EditorStyle::default(), cx);
4171 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "🏀🍐✋");
4174 assert_eq!(editor.selections.ranges(cx), [8..8]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "🏀✋🍐");
4178 assert_eq!(editor.selections.ranges(cx), [11..11]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "🏀🍐✋");
4182 assert_eq!(editor.selections.ranges(cx), [11..11]);
4183
4184 editor
4185 });
4186}
4187
4188#[gpui::test]
4189async fn test_rewrap(cx: &mut TestAppContext) {
4190 init_test(cx, |_| {});
4191
4192 let mut cx = EditorTestContext::new(cx).await;
4193
4194 let language_with_c_comments = Arc::new(Language::new(
4195 LanguageConfig {
4196 line_comments: vec!["// ".into()],
4197 ..LanguageConfig::default()
4198 },
4199 None,
4200 ));
4201 let language_with_pound_comments = Arc::new(Language::new(
4202 LanguageConfig {
4203 line_comments: vec!["# ".into()],
4204 ..LanguageConfig::default()
4205 },
4206 None,
4207 ));
4208 let markdown_language = Arc::new(Language::new(
4209 LanguageConfig {
4210 name: "Markdown".into(),
4211 ..LanguageConfig::default()
4212 },
4213 None,
4214 ));
4215 let language_with_doc_comments = Arc::new(Language::new(
4216 LanguageConfig {
4217 line_comments: vec!["// ".into(), "/// ".into()],
4218 ..LanguageConfig::default()
4219 },
4220 Some(tree_sitter_rust::LANGUAGE.into()),
4221 ));
4222
4223 let plaintext_language = Arc::new(Language::new(
4224 LanguageConfig {
4225 name: "Plain Text".into(),
4226 ..LanguageConfig::default()
4227 },
4228 None,
4229 ));
4230
4231 assert_rewrap(
4232 indoc! {"
4233 // ˇ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.
4234 "},
4235 indoc! {"
4236 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4237 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4238 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4239 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4240 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4241 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4242 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4243 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4244 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4245 // porttitor id. Aliquam id accumsan eros.
4246 "},
4247 language_with_c_comments.clone(),
4248 &mut cx,
4249 );
4250
4251 // Test that rewrapping works inside of a selection
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 cursors that expand to the same region are collapsed.
4273 assert_rewrap(
4274 indoc! {"
4275 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4276 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4277 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4278 // ˇ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.
4279 "},
4280 indoc! {"
4281 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4282 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4283 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4284 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4285 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4286 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4287 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4288 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4289 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4290 // porttitor id. Aliquam id accumsan eros.
4291 "},
4292 language_with_c_comments.clone(),
4293 &mut cx,
4294 );
4295
4296 // Test that non-contiguous selections are treated separately.
4297 assert_rewrap(
4298 indoc! {"
4299 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4300 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4301 //
4302 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4303 // ˇ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.
4304 "},
4305 indoc! {"
4306 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4307 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4308 // auctor, eu lacinia sapien scelerisque.
4309 //
4310 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4311 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4312 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4313 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4314 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4315 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4316 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4317 "},
4318 language_with_c_comments.clone(),
4319 &mut cx,
4320 );
4321
4322 // Test that different comment prefixes are supported.
4323 assert_rewrap(
4324 indoc! {"
4325 # ˇ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.
4326 "},
4327 indoc! {"
4328 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4329 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4330 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4331 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4332 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4333 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4334 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4335 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4336 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4337 # accumsan eros.
4338 "},
4339 language_with_pound_comments.clone(),
4340 &mut cx,
4341 );
4342
4343 // Test that rewrapping is ignored outside of comments in most languages.
4344 assert_rewrap(
4345 indoc! {"
4346 /// Adds two numbers.
4347 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4348 fn add(a: u32, b: u32) -> u32 {
4349 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ˇ
4350 }
4351 "},
4352 indoc! {"
4353 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4354 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4355 fn add(a: u32, b: u32) -> u32 {
4356 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ˇ
4357 }
4358 "},
4359 language_with_doc_comments.clone(),
4360 &mut cx,
4361 );
4362
4363 // Test that rewrapping works in Markdown and Plain Text languages.
4364 assert_rewrap(
4365 indoc! {"
4366 # Hello
4367
4368 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.
4369 "},
4370 indoc! {"
4371 # Hello
4372
4373 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4374 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4375 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4376 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4377 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4378 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4379 Integer sit amet scelerisque nisi.
4380 "},
4381 markdown_language,
4382 &mut cx,
4383 );
4384
4385 assert_rewrap(
4386 indoc! {"
4387 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.
4388 "},
4389 indoc! {"
4390 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4391 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4392 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4393 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4394 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4395 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4396 Integer sit amet scelerisque nisi.
4397 "},
4398 plaintext_language,
4399 &mut cx,
4400 );
4401
4402 // Test rewrapping unaligned comments in a selection.
4403 assert_rewrap(
4404 indoc! {"
4405 fn foo() {
4406 if true {
4407 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4408 // Praesent semper egestas tellus id dignissim.ˇ»
4409 do_something();
4410 } else {
4411 //
4412 }
4413 }
4414 "},
4415 indoc! {"
4416 fn foo() {
4417 if true {
4418 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4419 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4420 // egestas tellus id dignissim.ˇ»
4421 do_something();
4422 } else {
4423 //
4424 }
4425 }
4426 "},
4427 language_with_doc_comments.clone(),
4428 &mut cx,
4429 );
4430
4431 assert_rewrap(
4432 indoc! {"
4433 fn foo() {
4434 if true {
4435 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4436 // Praesent semper egestas tellus id dignissim.»
4437 do_something();
4438 } else {
4439 //
4440 }
4441
4442 }
4443 "},
4444 indoc! {"
4445 fn foo() {
4446 if true {
4447 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4448 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4449 // egestas tellus id dignissim.»
4450 do_something();
4451 } else {
4452 //
4453 }
4454
4455 }
4456 "},
4457 language_with_doc_comments.clone(),
4458 &mut cx,
4459 );
4460
4461 #[track_caller]
4462 fn assert_rewrap(
4463 unwrapped_text: &str,
4464 wrapped_text: &str,
4465 language: Arc<Language>,
4466 cx: &mut EditorTestContext,
4467 ) {
4468 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4469 cx.set_state(unwrapped_text);
4470 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4471 cx.assert_editor_state(wrapped_text);
4472 }
4473}
4474
4475#[gpui::test]
4476async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4477 init_test(cx, |_| {});
4478
4479 let mut cx = EditorTestContext::new(cx).await;
4480
4481 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4482 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4483 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4484
4485 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4486 cx.set_state("two ˇfour ˇsix ˇ");
4487 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4488 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4489
4490 // Paste again but with only two cursors. Since the number of cursors doesn't
4491 // match the number of slices in the clipboard, the entire clipboard text
4492 // is pasted at each cursor.
4493 cx.set_state("ˇtwo one✅ four three six five ˇ");
4494 cx.update_editor(|e, cx| {
4495 e.handle_input("( ", cx);
4496 e.paste(&Paste, cx);
4497 e.handle_input(") ", cx);
4498 });
4499 cx.assert_editor_state(
4500 &([
4501 "( one✅ ",
4502 "three ",
4503 "five ) ˇtwo one✅ four three six five ( one✅ ",
4504 "three ",
4505 "five ) ˇ",
4506 ]
4507 .join("\n")),
4508 );
4509
4510 // Cut with three selections, one of which is full-line.
4511 cx.set_state(indoc! {"
4512 1«2ˇ»3
4513 4ˇ567
4514 «8ˇ»9"});
4515 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4516 cx.assert_editor_state(indoc! {"
4517 1ˇ3
4518 ˇ9"});
4519
4520 // Paste with three selections, noticing how the copied selection that was full-line
4521 // gets inserted before the second cursor.
4522 cx.set_state(indoc! {"
4523 1ˇ3
4524 9ˇ
4525 «oˇ»ne"});
4526 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4527 cx.assert_editor_state(indoc! {"
4528 12ˇ3
4529 4567
4530 9ˇ
4531 8ˇne"});
4532
4533 // Copy with a single cursor only, which writes the whole line into the clipboard.
4534 cx.set_state(indoc! {"
4535 The quick brown
4536 fox juˇmps over
4537 the lazy dog"});
4538 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4539 assert_eq!(
4540 cx.read_from_clipboard()
4541 .and_then(|item| item.text().as_deref().map(str::to_string)),
4542 Some("fox jumps over\n".to_string())
4543 );
4544
4545 // Paste with three selections, noticing how the copied full-line selection is inserted
4546 // before the empty selections but replaces the selection that is non-empty.
4547 cx.set_state(indoc! {"
4548 Tˇhe quick brown
4549 «foˇ»x jumps over
4550 tˇhe lazy dog"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 fox jumps over
4554 Tˇhe quick brown
4555 fox jumps over
4556 ˇx jumps over
4557 fox jumps over
4558 tˇhe lazy dog"});
4559}
4560
4561#[gpui::test]
4562async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4563 init_test(cx, |_| {});
4564
4565 let mut cx = EditorTestContext::new(cx).await;
4566 let language = Arc::new(Language::new(
4567 LanguageConfig::default(),
4568 Some(tree_sitter_rust::LANGUAGE.into()),
4569 ));
4570 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4571
4572 // Cut an indented block, without the leading whitespace.
4573 cx.set_state(indoc! {"
4574 const a: B = (
4575 c(),
4576 «d(
4577 e,
4578 f
4579 )ˇ»
4580 );
4581 "});
4582 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4583 cx.assert_editor_state(indoc! {"
4584 const a: B = (
4585 c(),
4586 ˇ
4587 );
4588 "});
4589
4590 // Paste it at the same position.
4591 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4592 cx.assert_editor_state(indoc! {"
4593 const a: B = (
4594 c(),
4595 d(
4596 e,
4597 f
4598 )ˇ
4599 );
4600 "});
4601
4602 // Paste it at a line with a lower indent level.
4603 cx.set_state(indoc! {"
4604 ˇ
4605 const a: B = (
4606 c(),
4607 );
4608 "});
4609 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4610 cx.assert_editor_state(indoc! {"
4611 d(
4612 e,
4613 f
4614 )ˇ
4615 const a: B = (
4616 c(),
4617 );
4618 "});
4619
4620 // Cut an indented block, with the leading whitespace.
4621 cx.set_state(indoc! {"
4622 const a: B = (
4623 c(),
4624 « d(
4625 e,
4626 f
4627 )
4628 ˇ»);
4629 "});
4630 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4631 cx.assert_editor_state(indoc! {"
4632 const a: B = (
4633 c(),
4634 ˇ);
4635 "});
4636
4637 // Paste it at the same position.
4638 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4639 cx.assert_editor_state(indoc! {"
4640 const a: B = (
4641 c(),
4642 d(
4643 e,
4644 f
4645 )
4646 ˇ);
4647 "});
4648
4649 // Paste it at a line with a higher indent level.
4650 cx.set_state(indoc! {"
4651 const a: B = (
4652 c(),
4653 d(
4654 e,
4655 fˇ
4656 )
4657 );
4658 "});
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 d(
4666 e,
4667 f
4668 )
4669 ˇ
4670 )
4671 );
4672 "});
4673}
4674
4675#[gpui::test]
4676fn test_select_all(cx: &mut TestAppContext) {
4677 init_test(cx, |_| {});
4678
4679 let view = cx.add_window(|cx| {
4680 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4681 build_editor(buffer, cx)
4682 });
4683 _ = view.update(cx, |view, cx| {
4684 view.select_all(&SelectAll, cx);
4685 assert_eq!(
4686 view.selections.display_ranges(cx),
4687 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4688 );
4689 });
4690}
4691
4692#[gpui::test]
4693fn test_select_line(cx: &mut TestAppContext) {
4694 init_test(cx, |_| {});
4695
4696 let view = cx.add_window(|cx| {
4697 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4698 build_editor(buffer, cx)
4699 });
4700 _ = view.update(cx, |view, cx| {
4701 view.change_selections(None, cx, |s| {
4702 s.select_display_ranges([
4703 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4704 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4705 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4706 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4707 ])
4708 });
4709 view.select_line(&SelectLine, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 vec![
4713 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4715 ]
4716 );
4717 });
4718
4719 _ = view.update(cx, |view, cx| {
4720 view.select_line(&SelectLine, cx);
4721 assert_eq!(
4722 view.selections.display_ranges(cx),
4723 vec![
4724 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4725 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4726 ]
4727 );
4728 });
4729
4730 _ = view.update(cx, |view, cx| {
4731 view.select_line(&SelectLine, cx);
4732 assert_eq!(
4733 view.selections.display_ranges(cx),
4734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4735 );
4736 });
4737}
4738
4739#[gpui::test]
4740fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4741 init_test(cx, |_| {});
4742
4743 let view = cx.add_window(|cx| {
4744 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4745 build_editor(buffer, cx)
4746 });
4747 _ = view.update(cx, |view, cx| {
4748 view.fold_creases(
4749 vec![
4750 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4751 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4752 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4753 ],
4754 true,
4755 cx,
4756 );
4757 view.change_selections(None, cx, |s| {
4758 s.select_display_ranges([
4759 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4760 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4761 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4762 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4763 ])
4764 });
4765 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4766 });
4767
4768 _ = view.update(cx, |view, cx| {
4769 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4770 assert_eq!(
4771 view.display_text(cx),
4772 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4773 );
4774 assert_eq!(
4775 view.selections.display_ranges(cx),
4776 [
4777 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4778 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4779 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4780 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4781 ]
4782 );
4783 });
4784
4785 _ = view.update(cx, |view, cx| {
4786 view.change_selections(None, cx, |s| {
4787 s.select_display_ranges([
4788 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4789 ])
4790 });
4791 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4792 assert_eq!(
4793 view.display_text(cx),
4794 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4795 );
4796 assert_eq!(
4797 view.selections.display_ranges(cx),
4798 [
4799 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4800 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4801 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4802 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4803 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4804 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4805 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4806 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4807 ]
4808 );
4809 });
4810}
4811
4812#[gpui::test]
4813async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4814 init_test(cx, |_| {});
4815
4816 let mut cx = EditorTestContext::new(cx).await;
4817
4818 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4819 cx.set_state(indoc!(
4820 r#"abc
4821 defˇghi
4822
4823 jk
4824 nlmo
4825 "#
4826 ));
4827
4828 cx.update_editor(|editor, cx| {
4829 editor.add_selection_above(&Default::default(), cx);
4830 });
4831
4832 cx.assert_editor_state(indoc!(
4833 r#"abcˇ
4834 defˇghi
4835
4836 jk
4837 nlmo
4838 "#
4839 ));
4840
4841 cx.update_editor(|editor, cx| {
4842 editor.add_selection_above(&Default::default(), cx);
4843 });
4844
4845 cx.assert_editor_state(indoc!(
4846 r#"abcˇ
4847 defˇghi
4848
4849 jk
4850 nlmo
4851 "#
4852 ));
4853
4854 cx.update_editor(|view, cx| {
4855 view.add_selection_below(&Default::default(), cx);
4856 });
4857
4858 cx.assert_editor_state(indoc!(
4859 r#"abc
4860 defˇghi
4861
4862 jk
4863 nlmo
4864 "#
4865 ));
4866
4867 cx.update_editor(|view, cx| {
4868 view.undo_selection(&Default::default(), cx);
4869 });
4870
4871 cx.assert_editor_state(indoc!(
4872 r#"abcˇ
4873 defˇghi
4874
4875 jk
4876 nlmo
4877 "#
4878 ));
4879
4880 cx.update_editor(|view, cx| {
4881 view.redo_selection(&Default::default(), cx);
4882 });
4883
4884 cx.assert_editor_state(indoc!(
4885 r#"abc
4886 defˇghi
4887
4888 jk
4889 nlmo
4890 "#
4891 ));
4892
4893 cx.update_editor(|view, cx| {
4894 view.add_selection_below(&Default::default(), cx);
4895 });
4896
4897 cx.assert_editor_state(indoc!(
4898 r#"abc
4899 defˇghi
4900
4901 jk
4902 nlmˇo
4903 "#
4904 ));
4905
4906 cx.update_editor(|view, cx| {
4907 view.add_selection_below(&Default::default(), cx);
4908 });
4909
4910 cx.assert_editor_state(indoc!(
4911 r#"abc
4912 defˇghi
4913
4914 jk
4915 nlmˇo
4916 "#
4917 ));
4918
4919 // change selections
4920 cx.set_state(indoc!(
4921 r#"abc
4922 def«ˇg»hi
4923
4924 jk
4925 nlmo
4926 "#
4927 ));
4928
4929 cx.update_editor(|view, cx| {
4930 view.add_selection_below(&Default::default(), cx);
4931 });
4932
4933 cx.assert_editor_state(indoc!(
4934 r#"abc
4935 def«ˇg»hi
4936
4937 jk
4938 nlm«ˇo»
4939 "#
4940 ));
4941
4942 cx.update_editor(|view, cx| {
4943 view.add_selection_below(&Default::default(), cx);
4944 });
4945
4946 cx.assert_editor_state(indoc!(
4947 r#"abc
4948 def«ˇg»hi
4949
4950 jk
4951 nlm«ˇo»
4952 "#
4953 ));
4954
4955 cx.update_editor(|view, cx| {
4956 view.add_selection_above(&Default::default(), cx);
4957 });
4958
4959 cx.assert_editor_state(indoc!(
4960 r#"abc
4961 def«ˇg»hi
4962
4963 jk
4964 nlmo
4965 "#
4966 ));
4967
4968 cx.update_editor(|view, cx| {
4969 view.add_selection_above(&Default::default(), cx);
4970 });
4971
4972 cx.assert_editor_state(indoc!(
4973 r#"abc
4974 def«ˇg»hi
4975
4976 jk
4977 nlmo
4978 "#
4979 ));
4980
4981 // Change selections again
4982 cx.set_state(indoc!(
4983 r#"a«bc
4984 defgˇ»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_below(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"a«bcˇ»
4997 d«efgˇ»hi
4998
4999 j«kˇ»
5000 nlmo
5001 "#
5002 ));
5003
5004 cx.update_editor(|view, cx| {
5005 view.add_selection_below(&Default::default(), cx);
5006 });
5007 cx.assert_editor_state(indoc!(
5008 r#"a«bcˇ»
5009 d«efgˇ»hi
5010
5011 j«kˇ»
5012 n«lmoˇ»
5013 "#
5014 ));
5015 cx.update_editor(|view, cx| {
5016 view.add_selection_above(&Default::default(), cx);
5017 });
5018
5019 cx.assert_editor_state(indoc!(
5020 r#"a«bcˇ»
5021 d«efgˇ»hi
5022
5023 j«kˇ»
5024 nlmo
5025 "#
5026 ));
5027
5028 // Change selections again
5029 cx.set_state(indoc!(
5030 r#"abc
5031 d«ˇefghi
5032
5033 jk
5034 nlm»o
5035 "#
5036 ));
5037
5038 cx.update_editor(|view, cx| {
5039 view.add_selection_above(&Default::default(), cx);
5040 });
5041
5042 cx.assert_editor_state(indoc!(
5043 r#"a«ˇbc»
5044 d«ˇef»ghi
5045
5046 j«ˇk»
5047 n«ˇlm»o
5048 "#
5049 ));
5050
5051 cx.update_editor(|view, cx| {
5052 view.add_selection_below(&Default::default(), cx);
5053 });
5054
5055 cx.assert_editor_state(indoc!(
5056 r#"abc
5057 d«ˇef»ghi
5058
5059 j«ˇk»
5060 n«ˇlm»o
5061 "#
5062 ));
5063}
5064
5065#[gpui::test]
5066async fn test_select_next(cx: &mut gpui::TestAppContext) {
5067 init_test(cx, |_| {});
5068
5069 let mut cx = EditorTestContext::new(cx).await;
5070 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5071
5072 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5073 .unwrap();
5074 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5075
5076 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5077 .unwrap();
5078 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5079
5080 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5081 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5082
5083 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5084 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5085
5086 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5087 .unwrap();
5088 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5089
5090 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5091 .unwrap();
5092 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5093}
5094
5095#[gpui::test]
5096async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5097 init_test(cx, |_| {});
5098
5099 let mut cx = EditorTestContext::new(cx).await;
5100 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5101
5102 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5103 .unwrap();
5104 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5105}
5106
5107#[gpui::test]
5108async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5109 init_test(cx, |_| {});
5110
5111 let mut cx = EditorTestContext::new(cx).await;
5112 cx.set_state(
5113 r#"let foo = 2;
5114lˇet foo = 2;
5115let fooˇ = 2;
5116let foo = 2;
5117let foo = ˇ2;"#,
5118 );
5119
5120 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5121 .unwrap();
5122 cx.assert_editor_state(
5123 r#"let foo = 2;
5124«letˇ» foo = 2;
5125let «fooˇ» = 2;
5126let foo = 2;
5127let foo = «2ˇ»;"#,
5128 );
5129
5130 // noop for multiple selections with different contents
5131 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5132 .unwrap();
5133 cx.assert_editor_state(
5134 r#"let foo = 2;
5135«letˇ» foo = 2;
5136let «fooˇ» = 2;
5137let foo = 2;
5138let foo = «2ˇ»;"#,
5139 );
5140}
5141
5142#[gpui::test]
5143async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5144 init_test(cx, |_| {});
5145
5146 let mut cx =
5147 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5148
5149 cx.assert_editor_state(indoc! {"
5150 ˇbbb
5151 ccc
5152
5153 bbb
5154 ccc
5155 "});
5156 cx.dispatch_action(SelectPrevious::default());
5157 cx.assert_editor_state(indoc! {"
5158 «bbbˇ»
5159 ccc
5160
5161 bbb
5162 ccc
5163 "});
5164 cx.dispatch_action(SelectPrevious::default());
5165 cx.assert_editor_state(indoc! {"
5166 «bbbˇ»
5167 ccc
5168
5169 «bbbˇ»
5170 ccc
5171 "});
5172}
5173
5174#[gpui::test]
5175async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5176 init_test(cx, |_| {});
5177
5178 let mut cx = EditorTestContext::new(cx).await;
5179 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5180
5181 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5182 .unwrap();
5183 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5184
5185 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5186 .unwrap();
5187 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5188
5189 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5190 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5191
5192 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5193 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5194
5195 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5196 .unwrap();
5197 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5198
5199 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5200 .unwrap();
5201 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5202
5203 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5204 .unwrap();
5205 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5206}
5207
5208#[gpui::test]
5209async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5210 init_test(cx, |_| {});
5211
5212 let mut cx = EditorTestContext::new(cx).await;
5213 cx.set_state(
5214 r#"let foo = 2;
5215lˇet foo = 2;
5216let fooˇ = 2;
5217let foo = 2;
5218let foo = ˇ2;"#,
5219 );
5220
5221 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5222 .unwrap();
5223 cx.assert_editor_state(
5224 r#"let foo = 2;
5225«letˇ» foo = 2;
5226let «fooˇ» = 2;
5227let foo = 2;
5228let foo = «2ˇ»;"#,
5229 );
5230
5231 // noop for multiple selections with different contents
5232 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5233 .unwrap();
5234 cx.assert_editor_state(
5235 r#"let foo = 2;
5236«letˇ» foo = 2;
5237let «fooˇ» = 2;
5238let foo = 2;
5239let foo = «2ˇ»;"#,
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5249
5250 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5251 .unwrap();
5252 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5253
5254 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5255 .unwrap();
5256 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5257
5258 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5259 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5260
5261 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5262 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5263
5264 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5265 .unwrap();
5266 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5267
5268 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5269 .unwrap();
5270 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5271}
5272
5273#[gpui::test]
5274async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5275 init_test(cx, |_| {});
5276
5277 let language = Arc::new(Language::new(
5278 LanguageConfig::default(),
5279 Some(tree_sitter_rust::LANGUAGE.into()),
5280 ));
5281
5282 let text = r#"
5283 use mod1::mod2::{mod3, mod4};
5284
5285 fn fn_1(param1: bool, param2: &str) {
5286 let var1 = "text";
5287 }
5288 "#
5289 .unindent();
5290
5291 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5292 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5293 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5294
5295 editor
5296 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5297 .await;
5298
5299 editor.update(cx, |view, cx| {
5300 view.change_selections(None, cx, |s| {
5301 s.select_display_ranges([
5302 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5303 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5304 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5305 ]);
5306 });
5307 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5308 });
5309 editor.update(cx, |editor, cx| {
5310 assert_text_with_selections(
5311 editor,
5312 indoc! {r#"
5313 use mod1::mod2::{mod3, «mod4ˇ»};
5314
5315 fn fn_1«ˇ(param1: bool, param2: &str)» {
5316 let var1 = "«textˇ»";
5317 }
5318 "#},
5319 cx,
5320 );
5321 });
5322
5323 editor.update(cx, |view, cx| {
5324 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5325 });
5326 editor.update(cx, |editor, cx| {
5327 assert_text_with_selections(
5328 editor,
5329 indoc! {r#"
5330 use mod1::mod2::«{mod3, mod4}ˇ»;
5331
5332 «ˇfn fn_1(param1: bool, param2: &str) {
5333 let var1 = "text";
5334 }»
5335 "#},
5336 cx,
5337 );
5338 });
5339
5340 editor.update(cx, |view, cx| {
5341 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5342 });
5343 assert_eq!(
5344 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5345 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5346 );
5347
5348 // Trying to expand the selected syntax node one more time has no effect.
5349 editor.update(cx, |view, cx| {
5350 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5351 });
5352 assert_eq!(
5353 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5354 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5355 );
5356
5357 editor.update(cx, |view, cx| {
5358 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5359 });
5360 editor.update(cx, |editor, cx| {
5361 assert_text_with_selections(
5362 editor,
5363 indoc! {r#"
5364 use mod1::mod2::«{mod3, mod4}ˇ»;
5365
5366 «ˇfn fn_1(param1: bool, param2: &str) {
5367 let var1 = "text";
5368 }»
5369 "#},
5370 cx,
5371 );
5372 });
5373
5374 editor.update(cx, |view, cx| {
5375 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5376 });
5377 editor.update(cx, |editor, cx| {
5378 assert_text_with_selections(
5379 editor,
5380 indoc! {r#"
5381 use mod1::mod2::{mod3, «mod4ˇ»};
5382
5383 fn fn_1«ˇ(param1: bool, param2: &str)» {
5384 let var1 = "«textˇ»";
5385 }
5386 "#},
5387 cx,
5388 );
5389 });
5390
5391 editor.update(cx, |view, cx| {
5392 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5393 });
5394 editor.update(cx, |editor, cx| {
5395 assert_text_with_selections(
5396 editor,
5397 indoc! {r#"
5398 use mod1::mod2::{mod3, mo«ˇ»d4};
5399
5400 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5401 let var1 = "te«ˇ»xt";
5402 }
5403 "#},
5404 cx,
5405 );
5406 });
5407
5408 // Trying to shrink the selected syntax node one more time has no effect.
5409 editor.update(cx, |view, cx| {
5410 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5411 });
5412 editor.update(cx, |editor, cx| {
5413 assert_text_with_selections(
5414 editor,
5415 indoc! {r#"
5416 use mod1::mod2::{mod3, mo«ˇ»d4};
5417
5418 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5419 let var1 = "te«ˇ»xt";
5420 }
5421 "#},
5422 cx,
5423 );
5424 });
5425
5426 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5427 // a fold.
5428 editor.update(cx, |view, cx| {
5429 view.fold_creases(
5430 vec![
5431 Crease::simple(
5432 Point::new(0, 21)..Point::new(0, 24),
5433 FoldPlaceholder::test(),
5434 ),
5435 Crease::simple(
5436 Point::new(3, 20)..Point::new(3, 22),
5437 FoldPlaceholder::test(),
5438 ),
5439 ],
5440 true,
5441 cx,
5442 );
5443 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5444 });
5445 editor.update(cx, |editor, cx| {
5446 assert_text_with_selections(
5447 editor,
5448 indoc! {r#"
5449 use mod1::mod2::«{mod3, mod4}ˇ»;
5450
5451 fn fn_1«ˇ(param1: bool, param2: &str)» {
5452 «let var1 = "text";ˇ»
5453 }
5454 "#},
5455 cx,
5456 );
5457 });
5458}
5459
5460#[gpui::test]
5461async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5462 init_test(cx, |_| {});
5463
5464 let language = Arc::new(
5465 Language::new(
5466 LanguageConfig {
5467 brackets: BracketPairConfig {
5468 pairs: vec![
5469 BracketPair {
5470 start: "{".to_string(),
5471 end: "}".to_string(),
5472 close: false,
5473 surround: false,
5474 newline: true,
5475 },
5476 BracketPair {
5477 start: "(".to_string(),
5478 end: ")".to_string(),
5479 close: false,
5480 surround: false,
5481 newline: true,
5482 },
5483 ],
5484 ..Default::default()
5485 },
5486 ..Default::default()
5487 },
5488 Some(tree_sitter_rust::LANGUAGE.into()),
5489 )
5490 .with_indents_query(
5491 r#"
5492 (_ "(" ")" @end) @indent
5493 (_ "{" "}" @end) @indent
5494 "#,
5495 )
5496 .unwrap(),
5497 );
5498
5499 let text = "fn a() {}";
5500
5501 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5502 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5503 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5504 editor
5505 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5506 .await;
5507
5508 editor.update(cx, |editor, cx| {
5509 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5510 editor.newline(&Newline, cx);
5511 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5512 assert_eq!(
5513 editor.selections.ranges(cx),
5514 &[
5515 Point::new(1, 4)..Point::new(1, 4),
5516 Point::new(3, 4)..Point::new(3, 4),
5517 Point::new(5, 0)..Point::new(5, 0)
5518 ]
5519 );
5520 });
5521}
5522
5523#[gpui::test]
5524async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5525 init_test(cx, |_| {});
5526
5527 {
5528 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5529 cx.set_state(indoc! {"
5530 impl A {
5531
5532 fn b() {}
5533
5534 «fn c() {
5535
5536 }ˇ»
5537 }
5538 "});
5539
5540 cx.update_editor(|editor, cx| {
5541 editor.autoindent(&Default::default(), cx);
5542 });
5543
5544 cx.assert_editor_state(indoc! {"
5545 impl A {
5546
5547 fn b() {}
5548
5549 «fn c() {
5550
5551 }ˇ»
5552 }
5553 "});
5554 }
5555
5556 {
5557 let mut cx = EditorTestContext::new_multibuffer(
5558 cx,
5559 [indoc! { "
5560 impl A {
5561 «
5562 // a
5563 fn b(){}
5564 »
5565 «
5566 }
5567 fn c(){}
5568 »
5569 "}],
5570 );
5571
5572 let buffer = cx.update_editor(|editor, cx| {
5573 let buffer = editor.buffer().update(cx, |buffer, _| {
5574 buffer.all_buffers().iter().next().unwrap().clone()
5575 });
5576 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5577 buffer
5578 });
5579
5580 cx.run_until_parked();
5581 cx.update_editor(|editor, cx| {
5582 editor.select_all(&Default::default(), cx);
5583 editor.autoindent(&Default::default(), cx)
5584 });
5585 cx.run_until_parked();
5586
5587 cx.update(|cx| {
5588 pretty_assertions::assert_eq!(
5589 buffer.read(cx).text(),
5590 indoc! { "
5591 impl A {
5592
5593 // a
5594 fn b(){}
5595
5596
5597 }
5598 fn c(){}
5599
5600 " }
5601 )
5602 });
5603 }
5604}
5605
5606#[gpui::test]
5607async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5608 init_test(cx, |_| {});
5609
5610 let mut cx = EditorTestContext::new(cx).await;
5611
5612 let language = Arc::new(Language::new(
5613 LanguageConfig {
5614 brackets: BracketPairConfig {
5615 pairs: vec![
5616 BracketPair {
5617 start: "{".to_string(),
5618 end: "}".to_string(),
5619 close: true,
5620 surround: true,
5621 newline: true,
5622 },
5623 BracketPair {
5624 start: "(".to_string(),
5625 end: ")".to_string(),
5626 close: true,
5627 surround: true,
5628 newline: true,
5629 },
5630 BracketPair {
5631 start: "/*".to_string(),
5632 end: " */".to_string(),
5633 close: true,
5634 surround: true,
5635 newline: true,
5636 },
5637 BracketPair {
5638 start: "[".to_string(),
5639 end: "]".to_string(),
5640 close: false,
5641 surround: false,
5642 newline: true,
5643 },
5644 BracketPair {
5645 start: "\"".to_string(),
5646 end: "\"".to_string(),
5647 close: true,
5648 surround: true,
5649 newline: false,
5650 },
5651 BracketPair {
5652 start: "<".to_string(),
5653 end: ">".to_string(),
5654 close: false,
5655 surround: true,
5656 newline: true,
5657 },
5658 ],
5659 ..Default::default()
5660 },
5661 autoclose_before: "})]".to_string(),
5662 ..Default::default()
5663 },
5664 Some(tree_sitter_rust::LANGUAGE.into()),
5665 ));
5666
5667 cx.language_registry().add(language.clone());
5668 cx.update_buffer(|buffer, cx| {
5669 buffer.set_language(Some(language), cx);
5670 });
5671
5672 cx.set_state(
5673 &r#"
5674 🏀ˇ
5675 εˇ
5676 ❤️ˇ
5677 "#
5678 .unindent(),
5679 );
5680
5681 // autoclose multiple nested brackets at multiple cursors
5682 cx.update_editor(|view, cx| {
5683 view.handle_input("{", cx);
5684 view.handle_input("{", cx);
5685 view.handle_input("{", cx);
5686 });
5687 cx.assert_editor_state(
5688 &"
5689 🏀{{{ˇ}}}
5690 ε{{{ˇ}}}
5691 ❤️{{{ˇ}}}
5692 "
5693 .unindent(),
5694 );
5695
5696 // insert a different closing bracket
5697 cx.update_editor(|view, cx| {
5698 view.handle_input(")", cx);
5699 });
5700 cx.assert_editor_state(
5701 &"
5702 🏀{{{)ˇ}}}
5703 ε{{{)ˇ}}}
5704 ❤️{{{)ˇ}}}
5705 "
5706 .unindent(),
5707 );
5708
5709 // skip over the auto-closed brackets when typing a closing bracket
5710 cx.update_editor(|view, cx| {
5711 view.move_right(&MoveRight, cx);
5712 view.handle_input("}", cx);
5713 view.handle_input("}", cx);
5714 view.handle_input("}", cx);
5715 });
5716 cx.assert_editor_state(
5717 &"
5718 🏀{{{)}}}}ˇ
5719 ε{{{)}}}}ˇ
5720 ❤️{{{)}}}}ˇ
5721 "
5722 .unindent(),
5723 );
5724
5725 // autoclose multi-character pairs
5726 cx.set_state(
5727 &"
5728 ˇ
5729 ˇ
5730 "
5731 .unindent(),
5732 );
5733 cx.update_editor(|view, cx| {
5734 view.handle_input("/", cx);
5735 view.handle_input("*", cx);
5736 });
5737 cx.assert_editor_state(
5738 &"
5739 /*ˇ */
5740 /*ˇ */
5741 "
5742 .unindent(),
5743 );
5744
5745 // one cursor autocloses a multi-character pair, one cursor
5746 // does not autoclose.
5747 cx.set_state(
5748 &"
5749 /ˇ
5750 ˇ
5751 "
5752 .unindent(),
5753 );
5754 cx.update_editor(|view, cx| view.handle_input("*", cx));
5755 cx.assert_editor_state(
5756 &"
5757 /*ˇ */
5758 *ˇ
5759 "
5760 .unindent(),
5761 );
5762
5763 // Don't autoclose if the next character isn't whitespace and isn't
5764 // listed in the language's "autoclose_before" section.
5765 cx.set_state("ˇa b");
5766 cx.update_editor(|view, cx| view.handle_input("{", cx));
5767 cx.assert_editor_state("{ˇa b");
5768
5769 // Don't autoclose if `close` is false for the bracket pair
5770 cx.set_state("ˇ");
5771 cx.update_editor(|view, cx| view.handle_input("[", cx));
5772 cx.assert_editor_state("[ˇ");
5773
5774 // Surround with brackets if text is selected
5775 cx.set_state("«aˇ» b");
5776 cx.update_editor(|view, cx| view.handle_input("{", cx));
5777 cx.assert_editor_state("{«aˇ»} b");
5778
5779 // Autclose pair where the start and end characters are the same
5780 cx.set_state("aˇ");
5781 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5782 cx.assert_editor_state("a\"ˇ\"");
5783 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5784 cx.assert_editor_state("a\"\"ˇ");
5785
5786 // Don't autoclose pair if autoclose is disabled
5787 cx.set_state("ˇ");
5788 cx.update_editor(|view, cx| view.handle_input("<", cx));
5789 cx.assert_editor_state("<ˇ");
5790
5791 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5792 cx.set_state("«aˇ» b");
5793 cx.update_editor(|view, cx| view.handle_input("<", cx));
5794 cx.assert_editor_state("<«aˇ»> b");
5795}
5796
5797#[gpui::test]
5798async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5799 init_test(cx, |settings| {
5800 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5801 });
5802
5803 let mut cx = EditorTestContext::new(cx).await;
5804
5805 let language = Arc::new(Language::new(
5806 LanguageConfig {
5807 brackets: BracketPairConfig {
5808 pairs: vec![
5809 BracketPair {
5810 start: "{".to_string(),
5811 end: "}".to_string(),
5812 close: true,
5813 surround: true,
5814 newline: true,
5815 },
5816 BracketPair {
5817 start: "(".to_string(),
5818 end: ")".to_string(),
5819 close: true,
5820 surround: true,
5821 newline: true,
5822 },
5823 BracketPair {
5824 start: "[".to_string(),
5825 end: "]".to_string(),
5826 close: false,
5827 surround: false,
5828 newline: true,
5829 },
5830 ],
5831 ..Default::default()
5832 },
5833 autoclose_before: "})]".to_string(),
5834 ..Default::default()
5835 },
5836 Some(tree_sitter_rust::LANGUAGE.into()),
5837 ));
5838
5839 cx.language_registry().add(language.clone());
5840 cx.update_buffer(|buffer, cx| {
5841 buffer.set_language(Some(language), cx);
5842 });
5843
5844 cx.set_state(
5845 &"
5846 ˇ
5847 ˇ
5848 ˇ
5849 "
5850 .unindent(),
5851 );
5852
5853 // ensure only matching closing brackets are skipped over
5854 cx.update_editor(|view, cx| {
5855 view.handle_input("}", cx);
5856 view.move_left(&MoveLeft, cx);
5857 view.handle_input(")", cx);
5858 view.move_left(&MoveLeft, cx);
5859 });
5860 cx.assert_editor_state(
5861 &"
5862 ˇ)}
5863 ˇ)}
5864 ˇ)}
5865 "
5866 .unindent(),
5867 );
5868
5869 // skip-over closing brackets at multiple cursors
5870 cx.update_editor(|view, cx| {
5871 view.handle_input(")", cx);
5872 view.handle_input("}", cx);
5873 });
5874 cx.assert_editor_state(
5875 &"
5876 )}ˇ
5877 )}ˇ
5878 )}ˇ
5879 "
5880 .unindent(),
5881 );
5882
5883 // ignore non-close brackets
5884 cx.update_editor(|view, cx| {
5885 view.handle_input("]", cx);
5886 view.move_left(&MoveLeft, cx);
5887 view.handle_input("]", cx);
5888 });
5889 cx.assert_editor_state(
5890 &"
5891 )}]ˇ]
5892 )}]ˇ]
5893 )}]ˇ]
5894 "
5895 .unindent(),
5896 );
5897}
5898
5899#[gpui::test]
5900async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5901 init_test(cx, |_| {});
5902
5903 let mut cx = EditorTestContext::new(cx).await;
5904
5905 let html_language = Arc::new(
5906 Language::new(
5907 LanguageConfig {
5908 name: "HTML".into(),
5909 brackets: BracketPairConfig {
5910 pairs: vec![
5911 BracketPair {
5912 start: "<".into(),
5913 end: ">".into(),
5914 close: true,
5915 ..Default::default()
5916 },
5917 BracketPair {
5918 start: "{".into(),
5919 end: "}".into(),
5920 close: true,
5921 ..Default::default()
5922 },
5923 BracketPair {
5924 start: "(".into(),
5925 end: ")".into(),
5926 close: true,
5927 ..Default::default()
5928 },
5929 ],
5930 ..Default::default()
5931 },
5932 autoclose_before: "})]>".into(),
5933 ..Default::default()
5934 },
5935 Some(tree_sitter_html::language()),
5936 )
5937 .with_injection_query(
5938 r#"
5939 (script_element
5940 (raw_text) @content
5941 (#set! "language" "javascript"))
5942 "#,
5943 )
5944 .unwrap(),
5945 );
5946
5947 let javascript_language = Arc::new(Language::new(
5948 LanguageConfig {
5949 name: "JavaScript".into(),
5950 brackets: BracketPairConfig {
5951 pairs: vec![
5952 BracketPair {
5953 start: "/*".into(),
5954 end: " */".into(),
5955 close: true,
5956 ..Default::default()
5957 },
5958 BracketPair {
5959 start: "{".into(),
5960 end: "}".into(),
5961 close: true,
5962 ..Default::default()
5963 },
5964 BracketPair {
5965 start: "(".into(),
5966 end: ")".into(),
5967 close: true,
5968 ..Default::default()
5969 },
5970 ],
5971 ..Default::default()
5972 },
5973 autoclose_before: "})]>".into(),
5974 ..Default::default()
5975 },
5976 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5977 ));
5978
5979 cx.language_registry().add(html_language.clone());
5980 cx.language_registry().add(javascript_language.clone());
5981
5982 cx.update_buffer(|buffer, cx| {
5983 buffer.set_language(Some(html_language), cx);
5984 });
5985
5986 cx.set_state(
5987 &r#"
5988 <body>ˇ
5989 <script>
5990 var x = 1;ˇ
5991 </script>
5992 </body>ˇ
5993 "#
5994 .unindent(),
5995 );
5996
5997 // Precondition: different languages are active at different locations.
5998 cx.update_editor(|editor, cx| {
5999 let snapshot = editor.snapshot(cx);
6000 let cursors = editor.selections.ranges::<usize>(cx);
6001 let languages = cursors
6002 .iter()
6003 .map(|c| snapshot.language_at(c.start).unwrap().name())
6004 .collect::<Vec<_>>();
6005 assert_eq!(
6006 languages,
6007 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6008 );
6009 });
6010
6011 // Angle brackets autoclose in HTML, but not JavaScript.
6012 cx.update_editor(|editor, cx| {
6013 editor.handle_input("<", cx);
6014 editor.handle_input("a", cx);
6015 });
6016 cx.assert_editor_state(
6017 &r#"
6018 <body><aˇ>
6019 <script>
6020 var x = 1;<aˇ
6021 </script>
6022 </body><aˇ>
6023 "#
6024 .unindent(),
6025 );
6026
6027 // Curly braces and parens autoclose in both HTML and JavaScript.
6028 cx.update_editor(|editor, cx| {
6029 editor.handle_input(" b=", cx);
6030 editor.handle_input("{", cx);
6031 editor.handle_input("c", cx);
6032 editor.handle_input("(", cx);
6033 });
6034 cx.assert_editor_state(
6035 &r#"
6036 <body><a b={c(ˇ)}>
6037 <script>
6038 var x = 1;<a b={c(ˇ)}
6039 </script>
6040 </body><a b={c(ˇ)}>
6041 "#
6042 .unindent(),
6043 );
6044
6045 // Brackets that were already autoclosed are skipped.
6046 cx.update_editor(|editor, cx| {
6047 editor.handle_input(")", cx);
6048 editor.handle_input("d", cx);
6049 editor.handle_input("}", cx);
6050 });
6051 cx.assert_editor_state(
6052 &r#"
6053 <body><a b={c()d}ˇ>
6054 <script>
6055 var x = 1;<a b={c()d}ˇ
6056 </script>
6057 </body><a b={c()d}ˇ>
6058 "#
6059 .unindent(),
6060 );
6061 cx.update_editor(|editor, cx| {
6062 editor.handle_input(">", cx);
6063 });
6064 cx.assert_editor_state(
6065 &r#"
6066 <body><a b={c()d}>ˇ
6067 <script>
6068 var x = 1;<a b={c()d}>ˇ
6069 </script>
6070 </body><a b={c()d}>ˇ
6071 "#
6072 .unindent(),
6073 );
6074
6075 // Reset
6076 cx.set_state(
6077 &r#"
6078 <body>ˇ
6079 <script>
6080 var x = 1;ˇ
6081 </script>
6082 </body>ˇ
6083 "#
6084 .unindent(),
6085 );
6086
6087 cx.update_editor(|editor, cx| {
6088 editor.handle_input("<", cx);
6089 });
6090 cx.assert_editor_state(
6091 &r#"
6092 <body><ˇ>
6093 <script>
6094 var x = 1;<ˇ
6095 </script>
6096 </body><ˇ>
6097 "#
6098 .unindent(),
6099 );
6100
6101 // When backspacing, the closing angle brackets are removed.
6102 cx.update_editor(|editor, cx| {
6103 editor.backspace(&Backspace, cx);
6104 });
6105 cx.assert_editor_state(
6106 &r#"
6107 <body>ˇ
6108 <script>
6109 var x = 1;ˇ
6110 </script>
6111 </body>ˇ
6112 "#
6113 .unindent(),
6114 );
6115
6116 // Block comments autoclose in JavaScript, but not HTML.
6117 cx.update_editor(|editor, cx| {
6118 editor.handle_input("/", cx);
6119 editor.handle_input("*", cx);
6120 });
6121 cx.assert_editor_state(
6122 &r#"
6123 <body>/*ˇ
6124 <script>
6125 var x = 1;/*ˇ */
6126 </script>
6127 </body>/*ˇ
6128 "#
6129 .unindent(),
6130 );
6131}
6132
6133#[gpui::test]
6134async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6135 init_test(cx, |_| {});
6136
6137 let mut cx = EditorTestContext::new(cx).await;
6138
6139 let rust_language = Arc::new(
6140 Language::new(
6141 LanguageConfig {
6142 name: "Rust".into(),
6143 brackets: serde_json::from_value(json!([
6144 { "start": "{", "end": "}", "close": true, "newline": true },
6145 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6146 ]))
6147 .unwrap(),
6148 autoclose_before: "})]>".into(),
6149 ..Default::default()
6150 },
6151 Some(tree_sitter_rust::LANGUAGE.into()),
6152 )
6153 .with_override_query("(string_literal) @string")
6154 .unwrap(),
6155 );
6156
6157 cx.language_registry().add(rust_language.clone());
6158 cx.update_buffer(|buffer, cx| {
6159 buffer.set_language(Some(rust_language), cx);
6160 });
6161
6162 cx.set_state(
6163 &r#"
6164 let x = ˇ
6165 "#
6166 .unindent(),
6167 );
6168
6169 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6170 cx.update_editor(|editor, cx| {
6171 editor.handle_input("\"", cx);
6172 });
6173 cx.assert_editor_state(
6174 &r#"
6175 let x = "ˇ"
6176 "#
6177 .unindent(),
6178 );
6179
6180 // Inserting another quotation mark. The cursor moves across the existing
6181 // automatically-inserted quotation mark.
6182 cx.update_editor(|editor, cx| {
6183 editor.handle_input("\"", cx);
6184 });
6185 cx.assert_editor_state(
6186 &r#"
6187 let x = ""ˇ
6188 "#
6189 .unindent(),
6190 );
6191
6192 // Reset
6193 cx.set_state(
6194 &r#"
6195 let x = ˇ
6196 "#
6197 .unindent(),
6198 );
6199
6200 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6201 cx.update_editor(|editor, cx| {
6202 editor.handle_input("\"", cx);
6203 editor.handle_input(" ", cx);
6204 editor.move_left(&Default::default(), cx);
6205 editor.handle_input("\\", cx);
6206 editor.handle_input("\"", cx);
6207 });
6208 cx.assert_editor_state(
6209 &r#"
6210 let x = "\"ˇ "
6211 "#
6212 .unindent(),
6213 );
6214
6215 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6216 // mark. Nothing is inserted.
6217 cx.update_editor(|editor, cx| {
6218 editor.move_right(&Default::default(), cx);
6219 editor.handle_input("\"", cx);
6220 });
6221 cx.assert_editor_state(
6222 &r#"
6223 let x = "\" "ˇ
6224 "#
6225 .unindent(),
6226 );
6227}
6228
6229#[gpui::test]
6230async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6231 init_test(cx, |_| {});
6232
6233 let language = Arc::new(Language::new(
6234 LanguageConfig {
6235 brackets: BracketPairConfig {
6236 pairs: vec![
6237 BracketPair {
6238 start: "{".to_string(),
6239 end: "}".to_string(),
6240 close: true,
6241 surround: true,
6242 newline: true,
6243 },
6244 BracketPair {
6245 start: "/* ".to_string(),
6246 end: "*/".to_string(),
6247 close: true,
6248 surround: true,
6249 ..Default::default()
6250 },
6251 ],
6252 ..Default::default()
6253 },
6254 ..Default::default()
6255 },
6256 Some(tree_sitter_rust::LANGUAGE.into()),
6257 ));
6258
6259 let text = r#"
6260 a
6261 b
6262 c
6263 "#
6264 .unindent();
6265
6266 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6267 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6268 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6269 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6270 .await;
6271
6272 view.update(cx, |view, cx| {
6273 view.change_selections(None, cx, |s| {
6274 s.select_display_ranges([
6275 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6276 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6277 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6278 ])
6279 });
6280
6281 view.handle_input("{", cx);
6282 view.handle_input("{", cx);
6283 view.handle_input("{", cx);
6284 assert_eq!(
6285 view.text(cx),
6286 "
6287 {{{a}}}
6288 {{{b}}}
6289 {{{c}}}
6290 "
6291 .unindent()
6292 );
6293 assert_eq!(
6294 view.selections.display_ranges(cx),
6295 [
6296 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6297 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6298 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6299 ]
6300 );
6301
6302 view.undo(&Undo, cx);
6303 view.undo(&Undo, cx);
6304 view.undo(&Undo, 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), 0)..DisplayPoint::new(DisplayRow(0), 1),
6318 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6319 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6320 ]
6321 );
6322
6323 // Ensure inserting the first character of a multi-byte bracket pair
6324 // doesn't surround the selections with the bracket.
6325 view.handle_input("/", cx);
6326 assert_eq!(
6327 view.text(cx),
6328 "
6329 /
6330 /
6331 /
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 view.selections.display_ranges(cx),
6337 [
6338 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6339 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6340 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6341 ]
6342 );
6343
6344 view.undo(&Undo, cx);
6345 assert_eq!(
6346 view.text(cx),
6347 "
6348 a
6349 b
6350 c
6351 "
6352 .unindent()
6353 );
6354 assert_eq!(
6355 view.selections.display_ranges(cx),
6356 [
6357 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6358 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6359 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6360 ]
6361 );
6362
6363 // Ensure inserting the last character of a multi-byte bracket pair
6364 // doesn't surround the selections with the bracket.
6365 view.handle_input("*", cx);
6366 assert_eq!(
6367 view.text(cx),
6368 "
6369 *
6370 *
6371 *
6372 "
6373 .unindent()
6374 );
6375 assert_eq!(
6376 view.selections.display_ranges(cx),
6377 [
6378 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6379 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6380 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6381 ]
6382 );
6383 });
6384}
6385
6386#[gpui::test]
6387async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6388 init_test(cx, |_| {});
6389
6390 let language = Arc::new(Language::new(
6391 LanguageConfig {
6392 brackets: BracketPairConfig {
6393 pairs: vec![BracketPair {
6394 start: "{".to_string(),
6395 end: "}".to_string(),
6396 close: true,
6397 surround: true,
6398 newline: true,
6399 }],
6400 ..Default::default()
6401 },
6402 autoclose_before: "}".to_string(),
6403 ..Default::default()
6404 },
6405 Some(tree_sitter_rust::LANGUAGE.into()),
6406 ));
6407
6408 let text = r#"
6409 a
6410 b
6411 c
6412 "#
6413 .unindent();
6414
6415 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6416 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6417 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6418 editor
6419 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6420 .await;
6421
6422 editor.update(cx, |editor, cx| {
6423 editor.change_selections(None, cx, |s| {
6424 s.select_ranges([
6425 Point::new(0, 1)..Point::new(0, 1),
6426 Point::new(1, 1)..Point::new(1, 1),
6427 Point::new(2, 1)..Point::new(2, 1),
6428 ])
6429 });
6430
6431 editor.handle_input("{", cx);
6432 editor.handle_input("{", cx);
6433 editor.handle_input("_", cx);
6434 assert_eq!(
6435 editor.text(cx),
6436 "
6437 a{{_}}
6438 b{{_}}
6439 c{{_}}
6440 "
6441 .unindent()
6442 );
6443 assert_eq!(
6444 editor.selections.ranges::<Point>(cx),
6445 [
6446 Point::new(0, 4)..Point::new(0, 4),
6447 Point::new(1, 4)..Point::new(1, 4),
6448 Point::new(2, 4)..Point::new(2, 4)
6449 ]
6450 );
6451
6452 editor.backspace(&Default::default(), cx);
6453 editor.backspace(&Default::default(), cx);
6454 assert_eq!(
6455 editor.text(cx),
6456 "
6457 a{}
6458 b{}
6459 c{}
6460 "
6461 .unindent()
6462 );
6463 assert_eq!(
6464 editor.selections.ranges::<Point>(cx),
6465 [
6466 Point::new(0, 2)..Point::new(0, 2),
6467 Point::new(1, 2)..Point::new(1, 2),
6468 Point::new(2, 2)..Point::new(2, 2)
6469 ]
6470 );
6471
6472 editor.delete_to_previous_word_start(&Default::default(), cx);
6473 assert_eq!(
6474 editor.text(cx),
6475 "
6476 a
6477 b
6478 c
6479 "
6480 .unindent()
6481 );
6482 assert_eq!(
6483 editor.selections.ranges::<Point>(cx),
6484 [
6485 Point::new(0, 1)..Point::new(0, 1),
6486 Point::new(1, 1)..Point::new(1, 1),
6487 Point::new(2, 1)..Point::new(2, 1)
6488 ]
6489 );
6490 });
6491}
6492
6493#[gpui::test]
6494async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6495 init_test(cx, |settings| {
6496 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6497 });
6498
6499 let mut cx = EditorTestContext::new(cx).await;
6500
6501 let language = Arc::new(Language::new(
6502 LanguageConfig {
6503 brackets: BracketPairConfig {
6504 pairs: vec![
6505 BracketPair {
6506 start: "{".to_string(),
6507 end: "}".to_string(),
6508 close: true,
6509 surround: true,
6510 newline: true,
6511 },
6512 BracketPair {
6513 start: "(".to_string(),
6514 end: ")".to_string(),
6515 close: true,
6516 surround: true,
6517 newline: true,
6518 },
6519 BracketPair {
6520 start: "[".to_string(),
6521 end: "]".to_string(),
6522 close: false,
6523 surround: true,
6524 newline: true,
6525 },
6526 ],
6527 ..Default::default()
6528 },
6529 autoclose_before: "})]".to_string(),
6530 ..Default::default()
6531 },
6532 Some(tree_sitter_rust::LANGUAGE.into()),
6533 ));
6534
6535 cx.language_registry().add(language.clone());
6536 cx.update_buffer(|buffer, cx| {
6537 buffer.set_language(Some(language), cx);
6538 });
6539
6540 cx.set_state(
6541 &"
6542 {(ˇ)}
6543 [[ˇ]]
6544 {(ˇ)}
6545 "
6546 .unindent(),
6547 );
6548
6549 cx.update_editor(|view, cx| {
6550 view.backspace(&Default::default(), cx);
6551 view.backspace(&Default::default(), cx);
6552 });
6553
6554 cx.assert_editor_state(
6555 &"
6556 ˇ
6557 ˇ]]
6558 ˇ
6559 "
6560 .unindent(),
6561 );
6562
6563 cx.update_editor(|view, cx| {
6564 view.handle_input("{", cx);
6565 view.handle_input("{", cx);
6566 view.move_right(&MoveRight, cx);
6567 view.move_right(&MoveRight, cx);
6568 view.move_left(&MoveLeft, cx);
6569 view.move_left(&MoveLeft, cx);
6570 view.backspace(&Default::default(), cx);
6571 });
6572
6573 cx.assert_editor_state(
6574 &"
6575 {ˇ}
6576 {ˇ}]]
6577 {ˇ}
6578 "
6579 .unindent(),
6580 );
6581
6582 cx.update_editor(|view, cx| {
6583 view.backspace(&Default::default(), cx);
6584 });
6585
6586 cx.assert_editor_state(
6587 &"
6588 ˇ
6589 ˇ]]
6590 ˇ
6591 "
6592 .unindent(),
6593 );
6594}
6595
6596#[gpui::test]
6597async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6598 init_test(cx, |_| {});
6599
6600 let language = Arc::new(Language::new(
6601 LanguageConfig::default(),
6602 Some(tree_sitter_rust::LANGUAGE.into()),
6603 ));
6604
6605 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6606 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6607 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6608 editor
6609 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6610 .await;
6611
6612 editor.update(cx, |editor, cx| {
6613 editor.set_auto_replace_emoji_shortcode(true);
6614
6615 editor.handle_input("Hello ", cx);
6616 editor.handle_input(":wave", cx);
6617 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6618
6619 editor.handle_input(":", cx);
6620 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6621
6622 editor.handle_input(" :smile", cx);
6623 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6624
6625 editor.handle_input(":", cx);
6626 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6627
6628 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6629 editor.handle_input(":wave", cx);
6630 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6631
6632 editor.handle_input(":", cx);
6633 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6634
6635 editor.handle_input(":1", cx);
6636 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6637
6638 editor.handle_input(":", cx);
6639 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6640
6641 // Ensure shortcode does not get replaced when it is part of a word
6642 editor.handle_input(" Test:wave", cx);
6643 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6644
6645 editor.handle_input(":", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6647
6648 editor.set_auto_replace_emoji_shortcode(false);
6649
6650 // Ensure shortcode does not get replaced when auto replace is off
6651 editor.handle_input(" :wave", cx);
6652 assert_eq!(
6653 editor.text(cx),
6654 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6655 );
6656
6657 editor.handle_input(":", cx);
6658 assert_eq!(
6659 editor.text(cx),
6660 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6661 );
6662 });
6663}
6664
6665#[gpui::test]
6666async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6667 init_test(cx, |_| {});
6668
6669 let (text, insertion_ranges) = marked_text_ranges(
6670 indoc! {"
6671 ˇ
6672 "},
6673 false,
6674 );
6675
6676 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6677 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6678
6679 _ = editor.update(cx, |editor, cx| {
6680 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6681
6682 editor
6683 .insert_snippet(&insertion_ranges, snippet, cx)
6684 .unwrap();
6685
6686 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6687 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6688 assert_eq!(editor.text(cx), expected_text);
6689 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6690 }
6691
6692 assert(
6693 editor,
6694 cx,
6695 indoc! {"
6696 type «» =•
6697 "},
6698 );
6699
6700 assert!(editor.context_menu_visible(), "There should be a matches");
6701 });
6702}
6703
6704#[gpui::test]
6705async fn test_snippets(cx: &mut gpui::TestAppContext) {
6706 init_test(cx, |_| {});
6707
6708 let (text, insertion_ranges) = marked_text_ranges(
6709 indoc! {"
6710 a.ˇ b
6711 a.ˇ b
6712 a.ˇ b
6713 "},
6714 false,
6715 );
6716
6717 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6718 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6719
6720 editor.update(cx, |editor, cx| {
6721 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6722
6723 editor
6724 .insert_snippet(&insertion_ranges, snippet, cx)
6725 .unwrap();
6726
6727 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6728 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6729 assert_eq!(editor.text(cx), expected_text);
6730 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6731 }
6732
6733 assert(
6734 editor,
6735 cx,
6736 indoc! {"
6737 a.f(«one», two, «three») b
6738 a.f(«one», two, «three») b
6739 a.f(«one», two, «three») b
6740 "},
6741 );
6742
6743 // Can't move earlier than the first tab stop
6744 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6745 assert(
6746 editor,
6747 cx,
6748 indoc! {"
6749 a.f(«one», two, «three») b
6750 a.f(«one», two, «three») b
6751 a.f(«one», two, «three») b
6752 "},
6753 );
6754
6755 assert!(editor.move_to_next_snippet_tabstop(cx));
6756 assert(
6757 editor,
6758 cx,
6759 indoc! {"
6760 a.f(one, «two», three) b
6761 a.f(one, «two», three) b
6762 a.f(one, «two», three) b
6763 "},
6764 );
6765
6766 editor.move_to_prev_snippet_tabstop(cx);
6767 assert(
6768 editor,
6769 cx,
6770 indoc! {"
6771 a.f(«one», two, «three») b
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 "},
6775 );
6776
6777 assert!(editor.move_to_next_snippet_tabstop(cx));
6778 assert(
6779 editor,
6780 cx,
6781 indoc! {"
6782 a.f(one, «two», three) b
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 "},
6786 );
6787 assert!(editor.move_to_next_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 // As soon as the last tab stop is reached, snippet state is gone
6799 editor.move_to_prev_snippet_tabstop(cx);
6800 assert(
6801 editor,
6802 cx,
6803 indoc! {"
6804 a.f(one, two, three)ˇ b
6805 a.f(one, two, three)ˇ b
6806 a.f(one, two, three)ˇ b
6807 "},
6808 );
6809 });
6810}
6811
6812#[gpui::test]
6813async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6814 init_test(cx, |_| {});
6815
6816 let fs = FakeFs::new(cx.executor());
6817 fs.insert_file("/file.rs", Default::default()).await;
6818
6819 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6820
6821 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6822 language_registry.add(rust_lang());
6823 let mut fake_servers = language_registry.register_fake_lsp(
6824 "Rust",
6825 FakeLspAdapter {
6826 capabilities: lsp::ServerCapabilities {
6827 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6828 ..Default::default()
6829 },
6830 ..Default::default()
6831 },
6832 );
6833
6834 let buffer = project
6835 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6836 .await
6837 .unwrap();
6838
6839 cx.executor().start_waiting();
6840 let fake_server = fake_servers.next().await.unwrap();
6841
6842 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6843 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6844 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6845 assert!(cx.read(|cx| editor.is_dirty(cx)));
6846
6847 let save = editor
6848 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6849 .unwrap();
6850 fake_server
6851 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6852 assert_eq!(
6853 params.text_document.uri,
6854 lsp::Url::from_file_path("/file.rs").unwrap()
6855 );
6856 assert_eq!(params.options.tab_size, 4);
6857 Ok(Some(vec![lsp::TextEdit::new(
6858 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6859 ", ".to_string(),
6860 )]))
6861 })
6862 .next()
6863 .await;
6864 cx.executor().start_waiting();
6865 save.await;
6866
6867 assert_eq!(
6868 editor.update(cx, |editor, cx| editor.text(cx)),
6869 "one, two\nthree\n"
6870 );
6871 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6872
6873 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6874 assert!(cx.read(|cx| editor.is_dirty(cx)));
6875
6876 // Ensure we can still save even if formatting hangs.
6877 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6878 assert_eq!(
6879 params.text_document.uri,
6880 lsp::Url::from_file_path("/file.rs").unwrap()
6881 );
6882 futures::future::pending::<()>().await;
6883 unreachable!()
6884 });
6885 let save = editor
6886 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6887 .unwrap();
6888 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6889 cx.executor().start_waiting();
6890 save.await;
6891 assert_eq!(
6892 editor.update(cx, |editor, cx| editor.text(cx)),
6893 "one\ntwo\nthree\n"
6894 );
6895 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6896
6897 // For non-dirty buffer, no formatting request should be sent
6898 let save = editor
6899 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6900 .unwrap();
6901 let _pending_format_request = fake_server
6902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6903 panic!("Should not be invoked on non-dirty buffer");
6904 })
6905 .next();
6906 cx.executor().start_waiting();
6907 save.await;
6908
6909 // Set rust language override and assert overridden tabsize is sent to language server
6910 update_test_language_settings(cx, |settings| {
6911 settings.languages.insert(
6912 "Rust".into(),
6913 LanguageSettingsContent {
6914 tab_size: NonZeroU32::new(8),
6915 ..Default::default()
6916 },
6917 );
6918 });
6919
6920 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6921 assert!(cx.read(|cx| editor.is_dirty(cx)));
6922 let save = editor
6923 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6924 .unwrap();
6925 fake_server
6926 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6927 assert_eq!(
6928 params.text_document.uri,
6929 lsp::Url::from_file_path("/file.rs").unwrap()
6930 );
6931 assert_eq!(params.options.tab_size, 8);
6932 Ok(Some(vec![]))
6933 })
6934 .next()
6935 .await;
6936 cx.executor().start_waiting();
6937 save.await;
6938}
6939
6940#[gpui::test]
6941async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6942 init_test(cx, |_| {});
6943
6944 let cols = 4;
6945 let rows = 10;
6946 let sample_text_1 = sample_text(rows, cols, 'a');
6947 assert_eq!(
6948 sample_text_1,
6949 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6950 );
6951 let sample_text_2 = sample_text(rows, cols, 'l');
6952 assert_eq!(
6953 sample_text_2,
6954 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6955 );
6956 let sample_text_3 = sample_text(rows, cols, 'v');
6957 assert_eq!(
6958 sample_text_3,
6959 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6960 );
6961
6962 let fs = FakeFs::new(cx.executor());
6963 fs.insert_tree(
6964 "/a",
6965 json!({
6966 "main.rs": sample_text_1,
6967 "other.rs": sample_text_2,
6968 "lib.rs": sample_text_3,
6969 }),
6970 )
6971 .await;
6972
6973 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6974 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6975 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6976
6977 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6978 language_registry.add(rust_lang());
6979 let mut fake_servers = language_registry.register_fake_lsp(
6980 "Rust",
6981 FakeLspAdapter {
6982 capabilities: lsp::ServerCapabilities {
6983 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6984 ..Default::default()
6985 },
6986 ..Default::default()
6987 },
6988 );
6989
6990 let worktree = project.update(cx, |project, cx| {
6991 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6992 assert_eq!(worktrees.len(), 1);
6993 worktrees.pop().unwrap()
6994 });
6995 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6996
6997 let buffer_1 = project
6998 .update(cx, |project, cx| {
6999 project.open_buffer((worktree_id, "main.rs"), cx)
7000 })
7001 .await
7002 .unwrap();
7003 let buffer_2 = project
7004 .update(cx, |project, cx| {
7005 project.open_buffer((worktree_id, "other.rs"), cx)
7006 })
7007 .await
7008 .unwrap();
7009 let buffer_3 = project
7010 .update(cx, |project, cx| {
7011 project.open_buffer((worktree_id, "lib.rs"), cx)
7012 })
7013 .await
7014 .unwrap();
7015
7016 let multi_buffer = cx.new_model(|cx| {
7017 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7018 multi_buffer.push_excerpts(
7019 buffer_1.clone(),
7020 [
7021 ExcerptRange {
7022 context: Point::new(0, 0)..Point::new(3, 0),
7023 primary: None,
7024 },
7025 ExcerptRange {
7026 context: Point::new(5, 0)..Point::new(7, 0),
7027 primary: None,
7028 },
7029 ExcerptRange {
7030 context: Point::new(9, 0)..Point::new(10, 4),
7031 primary: None,
7032 },
7033 ],
7034 cx,
7035 );
7036 multi_buffer.push_excerpts(
7037 buffer_2.clone(),
7038 [
7039 ExcerptRange {
7040 context: Point::new(0, 0)..Point::new(3, 0),
7041 primary: None,
7042 },
7043 ExcerptRange {
7044 context: Point::new(5, 0)..Point::new(7, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(9, 0)..Point::new(10, 4),
7049 primary: None,
7050 },
7051 ],
7052 cx,
7053 );
7054 multi_buffer.push_excerpts(
7055 buffer_3.clone(),
7056 [
7057 ExcerptRange {
7058 context: Point::new(0, 0)..Point::new(3, 0),
7059 primary: None,
7060 },
7061 ExcerptRange {
7062 context: Point::new(5, 0)..Point::new(7, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(9, 0)..Point::new(10, 4),
7067 primary: None,
7068 },
7069 ],
7070 cx,
7071 );
7072 multi_buffer
7073 });
7074 let multi_buffer_editor = cx.new_view(|cx| {
7075 Editor::new(
7076 EditorMode::Full,
7077 multi_buffer,
7078 Some(project.clone()),
7079 true,
7080 cx,
7081 )
7082 });
7083
7084 multi_buffer_editor.update(cx, |editor, cx| {
7085 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7086 editor.insert("|one|two|three|", cx);
7087 });
7088 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7089 multi_buffer_editor.update(cx, |editor, cx| {
7090 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7091 s.select_ranges(Some(60..70))
7092 });
7093 editor.insert("|four|five|six|", cx);
7094 });
7095 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7096
7097 // First two buffers should be edited, but not the third one.
7098 assert_eq!(
7099 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7100 "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}",
7101 );
7102 buffer_1.update(cx, |buffer, _| {
7103 assert!(buffer.is_dirty());
7104 assert_eq!(
7105 buffer.text(),
7106 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7107 )
7108 });
7109 buffer_2.update(cx, |buffer, _| {
7110 assert!(buffer.is_dirty());
7111 assert_eq!(
7112 buffer.text(),
7113 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7114 )
7115 });
7116 buffer_3.update(cx, |buffer, _| {
7117 assert!(!buffer.is_dirty());
7118 assert_eq!(buffer.text(), sample_text_3,)
7119 });
7120
7121 cx.executor().start_waiting();
7122 let save = multi_buffer_editor
7123 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7124 .unwrap();
7125
7126 let fake_server = fake_servers.next().await.unwrap();
7127 fake_server
7128 .server
7129 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7130 Ok(Some(vec![lsp::TextEdit::new(
7131 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7132 format!("[{} formatted]", params.text_document.uri),
7133 )]))
7134 })
7135 .detach();
7136 save.await;
7137
7138 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7139 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7140 assert_eq!(
7141 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7142 "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}",
7143 );
7144 buffer_1.update(cx, |buffer, _| {
7145 assert!(!buffer.is_dirty());
7146 assert_eq!(
7147 buffer.text(),
7148 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7149 )
7150 });
7151 buffer_2.update(cx, |buffer, _| {
7152 assert!(!buffer.is_dirty());
7153 assert_eq!(
7154 buffer.text(),
7155 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7156 )
7157 });
7158 buffer_3.update(cx, |buffer, _| {
7159 assert!(!buffer.is_dirty());
7160 assert_eq!(buffer.text(), sample_text_3,)
7161 });
7162}
7163
7164#[gpui::test]
7165async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7166 init_test(cx, |_| {});
7167
7168 let fs = FakeFs::new(cx.executor());
7169 fs.insert_file("/file.rs", Default::default()).await;
7170
7171 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7172
7173 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7174 language_registry.add(rust_lang());
7175 let mut fake_servers = language_registry.register_fake_lsp(
7176 "Rust",
7177 FakeLspAdapter {
7178 capabilities: lsp::ServerCapabilities {
7179 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7180 ..Default::default()
7181 },
7182 ..Default::default()
7183 },
7184 );
7185
7186 let buffer = project
7187 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7188 .await
7189 .unwrap();
7190
7191 cx.executor().start_waiting();
7192 let fake_server = fake_servers.next().await.unwrap();
7193
7194 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7195 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7196 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7197 assert!(cx.read(|cx| editor.is_dirty(cx)));
7198
7199 let save = editor
7200 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7201 .unwrap();
7202 fake_server
7203 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7204 assert_eq!(
7205 params.text_document.uri,
7206 lsp::Url::from_file_path("/file.rs").unwrap()
7207 );
7208 assert_eq!(params.options.tab_size, 4);
7209 Ok(Some(vec![lsp::TextEdit::new(
7210 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7211 ", ".to_string(),
7212 )]))
7213 })
7214 .next()
7215 .await;
7216 cx.executor().start_waiting();
7217 save.await;
7218 assert_eq!(
7219 editor.update(cx, |editor, cx| editor.text(cx)),
7220 "one, two\nthree\n"
7221 );
7222 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7223
7224 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7225 assert!(cx.read(|cx| editor.is_dirty(cx)));
7226
7227 // Ensure we can still save even if formatting hangs.
7228 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7229 move |params, _| async move {
7230 assert_eq!(
7231 params.text_document.uri,
7232 lsp::Url::from_file_path("/file.rs").unwrap()
7233 );
7234 futures::future::pending::<()>().await;
7235 unreachable!()
7236 },
7237 );
7238 let save = editor
7239 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7240 .unwrap();
7241 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7242 cx.executor().start_waiting();
7243 save.await;
7244 assert_eq!(
7245 editor.update(cx, |editor, cx| editor.text(cx)),
7246 "one\ntwo\nthree\n"
7247 );
7248 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7249
7250 // For non-dirty buffer, no formatting request should be sent
7251 let save = editor
7252 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7253 .unwrap();
7254 let _pending_format_request = fake_server
7255 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7256 panic!("Should not be invoked on non-dirty buffer");
7257 })
7258 .next();
7259 cx.executor().start_waiting();
7260 save.await;
7261
7262 // Set Rust language override and assert overridden tabsize is sent to language server
7263 update_test_language_settings(cx, |settings| {
7264 settings.languages.insert(
7265 "Rust".into(),
7266 LanguageSettingsContent {
7267 tab_size: NonZeroU32::new(8),
7268 ..Default::default()
7269 },
7270 );
7271 });
7272
7273 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7274 assert!(cx.read(|cx| editor.is_dirty(cx)));
7275 let save = editor
7276 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7277 .unwrap();
7278 fake_server
7279 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7280 assert_eq!(
7281 params.text_document.uri,
7282 lsp::Url::from_file_path("/file.rs").unwrap()
7283 );
7284 assert_eq!(params.options.tab_size, 8);
7285 Ok(Some(vec![]))
7286 })
7287 .next()
7288 .await;
7289 cx.executor().start_waiting();
7290 save.await;
7291}
7292
7293#[gpui::test]
7294async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7295 init_test(cx, |settings| {
7296 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7297 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7298 ))
7299 });
7300
7301 let fs = FakeFs::new(cx.executor());
7302 fs.insert_file("/file.rs", Default::default()).await;
7303
7304 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7305
7306 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7307 language_registry.add(Arc::new(Language::new(
7308 LanguageConfig {
7309 name: "Rust".into(),
7310 matcher: LanguageMatcher {
7311 path_suffixes: vec!["rs".to_string()],
7312 ..Default::default()
7313 },
7314 ..LanguageConfig::default()
7315 },
7316 Some(tree_sitter_rust::LANGUAGE.into()),
7317 )));
7318 update_test_language_settings(cx, |settings| {
7319 // Enable Prettier formatting for the same buffer, and ensure
7320 // LSP is called instead of Prettier.
7321 settings.defaults.prettier = Some(PrettierSettings {
7322 allowed: true,
7323 ..PrettierSettings::default()
7324 });
7325 });
7326 let mut fake_servers = language_registry.register_fake_lsp(
7327 "Rust",
7328 FakeLspAdapter {
7329 capabilities: lsp::ServerCapabilities {
7330 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7331 ..Default::default()
7332 },
7333 ..Default::default()
7334 },
7335 );
7336
7337 let buffer = project
7338 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7339 .await
7340 .unwrap();
7341
7342 cx.executor().start_waiting();
7343 let fake_server = fake_servers.next().await.unwrap();
7344
7345 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7346 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7347 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7348
7349 let format = editor
7350 .update(cx, |editor, cx| {
7351 editor.perform_format(
7352 project.clone(),
7353 FormatTrigger::Manual,
7354 FormatTarget::Buffer,
7355 cx,
7356 )
7357 })
7358 .unwrap();
7359 fake_server
7360 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7361 assert_eq!(
7362 params.text_document.uri,
7363 lsp::Url::from_file_path("/file.rs").unwrap()
7364 );
7365 assert_eq!(params.options.tab_size, 4);
7366 Ok(Some(vec![lsp::TextEdit::new(
7367 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7368 ", ".to_string(),
7369 )]))
7370 })
7371 .next()
7372 .await;
7373 cx.executor().start_waiting();
7374 format.await;
7375 assert_eq!(
7376 editor.update(cx, |editor, cx| editor.text(cx)),
7377 "one, two\nthree\n"
7378 );
7379
7380 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7381 // Ensure we don't lock if formatting hangs.
7382 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7383 assert_eq!(
7384 params.text_document.uri,
7385 lsp::Url::from_file_path("/file.rs").unwrap()
7386 );
7387 futures::future::pending::<()>().await;
7388 unreachable!()
7389 });
7390 let format = editor
7391 .update(cx, |editor, cx| {
7392 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7393 })
7394 .unwrap();
7395 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7396 cx.executor().start_waiting();
7397 format.await;
7398 assert_eq!(
7399 editor.update(cx, |editor, cx| editor.text(cx)),
7400 "one\ntwo\nthree\n"
7401 );
7402}
7403
7404#[gpui::test]
7405async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7406 init_test(cx, |_| {});
7407
7408 let mut cx = EditorLspTestContext::new_rust(
7409 lsp::ServerCapabilities {
7410 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7411 ..Default::default()
7412 },
7413 cx,
7414 )
7415 .await;
7416
7417 cx.set_state(indoc! {"
7418 one.twoˇ
7419 "});
7420
7421 // The format request takes a long time. When it completes, it inserts
7422 // a newline and an indent before the `.`
7423 cx.lsp
7424 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7425 let executor = cx.background_executor().clone();
7426 async move {
7427 executor.timer(Duration::from_millis(100)).await;
7428 Ok(Some(vec![lsp::TextEdit {
7429 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7430 new_text: "\n ".into(),
7431 }]))
7432 }
7433 });
7434
7435 // Submit a format request.
7436 let format_1 = cx
7437 .update_editor(|editor, cx| editor.format(&Format, cx))
7438 .unwrap();
7439 cx.executor().run_until_parked();
7440
7441 // Submit a second format request.
7442 let format_2 = cx
7443 .update_editor(|editor, cx| editor.format(&Format, cx))
7444 .unwrap();
7445 cx.executor().run_until_parked();
7446
7447 // Wait for both format requests to complete
7448 cx.executor().advance_clock(Duration::from_millis(200));
7449 cx.executor().start_waiting();
7450 format_1.await.unwrap();
7451 cx.executor().start_waiting();
7452 format_2.await.unwrap();
7453
7454 // The formatting edits only happens once.
7455 cx.assert_editor_state(indoc! {"
7456 one
7457 .twoˇ
7458 "});
7459}
7460
7461#[gpui::test]
7462async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7463 init_test(cx, |settings| {
7464 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7465 });
7466
7467 let mut cx = EditorLspTestContext::new_rust(
7468 lsp::ServerCapabilities {
7469 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7470 ..Default::default()
7471 },
7472 cx,
7473 )
7474 .await;
7475
7476 // Set up a buffer white some trailing whitespace and no trailing newline.
7477 cx.set_state(
7478 &[
7479 "one ", //
7480 "twoˇ", //
7481 "three ", //
7482 "four", //
7483 ]
7484 .join("\n"),
7485 );
7486
7487 // Submit a format request.
7488 let format = cx
7489 .update_editor(|editor, cx| editor.format(&Format, cx))
7490 .unwrap();
7491
7492 // Record which buffer changes have been sent to the language server
7493 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7494 cx.lsp
7495 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7496 let buffer_changes = buffer_changes.clone();
7497 move |params, _| {
7498 buffer_changes.lock().extend(
7499 params
7500 .content_changes
7501 .into_iter()
7502 .map(|e| (e.range.unwrap(), e.text)),
7503 );
7504 }
7505 });
7506
7507 // Handle formatting requests to the language server.
7508 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7509 let buffer_changes = buffer_changes.clone();
7510 move |_, _| {
7511 // When formatting is requested, trailing whitespace has already been stripped,
7512 // and the trailing newline has already been added.
7513 assert_eq!(
7514 &buffer_changes.lock()[1..],
7515 &[
7516 (
7517 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7518 "".into()
7519 ),
7520 (
7521 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7522 "".into()
7523 ),
7524 (
7525 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7526 "\n".into()
7527 ),
7528 ]
7529 );
7530
7531 // Insert blank lines between each line of the buffer.
7532 async move {
7533 Ok(Some(vec![
7534 lsp::TextEdit {
7535 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7536 new_text: "\n".into(),
7537 },
7538 lsp::TextEdit {
7539 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7540 new_text: "\n".into(),
7541 },
7542 ]))
7543 }
7544 }
7545 });
7546
7547 // After formatting the buffer, the trailing whitespace is stripped,
7548 // a newline is appended, and the edits provided by the language server
7549 // have been applied.
7550 format.await.unwrap();
7551 cx.assert_editor_state(
7552 &[
7553 "one", //
7554 "", //
7555 "twoˇ", //
7556 "", //
7557 "three", //
7558 "four", //
7559 "", //
7560 ]
7561 .join("\n"),
7562 );
7563
7564 // Undoing the formatting undoes the trailing whitespace removal, the
7565 // trailing newline, and the LSP edits.
7566 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7567 cx.assert_editor_state(
7568 &[
7569 "one ", //
7570 "twoˇ", //
7571 "three ", //
7572 "four", //
7573 ]
7574 .join("\n"),
7575 );
7576}
7577
7578#[gpui::test]
7579async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7580 cx: &mut gpui::TestAppContext,
7581) {
7582 init_test(cx, |_| {});
7583
7584 cx.update(|cx| {
7585 cx.update_global::<SettingsStore, _>(|settings, cx| {
7586 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7587 settings.auto_signature_help = Some(true);
7588 });
7589 });
7590 });
7591
7592 let mut cx = EditorLspTestContext::new_rust(
7593 lsp::ServerCapabilities {
7594 signature_help_provider: Some(lsp::SignatureHelpOptions {
7595 ..Default::default()
7596 }),
7597 ..Default::default()
7598 },
7599 cx,
7600 )
7601 .await;
7602
7603 let language = Language::new(
7604 LanguageConfig {
7605 name: "Rust".into(),
7606 brackets: BracketPairConfig {
7607 pairs: vec![
7608 BracketPair {
7609 start: "{".to_string(),
7610 end: "}".to_string(),
7611 close: true,
7612 surround: true,
7613 newline: true,
7614 },
7615 BracketPair {
7616 start: "(".to_string(),
7617 end: ")".to_string(),
7618 close: true,
7619 surround: true,
7620 newline: true,
7621 },
7622 BracketPair {
7623 start: "/*".to_string(),
7624 end: " */".to_string(),
7625 close: true,
7626 surround: true,
7627 newline: true,
7628 },
7629 BracketPair {
7630 start: "[".to_string(),
7631 end: "]".to_string(),
7632 close: false,
7633 surround: false,
7634 newline: true,
7635 },
7636 BracketPair {
7637 start: "\"".to_string(),
7638 end: "\"".to_string(),
7639 close: true,
7640 surround: true,
7641 newline: false,
7642 },
7643 BracketPair {
7644 start: "<".to_string(),
7645 end: ">".to_string(),
7646 close: false,
7647 surround: true,
7648 newline: true,
7649 },
7650 ],
7651 ..Default::default()
7652 },
7653 autoclose_before: "})]".to_string(),
7654 ..Default::default()
7655 },
7656 Some(tree_sitter_rust::LANGUAGE.into()),
7657 );
7658 let language = Arc::new(language);
7659
7660 cx.language_registry().add(language.clone());
7661 cx.update_buffer(|buffer, cx| {
7662 buffer.set_language(Some(language), cx);
7663 });
7664
7665 cx.set_state(
7666 &r#"
7667 fn main() {
7668 sampleˇ
7669 }
7670 "#
7671 .unindent(),
7672 );
7673
7674 cx.update_editor(|view, cx| {
7675 view.handle_input("(", cx);
7676 });
7677 cx.assert_editor_state(
7678 &"
7679 fn main() {
7680 sample(ˇ)
7681 }
7682 "
7683 .unindent(),
7684 );
7685
7686 let mocked_response = lsp::SignatureHelp {
7687 signatures: vec![lsp::SignatureInformation {
7688 label: "fn sample(param1: u8, param2: u8)".to_string(),
7689 documentation: None,
7690 parameters: Some(vec![
7691 lsp::ParameterInformation {
7692 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7693 documentation: None,
7694 },
7695 lsp::ParameterInformation {
7696 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7697 documentation: None,
7698 },
7699 ]),
7700 active_parameter: None,
7701 }],
7702 active_signature: Some(0),
7703 active_parameter: Some(0),
7704 };
7705 handle_signature_help_request(&mut cx, mocked_response).await;
7706
7707 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7708 .await;
7709
7710 cx.editor(|editor, _| {
7711 let signature_help_state = editor.signature_help_state.popover().cloned();
7712 assert!(signature_help_state.is_some());
7713 let ParsedMarkdown {
7714 text, highlights, ..
7715 } = signature_help_state.unwrap().parsed_content;
7716 assert_eq!(text, "param1: u8, param2: u8");
7717 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7718 });
7719}
7720
7721#[gpui::test]
7722async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7723 init_test(cx, |_| {});
7724
7725 cx.update(|cx| {
7726 cx.update_global::<SettingsStore, _>(|settings, cx| {
7727 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7728 settings.auto_signature_help = Some(false);
7729 settings.show_signature_help_after_edits = Some(false);
7730 });
7731 });
7732 });
7733
7734 let mut cx = EditorLspTestContext::new_rust(
7735 lsp::ServerCapabilities {
7736 signature_help_provider: Some(lsp::SignatureHelpOptions {
7737 ..Default::default()
7738 }),
7739 ..Default::default()
7740 },
7741 cx,
7742 )
7743 .await;
7744
7745 let language = Language::new(
7746 LanguageConfig {
7747 name: "Rust".into(),
7748 brackets: BracketPairConfig {
7749 pairs: vec![
7750 BracketPair {
7751 start: "{".to_string(),
7752 end: "}".to_string(),
7753 close: true,
7754 surround: true,
7755 newline: true,
7756 },
7757 BracketPair {
7758 start: "(".to_string(),
7759 end: ")".to_string(),
7760 close: true,
7761 surround: true,
7762 newline: true,
7763 },
7764 BracketPair {
7765 start: "/*".to_string(),
7766 end: " */".to_string(),
7767 close: true,
7768 surround: true,
7769 newline: true,
7770 },
7771 BracketPair {
7772 start: "[".to_string(),
7773 end: "]".to_string(),
7774 close: false,
7775 surround: false,
7776 newline: true,
7777 },
7778 BracketPair {
7779 start: "\"".to_string(),
7780 end: "\"".to_string(),
7781 close: true,
7782 surround: true,
7783 newline: false,
7784 },
7785 BracketPair {
7786 start: "<".to_string(),
7787 end: ">".to_string(),
7788 close: false,
7789 surround: true,
7790 newline: true,
7791 },
7792 ],
7793 ..Default::default()
7794 },
7795 autoclose_before: "})]".to_string(),
7796 ..Default::default()
7797 },
7798 Some(tree_sitter_rust::LANGUAGE.into()),
7799 );
7800 let language = Arc::new(language);
7801
7802 cx.language_registry().add(language.clone());
7803 cx.update_buffer(|buffer, cx| {
7804 buffer.set_language(Some(language), cx);
7805 });
7806
7807 // Ensure that signature_help is not called when no signature help is enabled.
7808 cx.set_state(
7809 &r#"
7810 fn main() {
7811 sampleˇ
7812 }
7813 "#
7814 .unindent(),
7815 );
7816 cx.update_editor(|view, cx| {
7817 view.handle_input("(", cx);
7818 });
7819 cx.assert_editor_state(
7820 &"
7821 fn main() {
7822 sample(ˇ)
7823 }
7824 "
7825 .unindent(),
7826 );
7827 cx.editor(|editor, _| {
7828 assert!(editor.signature_help_state.task().is_none());
7829 });
7830
7831 let mocked_response = lsp::SignatureHelp {
7832 signatures: vec![lsp::SignatureInformation {
7833 label: "fn sample(param1: u8, param2: u8)".to_string(),
7834 documentation: None,
7835 parameters: Some(vec![
7836 lsp::ParameterInformation {
7837 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7838 documentation: None,
7839 },
7840 lsp::ParameterInformation {
7841 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7842 documentation: None,
7843 },
7844 ]),
7845 active_parameter: None,
7846 }],
7847 active_signature: Some(0),
7848 active_parameter: Some(0),
7849 };
7850
7851 // Ensure that signature_help is called when enabled afte edits
7852 cx.update(|cx| {
7853 cx.update_global::<SettingsStore, _>(|settings, cx| {
7854 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7855 settings.auto_signature_help = Some(false);
7856 settings.show_signature_help_after_edits = Some(true);
7857 });
7858 });
7859 });
7860 cx.set_state(
7861 &r#"
7862 fn main() {
7863 sampleˇ
7864 }
7865 "#
7866 .unindent(),
7867 );
7868 cx.update_editor(|view, cx| {
7869 view.handle_input("(", cx);
7870 });
7871 cx.assert_editor_state(
7872 &"
7873 fn main() {
7874 sample(ˇ)
7875 }
7876 "
7877 .unindent(),
7878 );
7879 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7880 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7881 .await;
7882 cx.update_editor(|editor, _| {
7883 let signature_help_state = editor.signature_help_state.popover().cloned();
7884 assert!(signature_help_state.is_some());
7885 let ParsedMarkdown {
7886 text, highlights, ..
7887 } = signature_help_state.unwrap().parsed_content;
7888 assert_eq!(text, "param1: u8, param2: u8");
7889 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7890 editor.signature_help_state = SignatureHelpState::default();
7891 });
7892
7893 // Ensure that signature_help is called when auto signature help override is enabled
7894 cx.update(|cx| {
7895 cx.update_global::<SettingsStore, _>(|settings, cx| {
7896 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7897 settings.auto_signature_help = Some(true);
7898 settings.show_signature_help_after_edits = Some(false);
7899 });
7900 });
7901 });
7902 cx.set_state(
7903 &r#"
7904 fn main() {
7905 sampleˇ
7906 }
7907 "#
7908 .unindent(),
7909 );
7910 cx.update_editor(|view, cx| {
7911 view.handle_input("(", cx);
7912 });
7913 cx.assert_editor_state(
7914 &"
7915 fn main() {
7916 sample(ˇ)
7917 }
7918 "
7919 .unindent(),
7920 );
7921 handle_signature_help_request(&mut cx, mocked_response).await;
7922 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7923 .await;
7924 cx.editor(|editor, _| {
7925 let signature_help_state = editor.signature_help_state.popover().cloned();
7926 assert!(signature_help_state.is_some());
7927 let ParsedMarkdown {
7928 text, highlights, ..
7929 } = signature_help_state.unwrap().parsed_content;
7930 assert_eq!(text, "param1: u8, param2: u8");
7931 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7932 });
7933}
7934
7935#[gpui::test]
7936async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7937 init_test(cx, |_| {});
7938 cx.update(|cx| {
7939 cx.update_global::<SettingsStore, _>(|settings, cx| {
7940 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7941 settings.auto_signature_help = Some(true);
7942 });
7943 });
7944 });
7945
7946 let mut cx = EditorLspTestContext::new_rust(
7947 lsp::ServerCapabilities {
7948 signature_help_provider: Some(lsp::SignatureHelpOptions {
7949 ..Default::default()
7950 }),
7951 ..Default::default()
7952 },
7953 cx,
7954 )
7955 .await;
7956
7957 // A test that directly calls `show_signature_help`
7958 cx.update_editor(|editor, cx| {
7959 editor.show_signature_help(&ShowSignatureHelp, cx);
7960 });
7961
7962 let mocked_response = lsp::SignatureHelp {
7963 signatures: vec![lsp::SignatureInformation {
7964 label: "fn sample(param1: u8, param2: u8)".to_string(),
7965 documentation: None,
7966 parameters: Some(vec![
7967 lsp::ParameterInformation {
7968 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7969 documentation: None,
7970 },
7971 lsp::ParameterInformation {
7972 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7973 documentation: None,
7974 },
7975 ]),
7976 active_parameter: None,
7977 }],
7978 active_signature: Some(0),
7979 active_parameter: Some(0),
7980 };
7981 handle_signature_help_request(&mut cx, mocked_response).await;
7982
7983 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7984 .await;
7985
7986 cx.editor(|editor, _| {
7987 let signature_help_state = editor.signature_help_state.popover().cloned();
7988 assert!(signature_help_state.is_some());
7989 let ParsedMarkdown {
7990 text, highlights, ..
7991 } = signature_help_state.unwrap().parsed_content;
7992 assert_eq!(text, "param1: u8, param2: u8");
7993 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7994 });
7995
7996 // When exiting outside from inside the brackets, `signature_help` is closed.
7997 cx.set_state(indoc! {"
7998 fn main() {
7999 sample(ˇ);
8000 }
8001
8002 fn sample(param1: u8, param2: u8) {}
8003 "});
8004
8005 cx.update_editor(|editor, cx| {
8006 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8007 });
8008
8009 let mocked_response = lsp::SignatureHelp {
8010 signatures: Vec::new(),
8011 active_signature: None,
8012 active_parameter: None,
8013 };
8014 handle_signature_help_request(&mut cx, mocked_response).await;
8015
8016 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8017 .await;
8018
8019 cx.editor(|editor, _| {
8020 assert!(!editor.signature_help_state.is_shown());
8021 });
8022
8023 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8024 cx.set_state(indoc! {"
8025 fn main() {
8026 sample(ˇ);
8027 }
8028
8029 fn sample(param1: u8, param2: u8) {}
8030 "});
8031
8032 let mocked_response = lsp::SignatureHelp {
8033 signatures: vec![lsp::SignatureInformation {
8034 label: "fn sample(param1: u8, param2: u8)".to_string(),
8035 documentation: None,
8036 parameters: Some(vec![
8037 lsp::ParameterInformation {
8038 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8039 documentation: None,
8040 },
8041 lsp::ParameterInformation {
8042 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8043 documentation: None,
8044 },
8045 ]),
8046 active_parameter: None,
8047 }],
8048 active_signature: Some(0),
8049 active_parameter: Some(0),
8050 };
8051 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8052 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8053 .await;
8054 cx.editor(|editor, _| {
8055 assert!(editor.signature_help_state.is_shown());
8056 });
8057
8058 // Restore the popover with more parameter input
8059 cx.set_state(indoc! {"
8060 fn main() {
8061 sample(param1, param2ˇ);
8062 }
8063
8064 fn sample(param1: u8, param2: u8) {}
8065 "});
8066
8067 let mocked_response = lsp::SignatureHelp {
8068 signatures: vec![lsp::SignatureInformation {
8069 label: "fn sample(param1: u8, param2: u8)".to_string(),
8070 documentation: None,
8071 parameters: Some(vec![
8072 lsp::ParameterInformation {
8073 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8074 documentation: None,
8075 },
8076 lsp::ParameterInformation {
8077 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8078 documentation: None,
8079 },
8080 ]),
8081 active_parameter: None,
8082 }],
8083 active_signature: Some(0),
8084 active_parameter: Some(1),
8085 };
8086 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8087 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8088 .await;
8089
8090 // When selecting a range, the popover is gone.
8091 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8092 cx.update_editor(|editor, cx| {
8093 editor.change_selections(None, cx, |s| {
8094 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8095 })
8096 });
8097 cx.assert_editor_state(indoc! {"
8098 fn main() {
8099 sample(param1, «ˇparam2»);
8100 }
8101
8102 fn sample(param1: u8, param2: u8) {}
8103 "});
8104 cx.editor(|editor, _| {
8105 assert!(!editor.signature_help_state.is_shown());
8106 });
8107
8108 // When unselecting again, the popover is back if within the brackets.
8109 cx.update_editor(|editor, cx| {
8110 editor.change_selections(None, cx, |s| {
8111 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8112 })
8113 });
8114 cx.assert_editor_state(indoc! {"
8115 fn main() {
8116 sample(param1, ˇparam2);
8117 }
8118
8119 fn sample(param1: u8, param2: u8) {}
8120 "});
8121 handle_signature_help_request(&mut cx, mocked_response).await;
8122 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8123 .await;
8124 cx.editor(|editor, _| {
8125 assert!(editor.signature_help_state.is_shown());
8126 });
8127
8128 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8129 cx.update_editor(|editor, cx| {
8130 editor.change_selections(None, cx, |s| {
8131 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8132 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8133 })
8134 });
8135 cx.assert_editor_state(indoc! {"
8136 fn main() {
8137 sample(param1, ˇparam2);
8138 }
8139
8140 fn sample(param1: u8, param2: u8) {}
8141 "});
8142
8143 let mocked_response = lsp::SignatureHelp {
8144 signatures: vec![lsp::SignatureInformation {
8145 label: "fn sample(param1: u8, param2: u8)".to_string(),
8146 documentation: None,
8147 parameters: Some(vec![
8148 lsp::ParameterInformation {
8149 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8150 documentation: None,
8151 },
8152 lsp::ParameterInformation {
8153 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8154 documentation: None,
8155 },
8156 ]),
8157 active_parameter: None,
8158 }],
8159 active_signature: Some(0),
8160 active_parameter: Some(1),
8161 };
8162 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8163 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8164 .await;
8165 cx.update_editor(|editor, cx| {
8166 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8167 });
8168 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8169 .await;
8170 cx.update_editor(|editor, cx| {
8171 editor.change_selections(None, cx, |s| {
8172 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8173 })
8174 });
8175 cx.assert_editor_state(indoc! {"
8176 fn main() {
8177 sample(param1, «ˇparam2»);
8178 }
8179
8180 fn sample(param1: u8, param2: u8) {}
8181 "});
8182 cx.update_editor(|editor, cx| {
8183 editor.change_selections(None, cx, |s| {
8184 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8185 })
8186 });
8187 cx.assert_editor_state(indoc! {"
8188 fn main() {
8189 sample(param1, ˇparam2);
8190 }
8191
8192 fn sample(param1: u8, param2: u8) {}
8193 "});
8194 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8195 .await;
8196}
8197
8198#[gpui::test]
8199async fn test_completion(cx: &mut gpui::TestAppContext) {
8200 init_test(cx, |_| {});
8201
8202 let mut cx = EditorLspTestContext::new_rust(
8203 lsp::ServerCapabilities {
8204 completion_provider: Some(lsp::CompletionOptions {
8205 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8206 resolve_provider: Some(true),
8207 ..Default::default()
8208 }),
8209 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8210 ..Default::default()
8211 },
8212 cx,
8213 )
8214 .await;
8215 let counter = Arc::new(AtomicUsize::new(0));
8216
8217 cx.set_state(indoc! {"
8218 oneˇ
8219 two
8220 three
8221 "});
8222 cx.simulate_keystroke(".");
8223 handle_completion_request(
8224 &mut cx,
8225 indoc! {"
8226 one.|<>
8227 two
8228 three
8229 "},
8230 vec!["first_completion", "second_completion"],
8231 counter.clone(),
8232 )
8233 .await;
8234 cx.condition(|editor, _| editor.context_menu_visible())
8235 .await;
8236 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8237
8238 let _handler = handle_signature_help_request(
8239 &mut cx,
8240 lsp::SignatureHelp {
8241 signatures: vec![lsp::SignatureInformation {
8242 label: "test signature".to_string(),
8243 documentation: None,
8244 parameters: Some(vec![lsp::ParameterInformation {
8245 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8246 documentation: None,
8247 }]),
8248 active_parameter: None,
8249 }],
8250 active_signature: None,
8251 active_parameter: None,
8252 },
8253 );
8254 cx.update_editor(|editor, cx| {
8255 assert!(
8256 !editor.signature_help_state.is_shown(),
8257 "No signature help was called for"
8258 );
8259 editor.show_signature_help(&ShowSignatureHelp, cx);
8260 });
8261 cx.run_until_parked();
8262 cx.update_editor(|editor, _| {
8263 assert!(
8264 !editor.signature_help_state.is_shown(),
8265 "No signature help should be shown when completions menu is open"
8266 );
8267 });
8268
8269 let apply_additional_edits = cx.update_editor(|editor, cx| {
8270 editor.context_menu_next(&Default::default(), cx);
8271 editor
8272 .confirm_completion(&ConfirmCompletion::default(), cx)
8273 .unwrap()
8274 });
8275 cx.assert_editor_state(indoc! {"
8276 one.second_completionˇ
8277 two
8278 three
8279 "});
8280
8281 handle_resolve_completion_request(
8282 &mut cx,
8283 Some(vec![
8284 (
8285 //This overlaps with the primary completion edit which is
8286 //misbehavior from the LSP spec, test that we filter it out
8287 indoc! {"
8288 one.second_ˇcompletion
8289 two
8290 threeˇ
8291 "},
8292 "overlapping additional edit",
8293 ),
8294 (
8295 indoc! {"
8296 one.second_completion
8297 two
8298 threeˇ
8299 "},
8300 "\nadditional edit",
8301 ),
8302 ]),
8303 )
8304 .await;
8305 apply_additional_edits.await.unwrap();
8306 cx.assert_editor_state(indoc! {"
8307 one.second_completionˇ
8308 two
8309 three
8310 additional edit
8311 "});
8312
8313 cx.set_state(indoc! {"
8314 one.second_completion
8315 twoˇ
8316 threeˇ
8317 additional edit
8318 "});
8319 cx.simulate_keystroke(" ");
8320 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8321 cx.simulate_keystroke("s");
8322 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8323
8324 cx.assert_editor_state(indoc! {"
8325 one.second_completion
8326 two sˇ
8327 three sˇ
8328 additional edit
8329 "});
8330 handle_completion_request(
8331 &mut cx,
8332 indoc! {"
8333 one.second_completion
8334 two s
8335 three <s|>
8336 additional edit
8337 "},
8338 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8339 counter.clone(),
8340 )
8341 .await;
8342 cx.condition(|editor, _| editor.context_menu_visible())
8343 .await;
8344 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8345
8346 cx.simulate_keystroke("i");
8347
8348 handle_completion_request(
8349 &mut cx,
8350 indoc! {"
8351 one.second_completion
8352 two si
8353 three <si|>
8354 additional edit
8355 "},
8356 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8357 counter.clone(),
8358 )
8359 .await;
8360 cx.condition(|editor, _| editor.context_menu_visible())
8361 .await;
8362 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8363
8364 let apply_additional_edits = cx.update_editor(|editor, cx| {
8365 editor
8366 .confirm_completion(&ConfirmCompletion::default(), cx)
8367 .unwrap()
8368 });
8369 cx.assert_editor_state(indoc! {"
8370 one.second_completion
8371 two sixth_completionˇ
8372 three sixth_completionˇ
8373 additional edit
8374 "});
8375
8376 handle_resolve_completion_request(&mut cx, None).await;
8377 apply_additional_edits.await.unwrap();
8378
8379 update_test_language_settings(&mut cx, |settings| {
8380 settings.defaults.show_completions_on_input = Some(false);
8381 });
8382 cx.set_state("editorˇ");
8383 cx.simulate_keystroke(".");
8384 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8385 cx.simulate_keystroke("c");
8386 cx.simulate_keystroke("l");
8387 cx.simulate_keystroke("o");
8388 cx.assert_editor_state("editor.cloˇ");
8389 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8390 cx.update_editor(|editor, cx| {
8391 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8392 });
8393 handle_completion_request(
8394 &mut cx,
8395 "editor.<clo|>",
8396 vec!["close", "clobber"],
8397 counter.clone(),
8398 )
8399 .await;
8400 cx.condition(|editor, _| editor.context_menu_visible())
8401 .await;
8402 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8403
8404 let apply_additional_edits = cx.update_editor(|editor, cx| {
8405 editor
8406 .confirm_completion(&ConfirmCompletion::default(), cx)
8407 .unwrap()
8408 });
8409 cx.assert_editor_state("editor.closeˇ");
8410 handle_resolve_completion_request(&mut cx, None).await;
8411 apply_additional_edits.await.unwrap();
8412}
8413
8414#[gpui::test]
8415async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8416 init_test(cx, |_| {});
8417 let mut cx = EditorLspTestContext::new_rust(
8418 lsp::ServerCapabilities {
8419 completion_provider: Some(lsp::CompletionOptions {
8420 trigger_characters: Some(vec![".".to_string()]),
8421 ..Default::default()
8422 }),
8423 ..Default::default()
8424 },
8425 cx,
8426 )
8427 .await;
8428 cx.lsp
8429 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8430 Ok(Some(lsp::CompletionResponse::Array(vec![
8431 lsp::CompletionItem {
8432 label: "first".into(),
8433 ..Default::default()
8434 },
8435 lsp::CompletionItem {
8436 label: "last".into(),
8437 ..Default::default()
8438 },
8439 ])))
8440 });
8441 cx.set_state("variableˇ");
8442 cx.simulate_keystroke(".");
8443 cx.executor().run_until_parked();
8444
8445 cx.update_editor(|editor, _| {
8446 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8447 assert_eq!(
8448 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8449 &["first", "last"]
8450 );
8451 } else {
8452 panic!("expected completion menu to be open");
8453 }
8454 });
8455
8456 cx.update_editor(|editor, cx| {
8457 editor.move_page_down(&MovePageDown::default(), cx);
8458 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8459 assert!(
8460 menu.selected_item == 1,
8461 "expected PageDown to select the last item from the context menu"
8462 );
8463 } else {
8464 panic!("expected completion menu to stay open after PageDown");
8465 }
8466 });
8467
8468 cx.update_editor(|editor, cx| {
8469 editor.move_page_up(&MovePageUp::default(), cx);
8470 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8471 assert!(
8472 menu.selected_item == 0,
8473 "expected PageUp to select the first item from the context menu"
8474 );
8475 } else {
8476 panic!("expected completion menu to stay open after PageUp");
8477 }
8478 });
8479}
8480
8481#[gpui::test]
8482async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8483 init_test(cx, |_| {});
8484 let mut cx = EditorLspTestContext::new_rust(
8485 lsp::ServerCapabilities {
8486 completion_provider: Some(lsp::CompletionOptions {
8487 trigger_characters: Some(vec![".".to_string()]),
8488 ..Default::default()
8489 }),
8490 ..Default::default()
8491 },
8492 cx,
8493 )
8494 .await;
8495 cx.lsp
8496 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8497 Ok(Some(lsp::CompletionResponse::Array(vec![
8498 lsp::CompletionItem {
8499 label: "Range".into(),
8500 sort_text: Some("a".into()),
8501 ..Default::default()
8502 },
8503 lsp::CompletionItem {
8504 label: "r".into(),
8505 sort_text: Some("b".into()),
8506 ..Default::default()
8507 },
8508 lsp::CompletionItem {
8509 label: "ret".into(),
8510 sort_text: Some("c".into()),
8511 ..Default::default()
8512 },
8513 lsp::CompletionItem {
8514 label: "return".into(),
8515 sort_text: Some("d".into()),
8516 ..Default::default()
8517 },
8518 lsp::CompletionItem {
8519 label: "slice".into(),
8520 sort_text: Some("d".into()),
8521 ..Default::default()
8522 },
8523 ])))
8524 });
8525 cx.set_state("rˇ");
8526 cx.executor().run_until_parked();
8527 cx.update_editor(|editor, cx| {
8528 editor.show_completions(
8529 &ShowCompletions {
8530 trigger: Some("r".into()),
8531 },
8532 cx,
8533 );
8534 });
8535 cx.executor().run_until_parked();
8536
8537 cx.update_editor(|editor, _| {
8538 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8539 assert_eq!(
8540 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8541 &["r", "ret", "Range", "return"]
8542 );
8543 } else {
8544 panic!("expected completion menu to be open");
8545 }
8546 });
8547}
8548
8549#[gpui::test]
8550async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8551 init_test(cx, |_| {});
8552
8553 let mut cx = EditorLspTestContext::new_rust(
8554 lsp::ServerCapabilities {
8555 completion_provider: Some(lsp::CompletionOptions {
8556 trigger_characters: Some(vec![".".to_string()]),
8557 resolve_provider: Some(true),
8558 ..Default::default()
8559 }),
8560 ..Default::default()
8561 },
8562 cx,
8563 )
8564 .await;
8565
8566 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8567 cx.simulate_keystroke(".");
8568 let completion_item = lsp::CompletionItem {
8569 label: "Some".into(),
8570 kind: Some(lsp::CompletionItemKind::SNIPPET),
8571 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8572 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8573 kind: lsp::MarkupKind::Markdown,
8574 value: "```rust\nSome(2)\n```".to_string(),
8575 })),
8576 deprecated: Some(false),
8577 sort_text: Some("Some".to_string()),
8578 filter_text: Some("Some".to_string()),
8579 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8580 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8581 range: lsp::Range {
8582 start: lsp::Position {
8583 line: 0,
8584 character: 22,
8585 },
8586 end: lsp::Position {
8587 line: 0,
8588 character: 22,
8589 },
8590 },
8591 new_text: "Some(2)".to_string(),
8592 })),
8593 additional_text_edits: Some(vec![lsp::TextEdit {
8594 range: lsp::Range {
8595 start: lsp::Position {
8596 line: 0,
8597 character: 20,
8598 },
8599 end: lsp::Position {
8600 line: 0,
8601 character: 22,
8602 },
8603 },
8604 new_text: "".to_string(),
8605 }]),
8606 ..Default::default()
8607 };
8608
8609 let closure_completion_item = completion_item.clone();
8610 let counter = Arc::new(AtomicUsize::new(0));
8611 let counter_clone = counter.clone();
8612 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8613 let task_completion_item = closure_completion_item.clone();
8614 counter_clone.fetch_add(1, atomic::Ordering::Release);
8615 async move {
8616 Ok(Some(lsp::CompletionResponse::Array(vec![
8617 task_completion_item,
8618 ])))
8619 }
8620 });
8621
8622 cx.condition(|editor, _| editor.context_menu_visible())
8623 .await;
8624 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8625 assert!(request.next().await.is_some());
8626 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8627
8628 cx.simulate_keystroke("S");
8629 cx.simulate_keystroke("o");
8630 cx.simulate_keystroke("m");
8631 cx.condition(|editor, _| editor.context_menu_visible())
8632 .await;
8633 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8634 assert!(request.next().await.is_some());
8635 assert!(request.next().await.is_some());
8636 assert!(request.next().await.is_some());
8637 request.close();
8638 assert!(request.next().await.is_none());
8639 assert_eq!(
8640 counter.load(atomic::Ordering::Acquire),
8641 4,
8642 "With the completions menu open, only one LSP request should happen per input"
8643 );
8644}
8645
8646#[gpui::test]
8647async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8648 init_test(cx, |_| {});
8649 let mut cx = EditorTestContext::new(cx).await;
8650 let language = Arc::new(Language::new(
8651 LanguageConfig {
8652 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8653 ..Default::default()
8654 },
8655 Some(tree_sitter_rust::LANGUAGE.into()),
8656 ));
8657 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8658
8659 // If multiple selections intersect a line, the line is only toggled once.
8660 cx.set_state(indoc! {"
8661 fn a() {
8662 «//b();
8663 ˇ»// «c();
8664 //ˇ» d();
8665 }
8666 "});
8667
8668 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8669
8670 cx.assert_editor_state(indoc! {"
8671 fn a() {
8672 «b();
8673 c();
8674 ˇ» d();
8675 }
8676 "});
8677
8678 // The comment prefix is inserted at the same column for every line in a
8679 // selection.
8680 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8681
8682 cx.assert_editor_state(indoc! {"
8683 fn a() {
8684 // «b();
8685 // c();
8686 ˇ»// d();
8687 }
8688 "});
8689
8690 // If a selection ends at the beginning of a line, that line is not toggled.
8691 cx.set_selections_state(indoc! {"
8692 fn a() {
8693 // b();
8694 «// c();
8695 ˇ» // d();
8696 }
8697 "});
8698
8699 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8700
8701 cx.assert_editor_state(indoc! {"
8702 fn a() {
8703 // b();
8704 «c();
8705 ˇ» // d();
8706 }
8707 "});
8708
8709 // If a selection span a single line and is empty, the line is toggled.
8710 cx.set_state(indoc! {"
8711 fn a() {
8712 a();
8713 b();
8714 ˇ
8715 }
8716 "});
8717
8718 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8719
8720 cx.assert_editor_state(indoc! {"
8721 fn a() {
8722 a();
8723 b();
8724 //•ˇ
8725 }
8726 "});
8727
8728 // If a selection span multiple lines, empty lines are not toggled.
8729 cx.set_state(indoc! {"
8730 fn a() {
8731 «a();
8732
8733 c();ˇ»
8734 }
8735 "});
8736
8737 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8738
8739 cx.assert_editor_state(indoc! {"
8740 fn a() {
8741 // «a();
8742
8743 // c();ˇ»
8744 }
8745 "});
8746
8747 // If a selection includes multiple comment prefixes, all lines are uncommented.
8748 cx.set_state(indoc! {"
8749 fn a() {
8750 «// a();
8751 /// b();
8752 //! c();ˇ»
8753 }
8754 "});
8755
8756 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8757
8758 cx.assert_editor_state(indoc! {"
8759 fn a() {
8760 «a();
8761 b();
8762 c();ˇ»
8763 }
8764 "});
8765}
8766
8767#[gpui::test]
8768async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8769 init_test(cx, |_| {});
8770 let mut cx = EditorTestContext::new(cx).await;
8771 let language = Arc::new(Language::new(
8772 LanguageConfig {
8773 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8774 ..Default::default()
8775 },
8776 Some(tree_sitter_rust::LANGUAGE.into()),
8777 ));
8778 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8779
8780 let toggle_comments = &ToggleComments {
8781 advance_downwards: false,
8782 ignore_indent: true,
8783 };
8784
8785 // If multiple selections intersect a line, the line is only toggled once.
8786 cx.set_state(indoc! {"
8787 fn a() {
8788 // «b();
8789 // c();
8790 // ˇ» d();
8791 }
8792 "});
8793
8794 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8795
8796 cx.assert_editor_state(indoc! {"
8797 fn a() {
8798 «b();
8799 c();
8800 ˇ» d();
8801 }
8802 "});
8803
8804 // The comment prefix is inserted at the beginning of each line
8805 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8806
8807 cx.assert_editor_state(indoc! {"
8808 fn a() {
8809 // «b();
8810 // c();
8811 // ˇ» d();
8812 }
8813 "});
8814
8815 // If a selection ends at the beginning of a line, that line is not toggled.
8816 cx.set_selections_state(indoc! {"
8817 fn a() {
8818 // b();
8819 // «c();
8820 ˇ»// d();
8821 }
8822 "});
8823
8824 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8825
8826 cx.assert_editor_state(indoc! {"
8827 fn a() {
8828 // b();
8829 «c();
8830 ˇ»// d();
8831 }
8832 "});
8833
8834 // If a selection span a single line and is empty, the line is toggled.
8835 cx.set_state(indoc! {"
8836 fn a() {
8837 a();
8838 b();
8839 ˇ
8840 }
8841 "});
8842
8843 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8844
8845 cx.assert_editor_state(indoc! {"
8846 fn a() {
8847 a();
8848 b();
8849 //ˇ
8850 }
8851 "});
8852
8853 // If a selection span multiple lines, empty lines are not toggled.
8854 cx.set_state(indoc! {"
8855 fn a() {
8856 «a();
8857
8858 c();ˇ»
8859 }
8860 "});
8861
8862 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8863
8864 cx.assert_editor_state(indoc! {"
8865 fn a() {
8866 // «a();
8867
8868 // c();ˇ»
8869 }
8870 "});
8871
8872 // If a selection includes multiple comment prefixes, all lines are uncommented.
8873 cx.set_state(indoc! {"
8874 fn a() {
8875 // «a();
8876 /// b();
8877 //! c();ˇ»
8878 }
8879 "});
8880
8881 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8882
8883 cx.assert_editor_state(indoc! {"
8884 fn a() {
8885 «a();
8886 b();
8887 c();ˇ»
8888 }
8889 "});
8890}
8891
8892#[gpui::test]
8893async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8894 init_test(cx, |_| {});
8895
8896 let language = Arc::new(Language::new(
8897 LanguageConfig {
8898 line_comments: vec!["// ".into()],
8899 ..Default::default()
8900 },
8901 Some(tree_sitter_rust::LANGUAGE.into()),
8902 ));
8903
8904 let mut cx = EditorTestContext::new(cx).await;
8905
8906 cx.language_registry().add(language.clone());
8907 cx.update_buffer(|buffer, cx| {
8908 buffer.set_language(Some(language), cx);
8909 });
8910
8911 let toggle_comments = &ToggleComments {
8912 advance_downwards: true,
8913 ignore_indent: false,
8914 };
8915
8916 // Single cursor on one line -> advance
8917 // Cursor moves horizontally 3 characters as well on non-blank line
8918 cx.set_state(indoc!(
8919 "fn a() {
8920 ˇdog();
8921 cat();
8922 }"
8923 ));
8924 cx.update_editor(|editor, cx| {
8925 editor.toggle_comments(toggle_comments, cx);
8926 });
8927 cx.assert_editor_state(indoc!(
8928 "fn a() {
8929 // dog();
8930 catˇ();
8931 }"
8932 ));
8933
8934 // Single selection on one line -> don't advance
8935 cx.set_state(indoc!(
8936 "fn a() {
8937 «dog()ˇ»;
8938 cat();
8939 }"
8940 ));
8941 cx.update_editor(|editor, cx| {
8942 editor.toggle_comments(toggle_comments, cx);
8943 });
8944 cx.assert_editor_state(indoc!(
8945 "fn a() {
8946 // «dog()ˇ»;
8947 cat();
8948 }"
8949 ));
8950
8951 // Multiple cursors on one line -> advance
8952 cx.set_state(indoc!(
8953 "fn a() {
8954 ˇdˇog();
8955 cat();
8956 }"
8957 ));
8958 cx.update_editor(|editor, cx| {
8959 editor.toggle_comments(toggle_comments, cx);
8960 });
8961 cx.assert_editor_state(indoc!(
8962 "fn a() {
8963 // dog();
8964 catˇ(ˇ);
8965 }"
8966 ));
8967
8968 // Multiple cursors on one line, with selection -> don't advance
8969 cx.set_state(indoc!(
8970 "fn a() {
8971 ˇdˇog«()ˇ»;
8972 cat();
8973 }"
8974 ));
8975 cx.update_editor(|editor, cx| {
8976 editor.toggle_comments(toggle_comments, cx);
8977 });
8978 cx.assert_editor_state(indoc!(
8979 "fn a() {
8980 // ˇdˇog«()ˇ»;
8981 cat();
8982 }"
8983 ));
8984
8985 // Single cursor on one line -> advance
8986 // Cursor moves to column 0 on blank line
8987 cx.set_state(indoc!(
8988 "fn a() {
8989 ˇdog();
8990
8991 cat();
8992 }"
8993 ));
8994 cx.update_editor(|editor, cx| {
8995 editor.toggle_comments(toggle_comments, cx);
8996 });
8997 cx.assert_editor_state(indoc!(
8998 "fn a() {
8999 // dog();
9000 ˇ
9001 cat();
9002 }"
9003 ));
9004
9005 // Single cursor on one line -> advance
9006 // Cursor starts and ends at column 0
9007 cx.set_state(indoc!(
9008 "fn a() {
9009 ˇ dog();
9010 cat();
9011 }"
9012 ));
9013 cx.update_editor(|editor, cx| {
9014 editor.toggle_comments(toggle_comments, cx);
9015 });
9016 cx.assert_editor_state(indoc!(
9017 "fn a() {
9018 // dog();
9019 ˇ cat();
9020 }"
9021 ));
9022}
9023
9024#[gpui::test]
9025async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9026 init_test(cx, |_| {});
9027
9028 let mut cx = EditorTestContext::new(cx).await;
9029
9030 let html_language = Arc::new(
9031 Language::new(
9032 LanguageConfig {
9033 name: "HTML".into(),
9034 block_comment: Some(("<!-- ".into(), " -->".into())),
9035 ..Default::default()
9036 },
9037 Some(tree_sitter_html::language()),
9038 )
9039 .with_injection_query(
9040 r#"
9041 (script_element
9042 (raw_text) @content
9043 (#set! "language" "javascript"))
9044 "#,
9045 )
9046 .unwrap(),
9047 );
9048
9049 let javascript_language = Arc::new(Language::new(
9050 LanguageConfig {
9051 name: "JavaScript".into(),
9052 line_comments: vec!["// ".into()],
9053 ..Default::default()
9054 },
9055 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9056 ));
9057
9058 cx.language_registry().add(html_language.clone());
9059 cx.language_registry().add(javascript_language.clone());
9060 cx.update_buffer(|buffer, cx| {
9061 buffer.set_language(Some(html_language), cx);
9062 });
9063
9064 // Toggle comments for empty selections
9065 cx.set_state(
9066 &r#"
9067 <p>A</p>ˇ
9068 <p>B</p>ˇ
9069 <p>C</p>ˇ
9070 "#
9071 .unindent(),
9072 );
9073 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9074 cx.assert_editor_state(
9075 &r#"
9076 <!-- <p>A</p>ˇ -->
9077 <!-- <p>B</p>ˇ -->
9078 <!-- <p>C</p>ˇ -->
9079 "#
9080 .unindent(),
9081 );
9082 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9083 cx.assert_editor_state(
9084 &r#"
9085 <p>A</p>ˇ
9086 <p>B</p>ˇ
9087 <p>C</p>ˇ
9088 "#
9089 .unindent(),
9090 );
9091
9092 // Toggle comments for mixture of empty and non-empty selections, where
9093 // multiple selections occupy a given line.
9094 cx.set_state(
9095 &r#"
9096 <p>A«</p>
9097 <p>ˇ»B</p>ˇ
9098 <p>C«</p>
9099 <p>ˇ»D</p>ˇ
9100 "#
9101 .unindent(),
9102 );
9103
9104 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9105 cx.assert_editor_state(
9106 &r#"
9107 <!-- <p>A«</p>
9108 <p>ˇ»B</p>ˇ -->
9109 <!-- <p>C«</p>
9110 <p>ˇ»D</p>ˇ -->
9111 "#
9112 .unindent(),
9113 );
9114 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9115 cx.assert_editor_state(
9116 &r#"
9117 <p>A«</p>
9118 <p>ˇ»B</p>ˇ
9119 <p>C«</p>
9120 <p>ˇ»D</p>ˇ
9121 "#
9122 .unindent(),
9123 );
9124
9125 // Toggle comments when different languages are active for different
9126 // selections.
9127 cx.set_state(
9128 &r#"
9129 ˇ<script>
9130 ˇvar x = new Y();
9131 ˇ</script>
9132 "#
9133 .unindent(),
9134 );
9135 cx.executor().run_until_parked();
9136 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9137 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9138 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9139 cx.assert_editor_state(
9140 &r#"
9141 <!-- ˇ<script> -->
9142 // ˇvar x = new Y();
9143 // ˇ</script>
9144 "#
9145 .unindent(),
9146 );
9147}
9148
9149#[gpui::test]
9150fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9151 init_test(cx, |_| {});
9152
9153 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9154 let multibuffer = cx.new_model(|cx| {
9155 let mut multibuffer = MultiBuffer::new(ReadWrite);
9156 multibuffer.push_excerpts(
9157 buffer.clone(),
9158 [
9159 ExcerptRange {
9160 context: Point::new(0, 0)..Point::new(0, 4),
9161 primary: None,
9162 },
9163 ExcerptRange {
9164 context: Point::new(1, 0)..Point::new(1, 4),
9165 primary: None,
9166 },
9167 ],
9168 cx,
9169 );
9170 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9171 multibuffer
9172 });
9173
9174 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9175 view.update(cx, |view, cx| {
9176 assert_eq!(view.text(cx), "aaaa\nbbbb");
9177 view.change_selections(None, cx, |s| {
9178 s.select_ranges([
9179 Point::new(0, 0)..Point::new(0, 0),
9180 Point::new(1, 0)..Point::new(1, 0),
9181 ])
9182 });
9183
9184 view.handle_input("X", cx);
9185 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9186 assert_eq!(
9187 view.selections.ranges(cx),
9188 [
9189 Point::new(0, 1)..Point::new(0, 1),
9190 Point::new(1, 1)..Point::new(1, 1),
9191 ]
9192 );
9193
9194 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9195 view.change_selections(None, cx, |s| {
9196 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9197 });
9198 view.backspace(&Default::default(), cx);
9199 assert_eq!(view.text(cx), "Xa\nbbb");
9200 assert_eq!(
9201 view.selections.ranges(cx),
9202 [Point::new(1, 0)..Point::new(1, 0)]
9203 );
9204
9205 view.change_selections(None, cx, |s| {
9206 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9207 });
9208 view.backspace(&Default::default(), cx);
9209 assert_eq!(view.text(cx), "X\nbb");
9210 assert_eq!(
9211 view.selections.ranges(cx),
9212 [Point::new(0, 1)..Point::new(0, 1)]
9213 );
9214 });
9215}
9216
9217#[gpui::test]
9218fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9219 init_test(cx, |_| {});
9220
9221 let markers = vec![('[', ']').into(), ('(', ')').into()];
9222 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9223 indoc! {"
9224 [aaaa
9225 (bbbb]
9226 cccc)",
9227 },
9228 markers.clone(),
9229 );
9230 let excerpt_ranges = markers.into_iter().map(|marker| {
9231 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9232 ExcerptRange {
9233 context,
9234 primary: None,
9235 }
9236 });
9237 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9238 let multibuffer = cx.new_model(|cx| {
9239 let mut multibuffer = MultiBuffer::new(ReadWrite);
9240 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9241 multibuffer
9242 });
9243
9244 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9245 view.update(cx, |view, cx| {
9246 let (expected_text, selection_ranges) = marked_text_ranges(
9247 indoc! {"
9248 aaaa
9249 bˇbbb
9250 bˇbbˇb
9251 cccc"
9252 },
9253 true,
9254 );
9255 assert_eq!(view.text(cx), expected_text);
9256 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9257
9258 view.handle_input("X", cx);
9259
9260 let (expected_text, expected_selections) = marked_text_ranges(
9261 indoc! {"
9262 aaaa
9263 bXˇbbXb
9264 bXˇbbXˇb
9265 cccc"
9266 },
9267 false,
9268 );
9269 assert_eq!(view.text(cx), expected_text);
9270 assert_eq!(view.selections.ranges(cx), expected_selections);
9271
9272 view.newline(&Newline, cx);
9273 let (expected_text, expected_selections) = marked_text_ranges(
9274 indoc! {"
9275 aaaa
9276 bX
9277 ˇbbX
9278 b
9279 bX
9280 ˇbbX
9281 ˇb
9282 cccc"
9283 },
9284 false,
9285 );
9286 assert_eq!(view.text(cx), expected_text);
9287 assert_eq!(view.selections.ranges(cx), expected_selections);
9288 });
9289}
9290
9291#[gpui::test]
9292fn test_refresh_selections(cx: &mut TestAppContext) {
9293 init_test(cx, |_| {});
9294
9295 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9296 let mut excerpt1_id = None;
9297 let multibuffer = cx.new_model(|cx| {
9298 let mut multibuffer = MultiBuffer::new(ReadWrite);
9299 excerpt1_id = multibuffer
9300 .push_excerpts(
9301 buffer.clone(),
9302 [
9303 ExcerptRange {
9304 context: Point::new(0, 0)..Point::new(1, 4),
9305 primary: None,
9306 },
9307 ExcerptRange {
9308 context: Point::new(1, 0)..Point::new(2, 4),
9309 primary: None,
9310 },
9311 ],
9312 cx,
9313 )
9314 .into_iter()
9315 .next();
9316 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9317 multibuffer
9318 });
9319
9320 let editor = cx.add_window(|cx| {
9321 let mut editor = build_editor(multibuffer.clone(), cx);
9322 let snapshot = editor.snapshot(cx);
9323 editor.change_selections(None, cx, |s| {
9324 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9325 });
9326 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9327 assert_eq!(
9328 editor.selections.ranges(cx),
9329 [
9330 Point::new(1, 3)..Point::new(1, 3),
9331 Point::new(2, 1)..Point::new(2, 1),
9332 ]
9333 );
9334 editor
9335 });
9336
9337 // Refreshing selections is a no-op when excerpts haven't changed.
9338 _ = editor.update(cx, |editor, cx| {
9339 editor.change_selections(None, cx, |s| s.refresh());
9340 assert_eq!(
9341 editor.selections.ranges(cx),
9342 [
9343 Point::new(1, 3)..Point::new(1, 3),
9344 Point::new(2, 1)..Point::new(2, 1),
9345 ]
9346 );
9347 });
9348
9349 multibuffer.update(cx, |multibuffer, cx| {
9350 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9351 });
9352 _ = editor.update(cx, |editor, cx| {
9353 // Removing an excerpt causes the first selection to become degenerate.
9354 assert_eq!(
9355 editor.selections.ranges(cx),
9356 [
9357 Point::new(0, 0)..Point::new(0, 0),
9358 Point::new(0, 1)..Point::new(0, 1)
9359 ]
9360 );
9361
9362 // Refreshing selections will relocate the first selection to the original buffer
9363 // location.
9364 editor.change_selections(None, cx, |s| s.refresh());
9365 assert_eq!(
9366 editor.selections.ranges(cx),
9367 [
9368 Point::new(0, 1)..Point::new(0, 1),
9369 Point::new(0, 3)..Point::new(0, 3)
9370 ]
9371 );
9372 assert!(editor.selections.pending_anchor().is_some());
9373 });
9374}
9375
9376#[gpui::test]
9377fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9378 init_test(cx, |_| {});
9379
9380 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9381 let mut excerpt1_id = None;
9382 let multibuffer = cx.new_model(|cx| {
9383 let mut multibuffer = MultiBuffer::new(ReadWrite);
9384 excerpt1_id = multibuffer
9385 .push_excerpts(
9386 buffer.clone(),
9387 [
9388 ExcerptRange {
9389 context: Point::new(0, 0)..Point::new(1, 4),
9390 primary: None,
9391 },
9392 ExcerptRange {
9393 context: Point::new(1, 0)..Point::new(2, 4),
9394 primary: None,
9395 },
9396 ],
9397 cx,
9398 )
9399 .into_iter()
9400 .next();
9401 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9402 multibuffer
9403 });
9404
9405 let editor = cx.add_window(|cx| {
9406 let mut editor = build_editor(multibuffer.clone(), cx);
9407 let snapshot = editor.snapshot(cx);
9408 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9409 assert_eq!(
9410 editor.selections.ranges(cx),
9411 [Point::new(1, 3)..Point::new(1, 3)]
9412 );
9413 editor
9414 });
9415
9416 multibuffer.update(cx, |multibuffer, cx| {
9417 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9418 });
9419 _ = editor.update(cx, |editor, cx| {
9420 assert_eq!(
9421 editor.selections.ranges(cx),
9422 [Point::new(0, 0)..Point::new(0, 0)]
9423 );
9424
9425 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9426 editor.change_selections(None, cx, |s| s.refresh());
9427 assert_eq!(
9428 editor.selections.ranges(cx),
9429 [Point::new(0, 3)..Point::new(0, 3)]
9430 );
9431 assert!(editor.selections.pending_anchor().is_some());
9432 });
9433}
9434
9435#[gpui::test]
9436async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9437 init_test(cx, |_| {});
9438
9439 let language = Arc::new(
9440 Language::new(
9441 LanguageConfig {
9442 brackets: BracketPairConfig {
9443 pairs: vec![
9444 BracketPair {
9445 start: "{".to_string(),
9446 end: "}".to_string(),
9447 close: true,
9448 surround: true,
9449 newline: true,
9450 },
9451 BracketPair {
9452 start: "/* ".to_string(),
9453 end: " */".to_string(),
9454 close: true,
9455 surround: true,
9456 newline: true,
9457 },
9458 ],
9459 ..Default::default()
9460 },
9461 ..Default::default()
9462 },
9463 Some(tree_sitter_rust::LANGUAGE.into()),
9464 )
9465 .with_indents_query("")
9466 .unwrap(),
9467 );
9468
9469 let text = concat!(
9470 "{ }\n", //
9471 " x\n", //
9472 " /* */\n", //
9473 "x\n", //
9474 "{{} }\n", //
9475 );
9476
9477 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9478 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9479 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9480 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9481 .await;
9482
9483 view.update(cx, |view, cx| {
9484 view.change_selections(None, cx, |s| {
9485 s.select_display_ranges([
9486 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9487 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9488 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9489 ])
9490 });
9491 view.newline(&Newline, cx);
9492
9493 assert_eq!(
9494 view.buffer().read(cx).read(cx).text(),
9495 concat!(
9496 "{ \n", // Suppress rustfmt
9497 "\n", //
9498 "}\n", //
9499 " x\n", //
9500 " /* \n", //
9501 " \n", //
9502 " */\n", //
9503 "x\n", //
9504 "{{} \n", //
9505 "}\n", //
9506 )
9507 );
9508 });
9509}
9510
9511#[gpui::test]
9512fn test_highlighted_ranges(cx: &mut TestAppContext) {
9513 init_test(cx, |_| {});
9514
9515 let editor = cx.add_window(|cx| {
9516 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9517 build_editor(buffer.clone(), cx)
9518 });
9519
9520 _ = editor.update(cx, |editor, cx| {
9521 struct Type1;
9522 struct Type2;
9523
9524 let buffer = editor.buffer.read(cx).snapshot(cx);
9525
9526 let anchor_range =
9527 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9528
9529 editor.highlight_background::<Type1>(
9530 &[
9531 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9532 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9533 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9534 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9535 ],
9536 |_| Hsla::red(),
9537 cx,
9538 );
9539 editor.highlight_background::<Type2>(
9540 &[
9541 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9542 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9543 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9544 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9545 ],
9546 |_| Hsla::green(),
9547 cx,
9548 );
9549
9550 let snapshot = editor.snapshot(cx);
9551 let mut highlighted_ranges = editor.background_highlights_in_range(
9552 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9553 &snapshot,
9554 cx.theme().colors(),
9555 );
9556 // Enforce a consistent ordering based on color without relying on the ordering of the
9557 // highlight's `TypeId` which is non-executor.
9558 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9559 assert_eq!(
9560 highlighted_ranges,
9561 &[
9562 (
9563 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9564 Hsla::red(),
9565 ),
9566 (
9567 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9568 Hsla::red(),
9569 ),
9570 (
9571 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9572 Hsla::green(),
9573 ),
9574 (
9575 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9576 Hsla::green(),
9577 ),
9578 ]
9579 );
9580 assert_eq!(
9581 editor.background_highlights_in_range(
9582 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9583 &snapshot,
9584 cx.theme().colors(),
9585 ),
9586 &[(
9587 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9588 Hsla::red(),
9589 )]
9590 );
9591 });
9592}
9593
9594#[gpui::test]
9595async fn test_following(cx: &mut gpui::TestAppContext) {
9596 init_test(cx, |_| {});
9597
9598 let fs = FakeFs::new(cx.executor());
9599 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9600
9601 let buffer = project.update(cx, |project, cx| {
9602 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9603 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9604 });
9605 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9606 let follower = cx.update(|cx| {
9607 cx.open_window(
9608 WindowOptions {
9609 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9610 gpui::Point::new(px(0.), px(0.)),
9611 gpui::Point::new(px(10.), px(80.)),
9612 ))),
9613 ..Default::default()
9614 },
9615 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9616 )
9617 .unwrap()
9618 });
9619
9620 let is_still_following = Rc::new(RefCell::new(true));
9621 let follower_edit_event_count = Rc::new(RefCell::new(0));
9622 let pending_update = Rc::new(RefCell::new(None));
9623 _ = follower.update(cx, {
9624 let update = pending_update.clone();
9625 let is_still_following = is_still_following.clone();
9626 let follower_edit_event_count = follower_edit_event_count.clone();
9627 |_, cx| {
9628 cx.subscribe(
9629 &leader.root_view(cx).unwrap(),
9630 move |_, leader, event, cx| {
9631 leader
9632 .read(cx)
9633 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9634 },
9635 )
9636 .detach();
9637
9638 cx.subscribe(
9639 &follower.root_view(cx).unwrap(),
9640 move |_, _, event: &EditorEvent, _cx| {
9641 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9642 *is_still_following.borrow_mut() = false;
9643 }
9644
9645 if let EditorEvent::BufferEdited = event {
9646 *follower_edit_event_count.borrow_mut() += 1;
9647 }
9648 },
9649 )
9650 .detach();
9651 }
9652 });
9653
9654 // Update the selections only
9655 _ = leader.update(cx, |leader, cx| {
9656 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9657 });
9658 follower
9659 .update(cx, |follower, cx| {
9660 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9661 })
9662 .unwrap()
9663 .await
9664 .unwrap();
9665 _ = follower.update(cx, |follower, cx| {
9666 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9667 });
9668 assert!(*is_still_following.borrow());
9669 assert_eq!(*follower_edit_event_count.borrow(), 0);
9670
9671 // Update the scroll position only
9672 _ = leader.update(cx, |leader, cx| {
9673 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9674 });
9675 follower
9676 .update(cx, |follower, cx| {
9677 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9678 })
9679 .unwrap()
9680 .await
9681 .unwrap();
9682 assert_eq!(
9683 follower
9684 .update(cx, |follower, cx| follower.scroll_position(cx))
9685 .unwrap(),
9686 gpui::Point::new(1.5, 3.5)
9687 );
9688 assert!(*is_still_following.borrow());
9689 assert_eq!(*follower_edit_event_count.borrow(), 0);
9690
9691 // Update the selections and scroll position. The follower's scroll position is updated
9692 // via autoscroll, not via the leader's exact scroll position.
9693 _ = leader.update(cx, |leader, cx| {
9694 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9695 leader.request_autoscroll(Autoscroll::newest(), cx);
9696 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9697 });
9698 follower
9699 .update(cx, |follower, cx| {
9700 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9701 })
9702 .unwrap()
9703 .await
9704 .unwrap();
9705 _ = follower.update(cx, |follower, cx| {
9706 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9707 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9708 });
9709 assert!(*is_still_following.borrow());
9710
9711 // Creating a pending selection that precedes another selection
9712 _ = leader.update(cx, |leader, cx| {
9713 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9714 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9715 });
9716 follower
9717 .update(cx, |follower, cx| {
9718 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9719 })
9720 .unwrap()
9721 .await
9722 .unwrap();
9723 _ = follower.update(cx, |follower, cx| {
9724 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9725 });
9726 assert!(*is_still_following.borrow());
9727
9728 // Extend the pending selection so that it surrounds another selection
9729 _ = leader.update(cx, |leader, cx| {
9730 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9731 });
9732 follower
9733 .update(cx, |follower, cx| {
9734 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9735 })
9736 .unwrap()
9737 .await
9738 .unwrap();
9739 _ = follower.update(cx, |follower, cx| {
9740 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9741 });
9742
9743 // Scrolling locally breaks the follow
9744 _ = follower.update(cx, |follower, cx| {
9745 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9746 follower.set_scroll_anchor(
9747 ScrollAnchor {
9748 anchor: top_anchor,
9749 offset: gpui::Point::new(0.0, 0.5),
9750 },
9751 cx,
9752 );
9753 });
9754 assert!(!(*is_still_following.borrow()));
9755}
9756
9757#[gpui::test]
9758async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9759 init_test(cx, |_| {});
9760
9761 let fs = FakeFs::new(cx.executor());
9762 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9763 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9764 let pane = workspace
9765 .update(cx, |workspace, _| workspace.active_pane().clone())
9766 .unwrap();
9767
9768 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9769
9770 let leader = pane.update(cx, |_, cx| {
9771 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9772 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9773 });
9774
9775 // Start following the editor when it has no excerpts.
9776 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9777 let follower_1 = cx
9778 .update_window(*workspace.deref(), |_, cx| {
9779 Editor::from_state_proto(
9780 workspace.root_view(cx).unwrap(),
9781 ViewId {
9782 creator: Default::default(),
9783 id: 0,
9784 },
9785 &mut state_message,
9786 cx,
9787 )
9788 })
9789 .unwrap()
9790 .unwrap()
9791 .await
9792 .unwrap();
9793
9794 let update_message = Rc::new(RefCell::new(None));
9795 follower_1.update(cx, {
9796 let update = update_message.clone();
9797 |_, cx| {
9798 cx.subscribe(&leader, move |_, leader, event, cx| {
9799 leader
9800 .read(cx)
9801 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9802 })
9803 .detach();
9804 }
9805 });
9806
9807 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9808 (
9809 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9810 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9811 )
9812 });
9813
9814 // Insert some excerpts.
9815 leader.update(cx, |leader, cx| {
9816 leader.buffer.update(cx, |multibuffer, cx| {
9817 let excerpt_ids = multibuffer.push_excerpts(
9818 buffer_1.clone(),
9819 [
9820 ExcerptRange {
9821 context: 1..6,
9822 primary: None,
9823 },
9824 ExcerptRange {
9825 context: 12..15,
9826 primary: None,
9827 },
9828 ExcerptRange {
9829 context: 0..3,
9830 primary: None,
9831 },
9832 ],
9833 cx,
9834 );
9835 multibuffer.insert_excerpts_after(
9836 excerpt_ids[0],
9837 buffer_2.clone(),
9838 [
9839 ExcerptRange {
9840 context: 8..12,
9841 primary: None,
9842 },
9843 ExcerptRange {
9844 context: 0..6,
9845 primary: None,
9846 },
9847 ],
9848 cx,
9849 );
9850 });
9851 });
9852
9853 // Apply the update of adding the excerpts.
9854 follower_1
9855 .update(cx, |follower, cx| {
9856 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9857 })
9858 .await
9859 .unwrap();
9860 assert_eq!(
9861 follower_1.update(cx, |editor, cx| editor.text(cx)),
9862 leader.update(cx, |editor, cx| editor.text(cx))
9863 );
9864 update_message.borrow_mut().take();
9865
9866 // Start following separately after it already has excerpts.
9867 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9868 let follower_2 = cx
9869 .update_window(*workspace.deref(), |_, cx| {
9870 Editor::from_state_proto(
9871 workspace.root_view(cx).unwrap().clone(),
9872 ViewId {
9873 creator: Default::default(),
9874 id: 0,
9875 },
9876 &mut state_message,
9877 cx,
9878 )
9879 })
9880 .unwrap()
9881 .unwrap()
9882 .await
9883 .unwrap();
9884 assert_eq!(
9885 follower_2.update(cx, |editor, cx| editor.text(cx)),
9886 leader.update(cx, |editor, cx| editor.text(cx))
9887 );
9888
9889 // Remove some excerpts.
9890 leader.update(cx, |leader, cx| {
9891 leader.buffer.update(cx, |multibuffer, cx| {
9892 let excerpt_ids = multibuffer.excerpt_ids();
9893 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9894 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9895 });
9896 });
9897
9898 // Apply the update of removing the excerpts.
9899 follower_1
9900 .update(cx, |follower, cx| {
9901 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9902 })
9903 .await
9904 .unwrap();
9905 follower_2
9906 .update(cx, |follower, cx| {
9907 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9908 })
9909 .await
9910 .unwrap();
9911 update_message.borrow_mut().take();
9912 assert_eq!(
9913 follower_1.update(cx, |editor, cx| editor.text(cx)),
9914 leader.update(cx, |editor, cx| editor.text(cx))
9915 );
9916}
9917
9918#[gpui::test]
9919async fn go_to_prev_overlapping_diagnostic(
9920 executor: BackgroundExecutor,
9921 cx: &mut gpui::TestAppContext,
9922) {
9923 init_test(cx, |_| {});
9924
9925 let mut cx = EditorTestContext::new(cx).await;
9926 let lsp_store =
9927 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9928
9929 cx.set_state(indoc! {"
9930 ˇfn func(abc def: i32) -> u32 {
9931 }
9932 "});
9933
9934 cx.update(|cx| {
9935 lsp_store.update(cx, |lsp_store, cx| {
9936 lsp_store
9937 .update_diagnostics(
9938 LanguageServerId(0),
9939 lsp::PublishDiagnosticsParams {
9940 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9941 version: None,
9942 diagnostics: vec![
9943 lsp::Diagnostic {
9944 range: lsp::Range::new(
9945 lsp::Position::new(0, 11),
9946 lsp::Position::new(0, 12),
9947 ),
9948 severity: Some(lsp::DiagnosticSeverity::ERROR),
9949 ..Default::default()
9950 },
9951 lsp::Diagnostic {
9952 range: lsp::Range::new(
9953 lsp::Position::new(0, 12),
9954 lsp::Position::new(0, 15),
9955 ),
9956 severity: Some(lsp::DiagnosticSeverity::ERROR),
9957 ..Default::default()
9958 },
9959 lsp::Diagnostic {
9960 range: lsp::Range::new(
9961 lsp::Position::new(0, 25),
9962 lsp::Position::new(0, 28),
9963 ),
9964 severity: Some(lsp::DiagnosticSeverity::ERROR),
9965 ..Default::default()
9966 },
9967 ],
9968 },
9969 &[],
9970 cx,
9971 )
9972 .unwrap()
9973 });
9974 });
9975
9976 executor.run_until_parked();
9977
9978 cx.update_editor(|editor, cx| {
9979 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9980 });
9981
9982 cx.assert_editor_state(indoc! {"
9983 fn func(abc def: i32) -> ˇu32 {
9984 }
9985 "});
9986
9987 cx.update_editor(|editor, cx| {
9988 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9989 });
9990
9991 cx.assert_editor_state(indoc! {"
9992 fn func(abc ˇdef: i32) -> u32 {
9993 }
9994 "});
9995
9996 cx.update_editor(|editor, cx| {
9997 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9998 });
9999
10000 cx.assert_editor_state(indoc! {"
10001 fn func(abcˇ def: i32) -> u32 {
10002 }
10003 "});
10004
10005 cx.update_editor(|editor, cx| {
10006 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10007 });
10008
10009 cx.assert_editor_state(indoc! {"
10010 fn func(abc def: i32) -> ˇu32 {
10011 }
10012 "});
10013}
10014
10015#[gpui::test]
10016async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10017 init_test(cx, |_| {});
10018
10019 let mut cx = EditorTestContext::new(cx).await;
10020
10021 cx.set_state(indoc! {"
10022 fn func(abˇc def: i32) -> u32 {
10023 }
10024 "});
10025 let lsp_store =
10026 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10027
10028 cx.update(|cx| {
10029 lsp_store.update(cx, |lsp_store, cx| {
10030 lsp_store.update_diagnostics(
10031 LanguageServerId(0),
10032 lsp::PublishDiagnosticsParams {
10033 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10034 version: None,
10035 diagnostics: vec![lsp::Diagnostic {
10036 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10037 severity: Some(lsp::DiagnosticSeverity::ERROR),
10038 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10039 ..Default::default()
10040 }],
10041 },
10042 &[],
10043 cx,
10044 )
10045 })
10046 }).unwrap();
10047 cx.run_until_parked();
10048 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10049 cx.run_until_parked();
10050 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10051}
10052
10053#[gpui::test]
10054async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10055 init_test(cx, |_| {});
10056
10057 let mut cx = EditorTestContext::new(cx).await;
10058
10059 let diff_base = r#"
10060 use some::mod;
10061
10062 const A: u32 = 42;
10063
10064 fn main() {
10065 println!("hello");
10066
10067 println!("world");
10068 }
10069 "#
10070 .unindent();
10071
10072 // Edits are modified, removed, modified, added
10073 cx.set_state(
10074 &r#"
10075 use some::modified;
10076
10077 ˇ
10078 fn main() {
10079 println!("hello there");
10080
10081 println!("around the");
10082 println!("world");
10083 }
10084 "#
10085 .unindent(),
10086 );
10087
10088 cx.set_diff_base(&diff_base);
10089 executor.run_until_parked();
10090
10091 cx.update_editor(|editor, cx| {
10092 //Wrap around the bottom of the buffer
10093 for _ in 0..3 {
10094 editor.go_to_next_hunk(&GoToHunk, cx);
10095 }
10096 });
10097
10098 cx.assert_editor_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.update_editor(|editor, cx| {
10114 //Wrap around the top of the buffer
10115 for _ in 0..2 {
10116 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10117 }
10118 });
10119
10120 cx.assert_editor_state(
10121 &r#"
10122 use some::modified;
10123
10124
10125 fn main() {
10126 ˇ println!("hello there");
10127
10128 println!("around the");
10129 println!("world");
10130 }
10131 "#
10132 .unindent(),
10133 );
10134
10135 cx.update_editor(|editor, cx| {
10136 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10137 });
10138
10139 cx.assert_editor_state(
10140 &r#"
10141 use some::modified;
10142
10143 ˇ
10144 fn main() {
10145 println!("hello there");
10146
10147 println!("around the");
10148 println!("world");
10149 }
10150 "#
10151 .unindent(),
10152 );
10153
10154 cx.update_editor(|editor, cx| {
10155 for _ in 0..3 {
10156 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10157 }
10158 });
10159
10160 cx.assert_editor_state(
10161 &r#"
10162 use some::modified;
10163
10164
10165 fn main() {
10166 ˇ println!("hello there");
10167
10168 println!("around the");
10169 println!("world");
10170 }
10171 "#
10172 .unindent(),
10173 );
10174
10175 cx.update_editor(|editor, cx| {
10176 editor.fold(&Fold, cx);
10177
10178 //Make sure that the fold only gets one hunk
10179 for _ in 0..4 {
10180 editor.go_to_next_hunk(&GoToHunk, cx);
10181 }
10182 });
10183
10184 cx.assert_editor_state(
10185 &r#"
10186 ˇuse some::modified;
10187
10188
10189 fn main() {
10190 println!("hello there");
10191
10192 println!("around the");
10193 println!("world");
10194 }
10195 "#
10196 .unindent(),
10197 );
10198}
10199
10200#[test]
10201fn test_split_words() {
10202 fn split(text: &str) -> Vec<&str> {
10203 split_words(text).collect()
10204 }
10205
10206 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10207 assert_eq!(split("hello_world"), &["hello_", "world"]);
10208 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10209 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10210 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10211 assert_eq!(split("helloworld"), &["helloworld"]);
10212
10213 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10214}
10215
10216#[gpui::test]
10217async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10218 init_test(cx, |_| {});
10219
10220 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10221 let mut assert = |before, after| {
10222 let _state_context = cx.set_state(before);
10223 cx.update_editor(|editor, cx| {
10224 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10225 });
10226 cx.assert_editor_state(after);
10227 };
10228
10229 // Outside bracket jumps to outside of matching bracket
10230 assert("console.logˇ(var);", "console.log(var)ˇ;");
10231 assert("console.log(var)ˇ;", "console.logˇ(var);");
10232
10233 // Inside bracket jumps to inside of matching bracket
10234 assert("console.log(ˇvar);", "console.log(varˇ);");
10235 assert("console.log(varˇ);", "console.log(ˇvar);");
10236
10237 // When outside a bracket and inside, favor jumping to the inside bracket
10238 assert(
10239 "console.log('foo', [1, 2, 3]ˇ);",
10240 "console.log(ˇ'foo', [1, 2, 3]);",
10241 );
10242 assert(
10243 "console.log(ˇ'foo', [1, 2, 3]);",
10244 "console.log('foo', [1, 2, 3]ˇ);",
10245 );
10246
10247 // Bias forward if two options are equally likely
10248 assert(
10249 "let result = curried_fun()ˇ();",
10250 "let result = curried_fun()()ˇ;",
10251 );
10252
10253 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10254 assert(
10255 indoc! {"
10256 function test() {
10257 console.log('test')ˇ
10258 }"},
10259 indoc! {"
10260 function test() {
10261 console.logˇ('test')
10262 }"},
10263 );
10264}
10265
10266#[gpui::test]
10267async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10268 init_test(cx, |_| {});
10269
10270 let fs = FakeFs::new(cx.executor());
10271 fs.insert_tree(
10272 "/a",
10273 json!({
10274 "main.rs": "fn main() { let a = 5; }",
10275 "other.rs": "// Test file",
10276 }),
10277 )
10278 .await;
10279 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10280
10281 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10282 language_registry.add(Arc::new(Language::new(
10283 LanguageConfig {
10284 name: "Rust".into(),
10285 matcher: LanguageMatcher {
10286 path_suffixes: vec!["rs".to_string()],
10287 ..Default::default()
10288 },
10289 brackets: BracketPairConfig {
10290 pairs: vec![BracketPair {
10291 start: "{".to_string(),
10292 end: "}".to_string(),
10293 close: true,
10294 surround: true,
10295 newline: true,
10296 }],
10297 disabled_scopes_by_bracket_ix: Vec::new(),
10298 },
10299 ..Default::default()
10300 },
10301 Some(tree_sitter_rust::LANGUAGE.into()),
10302 )));
10303 let mut fake_servers = language_registry.register_fake_lsp(
10304 "Rust",
10305 FakeLspAdapter {
10306 capabilities: lsp::ServerCapabilities {
10307 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10308 first_trigger_character: "{".to_string(),
10309 more_trigger_character: None,
10310 }),
10311 ..Default::default()
10312 },
10313 ..Default::default()
10314 },
10315 );
10316
10317 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10318
10319 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10320
10321 let worktree_id = workspace
10322 .update(cx, |workspace, cx| {
10323 workspace.project().update(cx, |project, cx| {
10324 project.worktrees(cx).next().unwrap().read(cx).id()
10325 })
10326 })
10327 .unwrap();
10328
10329 let buffer = project
10330 .update(cx, |project, cx| {
10331 project.open_local_buffer("/a/main.rs", cx)
10332 })
10333 .await
10334 .unwrap();
10335 cx.executor().run_until_parked();
10336 cx.executor().start_waiting();
10337 let fake_server = fake_servers.next().await.unwrap();
10338 let editor_handle = workspace
10339 .update(cx, |workspace, cx| {
10340 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10341 })
10342 .unwrap()
10343 .await
10344 .unwrap()
10345 .downcast::<Editor>()
10346 .unwrap();
10347
10348 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10349 assert_eq!(
10350 params.text_document_position.text_document.uri,
10351 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10352 );
10353 assert_eq!(
10354 params.text_document_position.position,
10355 lsp::Position::new(0, 21),
10356 );
10357
10358 Ok(Some(vec![lsp::TextEdit {
10359 new_text: "]".to_string(),
10360 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10361 }]))
10362 });
10363
10364 editor_handle.update(cx, |editor, cx| {
10365 editor.focus(cx);
10366 editor.change_selections(None, cx, |s| {
10367 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10368 });
10369 editor.handle_input("{", cx);
10370 });
10371
10372 cx.executor().run_until_parked();
10373
10374 buffer.update(cx, |buffer, _| {
10375 assert_eq!(
10376 buffer.text(),
10377 "fn main() { let a = {5}; }",
10378 "No extra braces from on type formatting should appear in the buffer"
10379 )
10380 });
10381}
10382
10383#[gpui::test]
10384async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10385 init_test(cx, |_| {});
10386
10387 let fs = FakeFs::new(cx.executor());
10388 fs.insert_tree(
10389 "/a",
10390 json!({
10391 "main.rs": "fn main() { let a = 5; }",
10392 "other.rs": "// Test file",
10393 }),
10394 )
10395 .await;
10396
10397 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10398
10399 let server_restarts = Arc::new(AtomicUsize::new(0));
10400 let closure_restarts = Arc::clone(&server_restarts);
10401 let language_server_name = "test language server";
10402 let language_name: LanguageName = "Rust".into();
10403
10404 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10405 language_registry.add(Arc::new(Language::new(
10406 LanguageConfig {
10407 name: language_name.clone(),
10408 matcher: LanguageMatcher {
10409 path_suffixes: vec!["rs".to_string()],
10410 ..Default::default()
10411 },
10412 ..Default::default()
10413 },
10414 Some(tree_sitter_rust::LANGUAGE.into()),
10415 )));
10416 let mut fake_servers = language_registry.register_fake_lsp(
10417 "Rust",
10418 FakeLspAdapter {
10419 name: language_server_name,
10420 initialization_options: Some(json!({
10421 "testOptionValue": true
10422 })),
10423 initializer: Some(Box::new(move |fake_server| {
10424 let task_restarts = Arc::clone(&closure_restarts);
10425 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10426 task_restarts.fetch_add(1, atomic::Ordering::Release);
10427 futures::future::ready(Ok(()))
10428 });
10429 })),
10430 ..Default::default()
10431 },
10432 );
10433
10434 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10435 let _buffer = project
10436 .update(cx, |project, cx| {
10437 project.open_local_buffer("/a/main.rs", cx)
10438 })
10439 .await
10440 .unwrap();
10441 let _fake_server = fake_servers.next().await.unwrap();
10442 update_test_language_settings(cx, |language_settings| {
10443 language_settings.languages.insert(
10444 language_name.clone(),
10445 LanguageSettingsContent {
10446 tab_size: NonZeroU32::new(8),
10447 ..Default::default()
10448 },
10449 );
10450 });
10451 cx.executor().run_until_parked();
10452 assert_eq!(
10453 server_restarts.load(atomic::Ordering::Acquire),
10454 0,
10455 "Should not restart LSP server on an unrelated change"
10456 );
10457
10458 update_test_project_settings(cx, |project_settings| {
10459 project_settings.lsp.insert(
10460 "Some other server name".into(),
10461 LspSettings {
10462 binary: None,
10463 settings: None,
10464 initialization_options: Some(json!({
10465 "some other init value": false
10466 })),
10467 },
10468 );
10469 });
10470 cx.executor().run_until_parked();
10471 assert_eq!(
10472 server_restarts.load(atomic::Ordering::Acquire),
10473 0,
10474 "Should not restart LSP server on an unrelated LSP settings change"
10475 );
10476
10477 update_test_project_settings(cx, |project_settings| {
10478 project_settings.lsp.insert(
10479 language_server_name.into(),
10480 LspSettings {
10481 binary: None,
10482 settings: None,
10483 initialization_options: Some(json!({
10484 "anotherInitValue": false
10485 })),
10486 },
10487 );
10488 });
10489 cx.executor().run_until_parked();
10490 assert_eq!(
10491 server_restarts.load(atomic::Ordering::Acquire),
10492 1,
10493 "Should restart LSP server on a related LSP settings change"
10494 );
10495
10496 update_test_project_settings(cx, |project_settings| {
10497 project_settings.lsp.insert(
10498 language_server_name.into(),
10499 LspSettings {
10500 binary: None,
10501 settings: None,
10502 initialization_options: Some(json!({
10503 "anotherInitValue": false
10504 })),
10505 },
10506 );
10507 });
10508 cx.executor().run_until_parked();
10509 assert_eq!(
10510 server_restarts.load(atomic::Ordering::Acquire),
10511 1,
10512 "Should not restart LSP server on a related LSP settings change that is the same"
10513 );
10514
10515 update_test_project_settings(cx, |project_settings| {
10516 project_settings.lsp.insert(
10517 language_server_name.into(),
10518 LspSettings {
10519 binary: None,
10520 settings: None,
10521 initialization_options: None,
10522 },
10523 );
10524 });
10525 cx.executor().run_until_parked();
10526 assert_eq!(
10527 server_restarts.load(atomic::Ordering::Acquire),
10528 2,
10529 "Should restart LSP server on another related LSP settings change"
10530 );
10531}
10532
10533#[gpui::test]
10534async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10535 init_test(cx, |_| {});
10536
10537 let mut cx = EditorLspTestContext::new_rust(
10538 lsp::ServerCapabilities {
10539 completion_provider: Some(lsp::CompletionOptions {
10540 trigger_characters: Some(vec![".".to_string()]),
10541 resolve_provider: Some(true),
10542 ..Default::default()
10543 }),
10544 ..Default::default()
10545 },
10546 cx,
10547 )
10548 .await;
10549
10550 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10551 cx.simulate_keystroke(".");
10552 let completion_item = lsp::CompletionItem {
10553 label: "some".into(),
10554 kind: Some(lsp::CompletionItemKind::SNIPPET),
10555 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10556 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10557 kind: lsp::MarkupKind::Markdown,
10558 value: "```rust\nSome(2)\n```".to_string(),
10559 })),
10560 deprecated: Some(false),
10561 sort_text: Some("fffffff2".to_string()),
10562 filter_text: Some("some".to_string()),
10563 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10564 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10565 range: lsp::Range {
10566 start: lsp::Position {
10567 line: 0,
10568 character: 22,
10569 },
10570 end: lsp::Position {
10571 line: 0,
10572 character: 22,
10573 },
10574 },
10575 new_text: "Some(2)".to_string(),
10576 })),
10577 additional_text_edits: Some(vec![lsp::TextEdit {
10578 range: lsp::Range {
10579 start: lsp::Position {
10580 line: 0,
10581 character: 20,
10582 },
10583 end: lsp::Position {
10584 line: 0,
10585 character: 22,
10586 },
10587 },
10588 new_text: "".to_string(),
10589 }]),
10590 ..Default::default()
10591 };
10592
10593 let closure_completion_item = completion_item.clone();
10594 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10595 let task_completion_item = closure_completion_item.clone();
10596 async move {
10597 Ok(Some(lsp::CompletionResponse::Array(vec![
10598 task_completion_item,
10599 ])))
10600 }
10601 });
10602
10603 request.next().await;
10604
10605 cx.condition(|editor, _| editor.context_menu_visible())
10606 .await;
10607 let apply_additional_edits = cx.update_editor(|editor, cx| {
10608 editor
10609 .confirm_completion(&ConfirmCompletion::default(), cx)
10610 .unwrap()
10611 });
10612 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10613
10614 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10615 let task_completion_item = completion_item.clone();
10616 async move { Ok(task_completion_item) }
10617 })
10618 .next()
10619 .await
10620 .unwrap();
10621 apply_additional_edits.await.unwrap();
10622 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10623}
10624
10625#[gpui::test]
10626async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) {
10627 init_test(cx, |_| {});
10628
10629 let mut cx = EditorLspTestContext::new_rust(
10630 lsp::ServerCapabilities {
10631 completion_provider: Some(lsp::CompletionOptions {
10632 trigger_characters: Some(vec![".".to_string()]),
10633 resolve_provider: Some(true),
10634 ..Default::default()
10635 }),
10636 ..Default::default()
10637 },
10638 cx,
10639 )
10640 .await;
10641
10642 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10643 cx.simulate_keystroke(".");
10644
10645 let completion_item = lsp::CompletionItem {
10646 label: "unresolved".to_string(),
10647 detail: None,
10648 documentation: None,
10649 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10650 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10651 new_text: ".unresolved".to_string(),
10652 })),
10653 ..lsp::CompletionItem::default()
10654 };
10655
10656 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10657 let item = completion_item.clone();
10658 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10659 })
10660 .next()
10661 .await;
10662
10663 cx.condition(|editor, _| editor.context_menu_visible())
10664 .await;
10665 cx.update_editor(|editor, _| {
10666 let context_menu = editor.context_menu.read();
10667 let context_menu = context_menu
10668 .as_ref()
10669 .expect("Should have the context menu deployed");
10670 match context_menu {
10671 CodeContextMenu::Completions(completions_menu) => {
10672 let completions = completions_menu.completions.read();
10673 assert_eq!(completions.len(), 1, "Should have one completion");
10674 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10675 }
10676 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10677 }
10678 });
10679
10680 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10681 Ok(lsp::CompletionItem {
10682 label: "resolved".to_string(),
10683 detail: Some("Now resolved!".to_string()),
10684 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10685 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10686 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10687 new_text: ".resolved".to_string(),
10688 })),
10689 ..lsp::CompletionItem::default()
10690 })
10691 })
10692 .next()
10693 .await;
10694 cx.run_until_parked();
10695
10696 cx.update_editor(|editor, _| {
10697 let context_menu = editor.context_menu.read();
10698 let context_menu = context_menu
10699 .as_ref()
10700 .expect("Should have the context menu deployed");
10701 match context_menu {
10702 CodeContextMenu::Completions(completions_menu) => {
10703 let completions = completions_menu.completions.read();
10704 assert_eq!(completions.len(), 1, "Should have one completion");
10705 assert_eq!(
10706 completions.get(0).unwrap().label.text,
10707 "resolved",
10708 "Should update the completion label after resolving"
10709 );
10710 }
10711 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10712 }
10713 });
10714}
10715
10716#[gpui::test]
10717async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10718 init_test(cx, |_| {});
10719
10720 let item_0 = lsp::CompletionItem {
10721 label: "abs".into(),
10722 insert_text: Some("abs".into()),
10723 data: Some(json!({ "very": "special"})),
10724 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10725 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10726 lsp::InsertReplaceEdit {
10727 new_text: "abs".to_string(),
10728 insert: lsp::Range::default(),
10729 replace: lsp::Range::default(),
10730 },
10731 )),
10732 ..lsp::CompletionItem::default()
10733 };
10734 let items = iter::once(item_0.clone())
10735 .chain((11..51).map(|i| lsp::CompletionItem {
10736 label: format!("item_{}", i),
10737 insert_text: Some(format!("item_{}", i)),
10738 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10739 ..lsp::CompletionItem::default()
10740 }))
10741 .collect::<Vec<_>>();
10742
10743 let default_commit_characters = vec!["?".to_string()];
10744 let default_data = json!({ "default": "data"});
10745 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10746 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10747 let default_edit_range = lsp::Range {
10748 start: lsp::Position {
10749 line: 0,
10750 character: 5,
10751 },
10752 end: lsp::Position {
10753 line: 0,
10754 character: 5,
10755 },
10756 };
10757
10758 let item_0_out = lsp::CompletionItem {
10759 commit_characters: Some(default_commit_characters.clone()),
10760 insert_text_format: Some(default_insert_text_format),
10761 ..item_0
10762 };
10763 let items_out = iter::once(item_0_out)
10764 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
10765 commit_characters: Some(default_commit_characters.clone()),
10766 data: Some(default_data.clone()),
10767 insert_text_mode: Some(default_insert_text_mode),
10768 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10769 range: default_edit_range,
10770 new_text: item.label.clone(),
10771 })),
10772 ..item.clone()
10773 }))
10774 .collect::<Vec<lsp::CompletionItem>>();
10775
10776 let mut cx = EditorLspTestContext::new_rust(
10777 lsp::ServerCapabilities {
10778 completion_provider: Some(lsp::CompletionOptions {
10779 trigger_characters: Some(vec![".".to_string()]),
10780 resolve_provider: Some(true),
10781 ..Default::default()
10782 }),
10783 ..Default::default()
10784 },
10785 cx,
10786 )
10787 .await;
10788
10789 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10790 cx.simulate_keystroke(".");
10791
10792 let completion_data = default_data.clone();
10793 let completion_characters = default_commit_characters.clone();
10794 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10795 let default_data = completion_data.clone();
10796 let default_commit_characters = completion_characters.clone();
10797 let items = items.clone();
10798 async move {
10799 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10800 items,
10801 item_defaults: Some(lsp::CompletionListItemDefaults {
10802 data: Some(default_data.clone()),
10803 commit_characters: Some(default_commit_characters.clone()),
10804 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10805 default_edit_range,
10806 )),
10807 insert_text_format: Some(default_insert_text_format),
10808 insert_text_mode: Some(default_insert_text_mode),
10809 }),
10810 ..lsp::CompletionList::default()
10811 })))
10812 }
10813 })
10814 .next()
10815 .await;
10816
10817 let resolved_items = Arc::new(Mutex::new(Vec::new()));
10818 cx.lsp
10819 .server
10820 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10821 let closure_resolved_items = resolved_items.clone();
10822 move |item_to_resolve, _| {
10823 let closure_resolved_items = closure_resolved_items.clone();
10824 async move {
10825 closure_resolved_items.lock().push(item_to_resolve.clone());
10826 Ok(item_to_resolve)
10827 }
10828 }
10829 })
10830 .detach();
10831
10832 cx.condition(|editor, _| editor.context_menu_visible())
10833 .await;
10834 cx.run_until_parked();
10835 cx.update_editor(|editor, _| {
10836 let menu = editor.context_menu.read();
10837 match menu.as_ref().expect("should have the completions menu") {
10838 CodeContextMenu::Completions(completions_menu) => {
10839 assert_eq!(
10840 completions_menu
10841 .matches
10842 .iter()
10843 .map(|c| c.string.clone())
10844 .collect::<Vec<String>>(),
10845 items_out
10846 .iter()
10847 .map(|completion| completion.label.clone())
10848 .collect::<Vec<String>>()
10849 );
10850 }
10851 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10852 }
10853 });
10854 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
10855 // with 4 from the end.
10856 assert_eq!(
10857 *resolved_items.lock(),
10858 [
10859 &items_out[0..16],
10860 &items_out[items_out.len() - 4..items_out.len()]
10861 ]
10862 .concat()
10863 .iter()
10864 .cloned()
10865 .collect::<Vec<lsp::CompletionItem>>()
10866 );
10867 resolved_items.lock().clear();
10868
10869 cx.update_editor(|editor, cx| {
10870 editor.context_menu_prev(&ContextMenuPrev, cx);
10871 });
10872 cx.run_until_parked();
10873 // Completions that have already been resolved are skipped.
10874 assert_eq!(
10875 *resolved_items.lock(),
10876 [
10877 // Selected item is always resolved even if it was resolved before.
10878 &items_out[items_out.len() - 1..items_out.len()],
10879 &items_out[items_out.len() - 16..items_out.len() - 4]
10880 ]
10881 .concat()
10882 .iter()
10883 .cloned()
10884 .collect::<Vec<lsp::CompletionItem>>()
10885 );
10886 resolved_items.lock().clear();
10887}
10888
10889#[gpui::test]
10890async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10891 init_test(cx, |_| {});
10892
10893 let mut cx = EditorLspTestContext::new(
10894 Language::new(
10895 LanguageConfig {
10896 matcher: LanguageMatcher {
10897 path_suffixes: vec!["jsx".into()],
10898 ..Default::default()
10899 },
10900 overrides: [(
10901 "element".into(),
10902 LanguageConfigOverride {
10903 word_characters: Override::Set(['-'].into_iter().collect()),
10904 ..Default::default()
10905 },
10906 )]
10907 .into_iter()
10908 .collect(),
10909 ..Default::default()
10910 },
10911 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10912 )
10913 .with_override_query("(jsx_self_closing_element) @element")
10914 .unwrap(),
10915 lsp::ServerCapabilities {
10916 completion_provider: Some(lsp::CompletionOptions {
10917 trigger_characters: Some(vec![":".to_string()]),
10918 ..Default::default()
10919 }),
10920 ..Default::default()
10921 },
10922 cx,
10923 )
10924 .await;
10925
10926 cx.lsp
10927 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10928 Ok(Some(lsp::CompletionResponse::Array(vec![
10929 lsp::CompletionItem {
10930 label: "bg-blue".into(),
10931 ..Default::default()
10932 },
10933 lsp::CompletionItem {
10934 label: "bg-red".into(),
10935 ..Default::default()
10936 },
10937 lsp::CompletionItem {
10938 label: "bg-yellow".into(),
10939 ..Default::default()
10940 },
10941 ])))
10942 });
10943
10944 cx.set_state(r#"<p class="bgˇ" />"#);
10945
10946 // Trigger completion when typing a dash, because the dash is an extra
10947 // word character in the 'element' scope, which contains the cursor.
10948 cx.simulate_keystroke("-");
10949 cx.executor().run_until_parked();
10950 cx.update_editor(|editor, _| {
10951 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10952 assert_eq!(
10953 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10954 &["bg-red", "bg-blue", "bg-yellow"]
10955 );
10956 } else {
10957 panic!("expected completion menu to be open");
10958 }
10959 });
10960
10961 cx.simulate_keystroke("l");
10962 cx.executor().run_until_parked();
10963 cx.update_editor(|editor, _| {
10964 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10965 assert_eq!(
10966 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10967 &["bg-blue", "bg-yellow"]
10968 );
10969 } else {
10970 panic!("expected completion menu to be open");
10971 }
10972 });
10973
10974 // When filtering completions, consider the character after the '-' to
10975 // be the start of a subword.
10976 cx.set_state(r#"<p class="yelˇ" />"#);
10977 cx.simulate_keystroke("l");
10978 cx.executor().run_until_parked();
10979 cx.update_editor(|editor, _| {
10980 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10981 assert_eq!(
10982 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10983 &["bg-yellow"]
10984 );
10985 } else {
10986 panic!("expected completion menu to be open");
10987 }
10988 });
10989}
10990
10991#[gpui::test]
10992async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10993 init_test(cx, |settings| {
10994 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10995 FormatterList(vec![Formatter::Prettier].into()),
10996 ))
10997 });
10998
10999 let fs = FakeFs::new(cx.executor());
11000 fs.insert_file("/file.ts", Default::default()).await;
11001
11002 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11003 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11004
11005 language_registry.add(Arc::new(Language::new(
11006 LanguageConfig {
11007 name: "TypeScript".into(),
11008 matcher: LanguageMatcher {
11009 path_suffixes: vec!["ts".to_string()],
11010 ..Default::default()
11011 },
11012 ..Default::default()
11013 },
11014 Some(tree_sitter_rust::LANGUAGE.into()),
11015 )));
11016 update_test_language_settings(cx, |settings| {
11017 settings.defaults.prettier = Some(PrettierSettings {
11018 allowed: true,
11019 ..PrettierSettings::default()
11020 });
11021 });
11022
11023 let test_plugin = "test_plugin";
11024 let _ = language_registry.register_fake_lsp(
11025 "TypeScript",
11026 FakeLspAdapter {
11027 prettier_plugins: vec![test_plugin],
11028 ..Default::default()
11029 },
11030 );
11031
11032 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11033 let buffer = project
11034 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11035 .await
11036 .unwrap();
11037
11038 let buffer_text = "one\ntwo\nthree\n";
11039 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11040 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11041 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11042
11043 editor
11044 .update(cx, |editor, cx| {
11045 editor.perform_format(
11046 project.clone(),
11047 FormatTrigger::Manual,
11048 FormatTarget::Buffer,
11049 cx,
11050 )
11051 })
11052 .unwrap()
11053 .await;
11054 assert_eq!(
11055 editor.update(cx, |editor, cx| editor.text(cx)),
11056 buffer_text.to_string() + prettier_format_suffix,
11057 "Test prettier formatting was not applied to the original buffer text",
11058 );
11059
11060 update_test_language_settings(cx, |settings| {
11061 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11062 });
11063 let format = editor.update(cx, |editor, cx| {
11064 editor.perform_format(
11065 project.clone(),
11066 FormatTrigger::Manual,
11067 FormatTarget::Buffer,
11068 cx,
11069 )
11070 });
11071 format.await.unwrap();
11072 assert_eq!(
11073 editor.update(cx, |editor, cx| editor.text(cx)),
11074 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11075 "Autoformatting (via test prettier) was not applied to the original buffer text",
11076 );
11077}
11078
11079#[gpui::test]
11080async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11081 init_test(cx, |_| {});
11082 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11083 let base_text = indoc! {r#"
11084 struct Row;
11085 struct Row1;
11086 struct Row2;
11087
11088 struct Row4;
11089 struct Row5;
11090 struct Row6;
11091
11092 struct Row8;
11093 struct Row9;
11094 struct Row10;"#};
11095
11096 // When addition hunks are not adjacent to carets, no hunk revert is performed
11097 assert_hunk_revert(
11098 indoc! {r#"struct Row;
11099 struct Row1;
11100 struct Row1.1;
11101 struct Row1.2;
11102 struct Row2;ˇ
11103
11104 struct Row4;
11105 struct Row5;
11106 struct Row6;
11107
11108 struct Row8;
11109 ˇstruct Row9;
11110 struct Row9.1;
11111 struct Row9.2;
11112 struct Row9.3;
11113 struct Row10;"#},
11114 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11115 indoc! {r#"struct Row;
11116 struct Row1;
11117 struct Row1.1;
11118 struct Row1.2;
11119 struct Row2;ˇ
11120
11121 struct Row4;
11122 struct Row5;
11123 struct Row6;
11124
11125 struct Row8;
11126 ˇstruct Row9;
11127 struct Row9.1;
11128 struct Row9.2;
11129 struct Row9.3;
11130 struct Row10;"#},
11131 base_text,
11132 &mut cx,
11133 );
11134 // Same for selections
11135 assert_hunk_revert(
11136 indoc! {r#"struct Row;
11137 struct Row1;
11138 struct Row2;
11139 struct Row2.1;
11140 struct Row2.2;
11141 «ˇ
11142 struct Row4;
11143 struct» Row5;
11144 «struct Row6;
11145 ˇ»
11146 struct Row9.1;
11147 struct Row9.2;
11148 struct Row9.3;
11149 struct Row8;
11150 struct Row9;
11151 struct Row10;"#},
11152 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11153 indoc! {r#"struct Row;
11154 struct Row1;
11155 struct Row2;
11156 struct Row2.1;
11157 struct Row2.2;
11158 «ˇ
11159 struct Row4;
11160 struct» Row5;
11161 «struct Row6;
11162 ˇ»
11163 struct Row9.1;
11164 struct Row9.2;
11165 struct Row9.3;
11166 struct Row8;
11167 struct Row9;
11168 struct Row10;"#},
11169 base_text,
11170 &mut cx,
11171 );
11172
11173 // When carets and selections intersect the addition hunks, those are reverted.
11174 // Adjacent carets got merged.
11175 assert_hunk_revert(
11176 indoc! {r#"struct Row;
11177 ˇ// something on the top
11178 struct Row1;
11179 struct Row2;
11180 struct Roˇw3.1;
11181 struct Row2.2;
11182 struct Row2.3;ˇ
11183
11184 struct Row4;
11185 struct ˇRow5.1;
11186 struct Row5.2;
11187 struct «Rowˇ»5.3;
11188 struct Row5;
11189 struct Row6;
11190 ˇ
11191 struct Row9.1;
11192 struct «Rowˇ»9.2;
11193 struct «ˇRow»9.3;
11194 struct Row8;
11195 struct Row9;
11196 «ˇ// something on bottom»
11197 struct Row10;"#},
11198 vec![
11199 DiffHunkStatus::Added,
11200 DiffHunkStatus::Added,
11201 DiffHunkStatus::Added,
11202 DiffHunkStatus::Added,
11203 DiffHunkStatus::Added,
11204 ],
11205 indoc! {r#"struct Row;
11206 ˇstruct Row1;
11207 struct Row2;
11208 ˇ
11209 struct Row4;
11210 ˇstruct Row5;
11211 struct Row6;
11212 ˇ
11213 ˇstruct Row8;
11214 struct Row9;
11215 ˇstruct Row10;"#},
11216 base_text,
11217 &mut cx,
11218 );
11219}
11220
11221#[gpui::test]
11222async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11223 init_test(cx, |_| {});
11224 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11225 let base_text = indoc! {r#"
11226 struct Row;
11227 struct Row1;
11228 struct Row2;
11229
11230 struct Row4;
11231 struct Row5;
11232 struct Row6;
11233
11234 struct Row8;
11235 struct Row9;
11236 struct Row10;"#};
11237
11238 // Modification hunks behave the same as the addition ones.
11239 assert_hunk_revert(
11240 indoc! {r#"struct Row;
11241 struct Row1;
11242 struct Row33;
11243 ˇ
11244 struct Row4;
11245 struct Row5;
11246 struct Row6;
11247 ˇ
11248 struct Row99;
11249 struct Row9;
11250 struct Row10;"#},
11251 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11252 indoc! {r#"struct Row;
11253 struct Row1;
11254 struct Row33;
11255 ˇ
11256 struct Row4;
11257 struct Row5;
11258 struct Row6;
11259 ˇ
11260 struct Row99;
11261 struct Row9;
11262 struct Row10;"#},
11263 base_text,
11264 &mut cx,
11265 );
11266 assert_hunk_revert(
11267 indoc! {r#"struct Row;
11268 struct Row1;
11269 struct Row33;
11270 «ˇ
11271 struct Row4;
11272 struct» Row5;
11273 «struct Row6;
11274 ˇ»
11275 struct Row99;
11276 struct Row9;
11277 struct Row10;"#},
11278 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11279 indoc! {r#"struct Row;
11280 struct Row1;
11281 struct Row33;
11282 «ˇ
11283 struct Row4;
11284 struct» Row5;
11285 «struct Row6;
11286 ˇ»
11287 struct Row99;
11288 struct Row9;
11289 struct Row10;"#},
11290 base_text,
11291 &mut cx,
11292 );
11293
11294 assert_hunk_revert(
11295 indoc! {r#"ˇstruct Row1.1;
11296 struct Row1;
11297 «ˇstr»uct Row22;
11298
11299 struct ˇRow44;
11300 struct Row5;
11301 struct «Rˇ»ow66;ˇ
11302
11303 «struˇ»ct Row88;
11304 struct Row9;
11305 struct Row1011;ˇ"#},
11306 vec![
11307 DiffHunkStatus::Modified,
11308 DiffHunkStatus::Modified,
11309 DiffHunkStatus::Modified,
11310 DiffHunkStatus::Modified,
11311 DiffHunkStatus::Modified,
11312 DiffHunkStatus::Modified,
11313 ],
11314 indoc! {r#"struct Row;
11315 ˇstruct Row1;
11316 struct Row2;
11317 ˇ
11318 struct Row4;
11319 ˇstruct Row5;
11320 struct Row6;
11321 ˇ
11322 struct Row8;
11323 ˇstruct Row9;
11324 struct Row10;ˇ"#},
11325 base_text,
11326 &mut cx,
11327 );
11328}
11329
11330#[gpui::test]
11331async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11332 init_test(cx, |_| {});
11333 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11334 let base_text = indoc! {r#"struct Row;
11335struct Row1;
11336struct Row2;
11337
11338struct Row4;
11339struct Row5;
11340struct Row6;
11341
11342struct Row8;
11343struct Row9;
11344struct Row10;"#};
11345
11346 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11347 assert_hunk_revert(
11348 indoc! {r#"struct Row;
11349 struct Row2;
11350
11351 ˇstruct Row4;
11352 struct Row5;
11353 struct Row6;
11354 ˇ
11355 struct Row8;
11356 struct Row10;"#},
11357 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11358 indoc! {r#"struct Row;
11359 struct Row2;
11360
11361 ˇstruct Row4;
11362 struct Row5;
11363 struct Row6;
11364 ˇ
11365 struct Row8;
11366 struct Row10;"#},
11367 base_text,
11368 &mut cx,
11369 );
11370 assert_hunk_revert(
11371 indoc! {r#"struct Row;
11372 struct Row2;
11373
11374 «ˇstruct Row4;
11375 struct» Row5;
11376 «struct Row6;
11377 ˇ»
11378 struct Row8;
11379 struct Row10;"#},
11380 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11381 indoc! {r#"struct Row;
11382 struct Row2;
11383
11384 «ˇstruct Row4;
11385 struct» Row5;
11386 «struct Row6;
11387 ˇ»
11388 struct Row8;
11389 struct Row10;"#},
11390 base_text,
11391 &mut cx,
11392 );
11393
11394 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11395 assert_hunk_revert(
11396 indoc! {r#"struct Row;
11397 ˇstruct Row2;
11398
11399 struct Row4;
11400 struct Row5;
11401 struct Row6;
11402
11403 struct Row8;ˇ
11404 struct Row10;"#},
11405 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11406 indoc! {r#"struct Row;
11407 struct Row1;
11408 ˇstruct Row2;
11409
11410 struct Row4;
11411 struct Row5;
11412 struct Row6;
11413
11414 struct Row8;ˇ
11415 struct Row9;
11416 struct Row10;"#},
11417 base_text,
11418 &mut cx,
11419 );
11420 assert_hunk_revert(
11421 indoc! {r#"struct Row;
11422 struct Row2«ˇ;
11423 struct Row4;
11424 struct» Row5;
11425 «struct Row6;
11426
11427 struct Row8;ˇ»
11428 struct Row10;"#},
11429 vec![
11430 DiffHunkStatus::Removed,
11431 DiffHunkStatus::Removed,
11432 DiffHunkStatus::Removed,
11433 ],
11434 indoc! {r#"struct Row;
11435 struct Row1;
11436 struct Row2«ˇ;
11437
11438 struct Row4;
11439 struct» Row5;
11440 «struct Row6;
11441
11442 struct Row8;ˇ»
11443 struct Row9;
11444 struct Row10;"#},
11445 base_text,
11446 &mut cx,
11447 );
11448}
11449
11450#[gpui::test]
11451async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11452 init_test(cx, |_| {});
11453
11454 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11455 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11456 let base_text_3 =
11457 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11458
11459 let text_1 = edit_first_char_of_every_line(base_text_1);
11460 let text_2 = edit_first_char_of_every_line(base_text_2);
11461 let text_3 = edit_first_char_of_every_line(base_text_3);
11462
11463 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11464 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11465 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11466
11467 let multibuffer = cx.new_model(|cx| {
11468 let mut multibuffer = MultiBuffer::new(ReadWrite);
11469 multibuffer.push_excerpts(
11470 buffer_1.clone(),
11471 [
11472 ExcerptRange {
11473 context: Point::new(0, 0)..Point::new(3, 0),
11474 primary: None,
11475 },
11476 ExcerptRange {
11477 context: Point::new(5, 0)..Point::new(7, 0),
11478 primary: None,
11479 },
11480 ExcerptRange {
11481 context: Point::new(9, 0)..Point::new(10, 4),
11482 primary: None,
11483 },
11484 ],
11485 cx,
11486 );
11487 multibuffer.push_excerpts(
11488 buffer_2.clone(),
11489 [
11490 ExcerptRange {
11491 context: Point::new(0, 0)..Point::new(3, 0),
11492 primary: None,
11493 },
11494 ExcerptRange {
11495 context: Point::new(5, 0)..Point::new(7, 0),
11496 primary: None,
11497 },
11498 ExcerptRange {
11499 context: Point::new(9, 0)..Point::new(10, 4),
11500 primary: None,
11501 },
11502 ],
11503 cx,
11504 );
11505 multibuffer.push_excerpts(
11506 buffer_3.clone(),
11507 [
11508 ExcerptRange {
11509 context: Point::new(0, 0)..Point::new(3, 0),
11510 primary: None,
11511 },
11512 ExcerptRange {
11513 context: Point::new(5, 0)..Point::new(7, 0),
11514 primary: None,
11515 },
11516 ExcerptRange {
11517 context: Point::new(9, 0)..Point::new(10, 4),
11518 primary: None,
11519 },
11520 ],
11521 cx,
11522 );
11523 multibuffer
11524 });
11525
11526 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11527 editor.update(cx, |editor, cx| {
11528 for (buffer, diff_base) in [
11529 (buffer_1.clone(), base_text_1),
11530 (buffer_2.clone(), base_text_2),
11531 (buffer_3.clone(), base_text_3),
11532 ] {
11533 let change_set = cx.new_model(|cx| {
11534 BufferChangeSet::new_with_base_text(
11535 diff_base.to_string(),
11536 buffer.read(cx).text_snapshot(),
11537 cx,
11538 )
11539 });
11540 editor.diff_map.add_change_set(change_set, cx)
11541 }
11542 });
11543 cx.executor().run_until_parked();
11544
11545 editor.update(cx, |editor, cx| {
11546 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}");
11547 editor.select_all(&SelectAll, cx);
11548 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11549 });
11550 cx.executor().run_until_parked();
11551
11552 // When all ranges are selected, all buffer hunks are reverted.
11553 editor.update(cx, |editor, cx| {
11554 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");
11555 });
11556 buffer_1.update(cx, |buffer, _| {
11557 assert_eq!(buffer.text(), base_text_1);
11558 });
11559 buffer_2.update(cx, |buffer, _| {
11560 assert_eq!(buffer.text(), base_text_2);
11561 });
11562 buffer_3.update(cx, |buffer, _| {
11563 assert_eq!(buffer.text(), base_text_3);
11564 });
11565
11566 editor.update(cx, |editor, cx| {
11567 editor.undo(&Default::default(), cx);
11568 });
11569
11570 editor.update(cx, |editor, cx| {
11571 editor.change_selections(None, cx, |s| {
11572 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11573 });
11574 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11575 });
11576
11577 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11578 // but not affect buffer_2 and its related excerpts.
11579 editor.update(cx, |editor, cx| {
11580 assert_eq!(
11581 editor.text(cx),
11582 "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}"
11583 );
11584 });
11585 buffer_1.update(cx, |buffer, _| {
11586 assert_eq!(buffer.text(), base_text_1);
11587 });
11588 buffer_2.update(cx, |buffer, _| {
11589 assert_eq!(
11590 buffer.text(),
11591 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11592 );
11593 });
11594 buffer_3.update(cx, |buffer, _| {
11595 assert_eq!(
11596 buffer.text(),
11597 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11598 );
11599 });
11600
11601 fn edit_first_char_of_every_line(text: &str) -> String {
11602 text.split('\n')
11603 .map(|line| format!("X{}", &line[1..]))
11604 .collect::<Vec<_>>()
11605 .join("\n")
11606 }
11607}
11608
11609#[gpui::test]
11610async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11611 init_test(cx, |_| {});
11612
11613 let cols = 4;
11614 let rows = 10;
11615 let sample_text_1 = sample_text(rows, cols, 'a');
11616 assert_eq!(
11617 sample_text_1,
11618 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11619 );
11620 let sample_text_2 = sample_text(rows, cols, 'l');
11621 assert_eq!(
11622 sample_text_2,
11623 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11624 );
11625 let sample_text_3 = sample_text(rows, cols, 'v');
11626 assert_eq!(
11627 sample_text_3,
11628 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11629 );
11630
11631 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11632 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11633 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11634
11635 let multi_buffer = cx.new_model(|cx| {
11636 let mut multibuffer = MultiBuffer::new(ReadWrite);
11637 multibuffer.push_excerpts(
11638 buffer_1.clone(),
11639 [
11640 ExcerptRange {
11641 context: Point::new(0, 0)..Point::new(3, 0),
11642 primary: None,
11643 },
11644 ExcerptRange {
11645 context: Point::new(5, 0)..Point::new(7, 0),
11646 primary: None,
11647 },
11648 ExcerptRange {
11649 context: Point::new(9, 0)..Point::new(10, 4),
11650 primary: None,
11651 },
11652 ],
11653 cx,
11654 );
11655 multibuffer.push_excerpts(
11656 buffer_2.clone(),
11657 [
11658 ExcerptRange {
11659 context: Point::new(0, 0)..Point::new(3, 0),
11660 primary: None,
11661 },
11662 ExcerptRange {
11663 context: Point::new(5, 0)..Point::new(7, 0),
11664 primary: None,
11665 },
11666 ExcerptRange {
11667 context: Point::new(9, 0)..Point::new(10, 4),
11668 primary: None,
11669 },
11670 ],
11671 cx,
11672 );
11673 multibuffer.push_excerpts(
11674 buffer_3.clone(),
11675 [
11676 ExcerptRange {
11677 context: Point::new(0, 0)..Point::new(3, 0),
11678 primary: None,
11679 },
11680 ExcerptRange {
11681 context: Point::new(5, 0)..Point::new(7, 0),
11682 primary: None,
11683 },
11684 ExcerptRange {
11685 context: Point::new(9, 0)..Point::new(10, 4),
11686 primary: None,
11687 },
11688 ],
11689 cx,
11690 );
11691 multibuffer
11692 });
11693
11694 let fs = FakeFs::new(cx.executor());
11695 fs.insert_tree(
11696 "/a",
11697 json!({
11698 "main.rs": sample_text_1,
11699 "other.rs": sample_text_2,
11700 "lib.rs": sample_text_3,
11701 }),
11702 )
11703 .await;
11704 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11705 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11706 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11707 let multi_buffer_editor = cx.new_view(|cx| {
11708 Editor::new(
11709 EditorMode::Full,
11710 multi_buffer,
11711 Some(project.clone()),
11712 true,
11713 cx,
11714 )
11715 });
11716 let multibuffer_item_id = workspace
11717 .update(cx, |workspace, cx| {
11718 assert!(
11719 workspace.active_item(cx).is_none(),
11720 "active item should be None before the first item is added"
11721 );
11722 workspace.add_item_to_active_pane(
11723 Box::new(multi_buffer_editor.clone()),
11724 None,
11725 true,
11726 cx,
11727 );
11728 let active_item = workspace
11729 .active_item(cx)
11730 .expect("should have an active item after adding the multi buffer");
11731 assert!(
11732 !active_item.is_singleton(cx),
11733 "A multi buffer was expected to active after adding"
11734 );
11735 active_item.item_id()
11736 })
11737 .unwrap();
11738 cx.executor().run_until_parked();
11739
11740 multi_buffer_editor.update(cx, |editor, cx| {
11741 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11742 editor.open_excerpts(&OpenExcerpts, cx);
11743 });
11744 cx.executor().run_until_parked();
11745 let first_item_id = workspace
11746 .update(cx, |workspace, cx| {
11747 let active_item = workspace
11748 .active_item(cx)
11749 .expect("should have an active item after navigating into the 1st buffer");
11750 let first_item_id = active_item.item_id();
11751 assert_ne!(
11752 first_item_id, multibuffer_item_id,
11753 "Should navigate into the 1st buffer and activate it"
11754 );
11755 assert!(
11756 active_item.is_singleton(cx),
11757 "New active item should be a singleton buffer"
11758 );
11759 assert_eq!(
11760 active_item
11761 .act_as::<Editor>(cx)
11762 .expect("should have navigated into an editor for the 1st buffer")
11763 .read(cx)
11764 .text(cx),
11765 sample_text_1
11766 );
11767
11768 workspace
11769 .go_back(workspace.active_pane().downgrade(), cx)
11770 .detach_and_log_err(cx);
11771
11772 first_item_id
11773 })
11774 .unwrap();
11775 cx.executor().run_until_parked();
11776 workspace
11777 .update(cx, |workspace, cx| {
11778 let active_item = workspace
11779 .active_item(cx)
11780 .expect("should have an active item after navigating back");
11781 assert_eq!(
11782 active_item.item_id(),
11783 multibuffer_item_id,
11784 "Should navigate back to the multi buffer"
11785 );
11786 assert!(!active_item.is_singleton(cx));
11787 })
11788 .unwrap();
11789
11790 multi_buffer_editor.update(cx, |editor, cx| {
11791 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11792 s.select_ranges(Some(39..40))
11793 });
11794 editor.open_excerpts(&OpenExcerpts, cx);
11795 });
11796 cx.executor().run_until_parked();
11797 let second_item_id = workspace
11798 .update(cx, |workspace, cx| {
11799 let active_item = workspace
11800 .active_item(cx)
11801 .expect("should have an active item after navigating into the 2nd buffer");
11802 let second_item_id = active_item.item_id();
11803 assert_ne!(
11804 second_item_id, multibuffer_item_id,
11805 "Should navigate away from the multibuffer"
11806 );
11807 assert_ne!(
11808 second_item_id, first_item_id,
11809 "Should navigate into the 2nd buffer and activate it"
11810 );
11811 assert!(
11812 active_item.is_singleton(cx),
11813 "New active item should be a singleton buffer"
11814 );
11815 assert_eq!(
11816 active_item
11817 .act_as::<Editor>(cx)
11818 .expect("should have navigated into an editor")
11819 .read(cx)
11820 .text(cx),
11821 sample_text_2
11822 );
11823
11824 workspace
11825 .go_back(workspace.active_pane().downgrade(), cx)
11826 .detach_and_log_err(cx);
11827
11828 second_item_id
11829 })
11830 .unwrap();
11831 cx.executor().run_until_parked();
11832 workspace
11833 .update(cx, |workspace, cx| {
11834 let active_item = workspace
11835 .active_item(cx)
11836 .expect("should have an active item after navigating back from the 2nd buffer");
11837 assert_eq!(
11838 active_item.item_id(),
11839 multibuffer_item_id,
11840 "Should navigate back from the 2nd buffer to the multi buffer"
11841 );
11842 assert!(!active_item.is_singleton(cx));
11843 })
11844 .unwrap();
11845
11846 multi_buffer_editor.update(cx, |editor, cx| {
11847 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11848 s.select_ranges(Some(70..70))
11849 });
11850 editor.open_excerpts(&OpenExcerpts, cx);
11851 });
11852 cx.executor().run_until_parked();
11853 workspace
11854 .update(cx, |workspace, cx| {
11855 let active_item = workspace
11856 .active_item(cx)
11857 .expect("should have an active item after navigating into the 3rd buffer");
11858 let third_item_id = active_item.item_id();
11859 assert_ne!(
11860 third_item_id, multibuffer_item_id,
11861 "Should navigate into the 3rd buffer and activate it"
11862 );
11863 assert_ne!(third_item_id, first_item_id);
11864 assert_ne!(third_item_id, second_item_id);
11865 assert!(
11866 active_item.is_singleton(cx),
11867 "New active item should be a singleton buffer"
11868 );
11869 assert_eq!(
11870 active_item
11871 .act_as::<Editor>(cx)
11872 .expect("should have navigated into an editor")
11873 .read(cx)
11874 .text(cx),
11875 sample_text_3
11876 );
11877
11878 workspace
11879 .go_back(workspace.active_pane().downgrade(), cx)
11880 .detach_and_log_err(cx);
11881 })
11882 .unwrap();
11883 cx.executor().run_until_parked();
11884 workspace
11885 .update(cx, |workspace, cx| {
11886 let active_item = workspace
11887 .active_item(cx)
11888 .expect("should have an active item after navigating back from the 3rd buffer");
11889 assert_eq!(
11890 active_item.item_id(),
11891 multibuffer_item_id,
11892 "Should navigate back from the 3rd buffer to the multi buffer"
11893 );
11894 assert!(!active_item.is_singleton(cx));
11895 })
11896 .unwrap();
11897}
11898
11899#[gpui::test]
11900async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11901 init_test(cx, |_| {});
11902
11903 let mut cx = EditorTestContext::new(cx).await;
11904
11905 let diff_base = r#"
11906 use some::mod;
11907
11908 const A: u32 = 42;
11909
11910 fn main() {
11911 println!("hello");
11912
11913 println!("world");
11914 }
11915 "#
11916 .unindent();
11917
11918 cx.set_state(
11919 &r#"
11920 use some::modified;
11921
11922 ˇ
11923 fn main() {
11924 println!("hello there");
11925
11926 println!("around the");
11927 println!("world");
11928 }
11929 "#
11930 .unindent(),
11931 );
11932
11933 cx.set_diff_base(&diff_base);
11934 executor.run_until_parked();
11935
11936 cx.update_editor(|editor, cx| {
11937 editor.go_to_next_hunk(&GoToHunk, cx);
11938 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11939 });
11940 executor.run_until_parked();
11941 cx.assert_state_with_diff(
11942 r#"
11943 use some::modified;
11944
11945
11946 fn main() {
11947 - println!("hello");
11948 + ˇ println!("hello there");
11949
11950 println!("around the");
11951 println!("world");
11952 }
11953 "#
11954 .unindent(),
11955 );
11956
11957 cx.update_editor(|editor, cx| {
11958 for _ in 0..3 {
11959 editor.go_to_next_hunk(&GoToHunk, cx);
11960 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11961 }
11962 });
11963 executor.run_until_parked();
11964 cx.assert_state_with_diff(
11965 r#"
11966 - use some::mod;
11967 + use some::modified;
11968
11969 - const A: u32 = 42;
11970 ˇ
11971 fn main() {
11972 - println!("hello");
11973 + println!("hello there");
11974
11975 + println!("around the");
11976 println!("world");
11977 }
11978 "#
11979 .unindent(),
11980 );
11981
11982 cx.update_editor(|editor, cx| {
11983 editor.cancel(&Cancel, cx);
11984 });
11985
11986 cx.assert_state_with_diff(
11987 r#"
11988 use some::modified;
11989
11990 ˇ
11991 fn main() {
11992 println!("hello there");
11993
11994 println!("around the");
11995 println!("world");
11996 }
11997 "#
11998 .unindent(),
11999 );
12000}
12001
12002#[gpui::test]
12003async fn test_diff_base_change_with_expanded_diff_hunks(
12004 executor: BackgroundExecutor,
12005 cx: &mut gpui::TestAppContext,
12006) {
12007 init_test(cx, |_| {});
12008
12009 let mut cx = EditorTestContext::new(cx).await;
12010
12011 let diff_base = r#"
12012 use some::mod1;
12013 use some::mod2;
12014
12015 const A: u32 = 42;
12016 const B: u32 = 42;
12017 const C: u32 = 42;
12018
12019 fn main() {
12020 println!("hello");
12021
12022 println!("world");
12023 }
12024 "#
12025 .unindent();
12026
12027 cx.set_state(
12028 &r#"
12029 use some::mod2;
12030
12031 const A: u32 = 42;
12032 const C: u32 = 42;
12033
12034 fn main(ˇ) {
12035 //println!("hello");
12036
12037 println!("world");
12038 //
12039 //
12040 }
12041 "#
12042 .unindent(),
12043 );
12044
12045 cx.set_diff_base(&diff_base);
12046 executor.run_until_parked();
12047
12048 cx.update_editor(|editor, cx| {
12049 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12050 });
12051 executor.run_until_parked();
12052 cx.assert_state_with_diff(
12053 r#"
12054 - use some::mod1;
12055 use some::mod2;
12056
12057 const A: u32 = 42;
12058 - const B: u32 = 42;
12059 const C: u32 = 42;
12060
12061 fn main(ˇ) {
12062 - println!("hello");
12063 + //println!("hello");
12064
12065 println!("world");
12066 + //
12067 + //
12068 }
12069 "#
12070 .unindent(),
12071 );
12072
12073 cx.set_diff_base("new diff base!");
12074 executor.run_until_parked();
12075 cx.assert_state_with_diff(
12076 r#"
12077 use some::mod2;
12078
12079 const A: u32 = 42;
12080 const C: u32 = 42;
12081
12082 fn main(ˇ) {
12083 //println!("hello");
12084
12085 println!("world");
12086 //
12087 //
12088 }
12089 "#
12090 .unindent(),
12091 );
12092
12093 cx.update_editor(|editor, cx| {
12094 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12095 });
12096 executor.run_until_parked();
12097 cx.assert_state_with_diff(
12098 r#"
12099 - new diff base!
12100 + use some::mod2;
12101 +
12102 + const A: u32 = 42;
12103 + const C: u32 = 42;
12104 +
12105 + fn main(ˇ) {
12106 + //println!("hello");
12107 +
12108 + println!("world");
12109 + //
12110 + //
12111 + }
12112 "#
12113 .unindent(),
12114 );
12115}
12116
12117#[gpui::test]
12118async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12119 init_test(cx, |_| {});
12120
12121 let mut cx = EditorTestContext::new(cx).await;
12122
12123 let diff_base = r#"
12124 use some::mod1;
12125 use some::mod2;
12126
12127 const A: u32 = 42;
12128 const B: u32 = 42;
12129 const C: u32 = 42;
12130
12131 fn main() {
12132 println!("hello");
12133
12134 println!("world");
12135 }
12136
12137 fn another() {
12138 println!("another");
12139 }
12140
12141 fn another2() {
12142 println!("another2");
12143 }
12144 "#
12145 .unindent();
12146
12147 cx.set_state(
12148 &r#"
12149 «use some::mod2;
12150
12151 const A: u32 = 42;
12152 const C: u32 = 42;
12153
12154 fn main() {
12155 //println!("hello");
12156
12157 println!("world");
12158 //
12159 //ˇ»
12160 }
12161
12162 fn another() {
12163 println!("another");
12164 println!("another");
12165 }
12166
12167 println!("another2");
12168 }
12169 "#
12170 .unindent(),
12171 );
12172
12173 cx.set_diff_base(&diff_base);
12174 executor.run_until_parked();
12175
12176 cx.update_editor(|editor, cx| {
12177 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12178 });
12179 executor.run_until_parked();
12180
12181 cx.assert_state_with_diff(
12182 r#"
12183 - use some::mod1;
12184 «use some::mod2;
12185
12186 const A: u32 = 42;
12187 - const B: u32 = 42;
12188 const C: u32 = 42;
12189
12190 fn main() {
12191 - println!("hello");
12192 + //println!("hello");
12193
12194 println!("world");
12195 + //
12196 + //ˇ»
12197 }
12198
12199 fn another() {
12200 println!("another");
12201 + println!("another");
12202 }
12203
12204 - fn another2() {
12205 println!("another2");
12206 }
12207 "#
12208 .unindent(),
12209 );
12210
12211 // Fold across some of the diff hunks. They should no longer appear expanded.
12212 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12213 cx.executor().run_until_parked();
12214
12215 // Hunks are not shown if their position is within a fold
12216 cx.assert_state_with_diff(
12217 r#"
12218 «use some::mod2;
12219
12220 const A: u32 = 42;
12221 const C: u32 = 42;
12222
12223 fn main() {
12224 //println!("hello");
12225
12226 println!("world");
12227 //
12228 //ˇ»
12229 }
12230
12231 fn another() {
12232 println!("another");
12233 + println!("another");
12234 }
12235
12236 - fn another2() {
12237 println!("another2");
12238 }
12239 "#
12240 .unindent(),
12241 );
12242
12243 cx.update_editor(|editor, cx| {
12244 editor.select_all(&SelectAll, cx);
12245 editor.unfold_lines(&UnfoldLines, cx);
12246 });
12247 cx.executor().run_until_parked();
12248
12249 // The deletions reappear when unfolding.
12250 cx.assert_state_with_diff(
12251 r#"
12252 - use some::mod1;
12253 «use some::mod2;
12254
12255 const A: u32 = 42;
12256 - const B: u32 = 42;
12257 const C: u32 = 42;
12258
12259 fn main() {
12260 - println!("hello");
12261 + //println!("hello");
12262
12263 println!("world");
12264 + //
12265 + //
12266 }
12267
12268 fn another() {
12269 println!("another");
12270 + println!("another");
12271 }
12272
12273 - fn another2() {
12274 println!("another2");
12275 }
12276 ˇ»"#
12277 .unindent(),
12278 );
12279}
12280
12281#[gpui::test]
12282async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12283 init_test(cx, |_| {});
12284
12285 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12286 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12287 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12288 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12289 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12290 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12291
12292 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12293 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12294 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12295
12296 let multi_buffer = cx.new_model(|cx| {
12297 let mut multibuffer = MultiBuffer::new(ReadWrite);
12298 multibuffer.push_excerpts(
12299 buffer_1.clone(),
12300 [
12301 ExcerptRange {
12302 context: Point::new(0, 0)..Point::new(3, 0),
12303 primary: None,
12304 },
12305 ExcerptRange {
12306 context: Point::new(5, 0)..Point::new(7, 0),
12307 primary: None,
12308 },
12309 ExcerptRange {
12310 context: Point::new(9, 0)..Point::new(10, 3),
12311 primary: None,
12312 },
12313 ],
12314 cx,
12315 );
12316 multibuffer.push_excerpts(
12317 buffer_2.clone(),
12318 [
12319 ExcerptRange {
12320 context: Point::new(0, 0)..Point::new(3, 0),
12321 primary: None,
12322 },
12323 ExcerptRange {
12324 context: Point::new(5, 0)..Point::new(7, 0),
12325 primary: None,
12326 },
12327 ExcerptRange {
12328 context: Point::new(9, 0)..Point::new(10, 3),
12329 primary: None,
12330 },
12331 ],
12332 cx,
12333 );
12334 multibuffer.push_excerpts(
12335 buffer_3.clone(),
12336 [
12337 ExcerptRange {
12338 context: Point::new(0, 0)..Point::new(3, 0),
12339 primary: None,
12340 },
12341 ExcerptRange {
12342 context: Point::new(5, 0)..Point::new(7, 0),
12343 primary: None,
12344 },
12345 ExcerptRange {
12346 context: Point::new(9, 0)..Point::new(10, 3),
12347 primary: None,
12348 },
12349 ],
12350 cx,
12351 );
12352 multibuffer
12353 });
12354
12355 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12356 editor
12357 .update(cx, |editor, cx| {
12358 for (buffer, diff_base) in [
12359 (buffer_1.clone(), file_1_old),
12360 (buffer_2.clone(), file_2_old),
12361 (buffer_3.clone(), file_3_old),
12362 ] {
12363 let change_set = cx.new_model(|cx| {
12364 BufferChangeSet::new_with_base_text(
12365 diff_base.to_string(),
12366 buffer.read(cx).text_snapshot(),
12367 cx,
12368 )
12369 });
12370 editor.diff_map.add_change_set(change_set, cx)
12371 }
12372 })
12373 .unwrap();
12374
12375 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12376 cx.run_until_parked();
12377
12378 cx.assert_editor_state(
12379 &"
12380 ˇaaa
12381 ccc
12382 ddd
12383
12384 ggg
12385 hhh
12386
12387
12388 lll
12389 mmm
12390 NNN
12391
12392 qqq
12393 rrr
12394
12395 uuu
12396 111
12397 222
12398 333
12399
12400 666
12401 777
12402
12403 000
12404 !!!"
12405 .unindent(),
12406 );
12407
12408 cx.update_editor(|editor, cx| {
12409 editor.select_all(&SelectAll, cx);
12410 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12411 });
12412 cx.executor().run_until_parked();
12413
12414 cx.assert_state_with_diff(
12415 "
12416 «aaa
12417 - bbb
12418 ccc
12419 ddd
12420
12421 ggg
12422 hhh
12423
12424
12425 lll
12426 mmm
12427 - nnn
12428 + NNN
12429
12430 qqq
12431 rrr
12432
12433 uuu
12434 111
12435 222
12436 333
12437
12438 + 666
12439 777
12440
12441 000
12442 !!!ˇ»"
12443 .unindent(),
12444 );
12445}
12446
12447#[gpui::test]
12448async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12449 init_test(cx, |_| {});
12450
12451 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12452 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12453
12454 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12455 let multi_buffer = cx.new_model(|cx| {
12456 let mut multibuffer = MultiBuffer::new(ReadWrite);
12457 multibuffer.push_excerpts(
12458 buffer.clone(),
12459 [
12460 ExcerptRange {
12461 context: Point::new(0, 0)..Point::new(2, 0),
12462 primary: None,
12463 },
12464 ExcerptRange {
12465 context: Point::new(5, 0)..Point::new(7, 0),
12466 primary: None,
12467 },
12468 ],
12469 cx,
12470 );
12471 multibuffer
12472 });
12473
12474 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12475 editor
12476 .update(cx, |editor, cx| {
12477 let buffer = buffer.read(cx).text_snapshot();
12478 let change_set = cx
12479 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12480 editor.diff_map.add_change_set(change_set, cx)
12481 })
12482 .unwrap();
12483
12484 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12485 cx.run_until_parked();
12486
12487 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12488 cx.executor().run_until_parked();
12489
12490 cx.assert_state_with_diff(
12491 "
12492 ˇaaa
12493 - bbb
12494 + BBB
12495
12496 - ddd
12497 - eee
12498 + EEE
12499 fff
12500 "
12501 .unindent(),
12502 );
12503}
12504
12505#[gpui::test]
12506async fn test_edits_around_expanded_insertion_hunks(
12507 executor: BackgroundExecutor,
12508 cx: &mut gpui::TestAppContext,
12509) {
12510 init_test(cx, |_| {});
12511
12512 let mut cx = EditorTestContext::new(cx).await;
12513
12514 let diff_base = r#"
12515 use some::mod1;
12516 use some::mod2;
12517
12518 const A: u32 = 42;
12519
12520 fn main() {
12521 println!("hello");
12522
12523 println!("world");
12524 }
12525 "#
12526 .unindent();
12527 executor.run_until_parked();
12528 cx.set_state(
12529 &r#"
12530 use some::mod1;
12531 use some::mod2;
12532
12533 const A: u32 = 42;
12534 const B: u32 = 42;
12535 const C: u32 = 42;
12536 ˇ
12537
12538 fn main() {
12539 println!("hello");
12540
12541 println!("world");
12542 }
12543 "#
12544 .unindent(),
12545 );
12546
12547 cx.set_diff_base(&diff_base);
12548 executor.run_until_parked();
12549
12550 cx.update_editor(|editor, cx| {
12551 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12552 });
12553 executor.run_until_parked();
12554
12555 cx.assert_state_with_diff(
12556 r#"
12557 use some::mod1;
12558 use some::mod2;
12559
12560 const A: u32 = 42;
12561 + const B: u32 = 42;
12562 + const C: u32 = 42;
12563 + ˇ
12564
12565 fn main() {
12566 println!("hello");
12567
12568 println!("world");
12569 }
12570 "#
12571 .unindent(),
12572 );
12573
12574 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12575 executor.run_until_parked();
12576
12577 cx.assert_state_with_diff(
12578 r#"
12579 use some::mod1;
12580 use some::mod2;
12581
12582 const A: u32 = 42;
12583 + const B: u32 = 42;
12584 + const C: u32 = 42;
12585 + const D: u32 = 42;
12586 + ˇ
12587
12588 fn main() {
12589 println!("hello");
12590
12591 println!("world");
12592 }
12593 "#
12594 .unindent(),
12595 );
12596
12597 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12598 executor.run_until_parked();
12599
12600 cx.assert_state_with_diff(
12601 r#"
12602 use some::mod1;
12603 use some::mod2;
12604
12605 const A: u32 = 42;
12606 + const B: u32 = 42;
12607 + const C: u32 = 42;
12608 + const D: u32 = 42;
12609 + const E: u32 = 42;
12610 + ˇ
12611
12612 fn main() {
12613 println!("hello");
12614
12615 println!("world");
12616 }
12617 "#
12618 .unindent(),
12619 );
12620
12621 cx.update_editor(|editor, cx| {
12622 editor.delete_line(&DeleteLine, cx);
12623 });
12624 executor.run_until_parked();
12625
12626 cx.assert_state_with_diff(
12627 r#"
12628 use some::mod1;
12629 use some::mod2;
12630
12631 const A: u32 = 42;
12632 + const B: u32 = 42;
12633 + const C: u32 = 42;
12634 + const D: u32 = 42;
12635 + const E: u32 = 42;
12636 ˇ
12637 fn main() {
12638 println!("hello");
12639
12640 println!("world");
12641 }
12642 "#
12643 .unindent(),
12644 );
12645
12646 cx.update_editor(|editor, cx| {
12647 editor.move_up(&MoveUp, cx);
12648 editor.delete_line(&DeleteLine, cx);
12649 editor.move_up(&MoveUp, cx);
12650 editor.delete_line(&DeleteLine, cx);
12651 editor.move_up(&MoveUp, cx);
12652 editor.delete_line(&DeleteLine, cx);
12653 });
12654 executor.run_until_parked();
12655 cx.assert_state_with_diff(
12656 r#"
12657 use some::mod1;
12658 use some::mod2;
12659
12660 const A: u32 = 42;
12661 + const B: u32 = 42;
12662 ˇ
12663 fn main() {
12664 println!("hello");
12665
12666 println!("world");
12667 }
12668 "#
12669 .unindent(),
12670 );
12671
12672 cx.update_editor(|editor, cx| {
12673 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12674 editor.delete_line(&DeleteLine, cx);
12675 });
12676 executor.run_until_parked();
12677 cx.assert_state_with_diff(
12678 r#"
12679 use some::mod1;
12680 - use some::mod2;
12681 -
12682 - const A: u32 = 42;
12683 ˇ
12684 fn main() {
12685 println!("hello");
12686
12687 println!("world");
12688 }
12689 "#
12690 .unindent(),
12691 );
12692}
12693
12694#[gpui::test]
12695async fn test_edits_around_expanded_deletion_hunks(
12696 executor: BackgroundExecutor,
12697 cx: &mut gpui::TestAppContext,
12698) {
12699 init_test(cx, |_| {});
12700
12701 let mut cx = EditorTestContext::new(cx).await;
12702
12703 let diff_base = r#"
12704 use some::mod1;
12705 use some::mod2;
12706
12707 const A: u32 = 42;
12708 const B: u32 = 42;
12709 const C: u32 = 42;
12710
12711
12712 fn main() {
12713 println!("hello");
12714
12715 println!("world");
12716 }
12717 "#
12718 .unindent();
12719 executor.run_until_parked();
12720 cx.set_state(
12721 &r#"
12722 use some::mod1;
12723 use some::mod2;
12724
12725 ˇconst B: u32 = 42;
12726 const C: u32 = 42;
12727
12728
12729 fn main() {
12730 println!("hello");
12731
12732 println!("world");
12733 }
12734 "#
12735 .unindent(),
12736 );
12737
12738 cx.set_diff_base(&diff_base);
12739 executor.run_until_parked();
12740
12741 cx.update_editor(|editor, cx| {
12742 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12743 });
12744 executor.run_until_parked();
12745
12746 cx.assert_state_with_diff(
12747 r#"
12748 use some::mod1;
12749 use some::mod2;
12750
12751 - const A: u32 = 42;
12752 ˇconst B: u32 = 42;
12753 const C: u32 = 42;
12754
12755
12756 fn main() {
12757 println!("hello");
12758
12759 println!("world");
12760 }
12761 "#
12762 .unindent(),
12763 );
12764
12765 cx.update_editor(|editor, cx| {
12766 editor.delete_line(&DeleteLine, cx);
12767 });
12768 executor.run_until_parked();
12769 cx.assert_state_with_diff(
12770 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 );
12787
12788 cx.update_editor(|editor, cx| {
12789 editor.delete_line(&DeleteLine, cx);
12790 });
12791 executor.run_until_parked();
12792 cx.assert_state_with_diff(
12793 r#"
12794 use some::mod1;
12795 use some::mod2;
12796
12797 - const A: u32 = 42;
12798 - const B: u32 = 42;
12799 - const C: u32 = 42;
12800 ˇ
12801
12802 fn main() {
12803 println!("hello");
12804
12805 println!("world");
12806 }
12807 "#
12808 .unindent(),
12809 );
12810
12811 cx.update_editor(|editor, cx| {
12812 editor.handle_input("replacement", cx);
12813 });
12814 executor.run_until_parked();
12815 cx.assert_state_with_diff(
12816 r#"
12817 use some::mod1;
12818 use some::mod2;
12819
12820 - const A: u32 = 42;
12821 - const B: u32 = 42;
12822 - const C: u32 = 42;
12823 -
12824 + replacementˇ
12825
12826 fn main() {
12827 println!("hello");
12828
12829 println!("world");
12830 }
12831 "#
12832 .unindent(),
12833 );
12834}
12835
12836#[gpui::test]
12837async fn test_edit_after_expanded_modification_hunk(
12838 executor: BackgroundExecutor,
12839 cx: &mut gpui::TestAppContext,
12840) {
12841 init_test(cx, |_| {});
12842
12843 let mut cx = EditorTestContext::new(cx).await;
12844
12845 let diff_base = r#"
12846 use some::mod1;
12847 use some::mod2;
12848
12849 const A: u32 = 42;
12850 const B: u32 = 42;
12851 const C: u32 = 42;
12852 const D: u32 = 42;
12853
12854
12855 fn main() {
12856 println!("hello");
12857
12858 println!("world");
12859 }"#
12860 .unindent();
12861
12862 cx.set_state(
12863 &r#"
12864 use some::mod1;
12865 use some::mod2;
12866
12867 const A: u32 = 42;
12868 const B: u32 = 42;
12869 const C: u32 = 43ˇ
12870 const D: u32 = 42;
12871
12872
12873 fn main() {
12874 println!("hello");
12875
12876 println!("world");
12877 }"#
12878 .unindent(),
12879 );
12880
12881 cx.set_diff_base(&diff_base);
12882 executor.run_until_parked();
12883 cx.update_editor(|editor, cx| {
12884 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12885 });
12886 executor.run_until_parked();
12887
12888 cx.assert_state_with_diff(
12889 r#"
12890 use some::mod1;
12891 use some::mod2;
12892
12893 const A: u32 = 42;
12894 const B: u32 = 42;
12895 - const C: u32 = 42;
12896 + const C: u32 = 43ˇ
12897 const D: u32 = 42;
12898
12899
12900 fn main() {
12901 println!("hello");
12902
12903 println!("world");
12904 }"#
12905 .unindent(),
12906 );
12907
12908 cx.update_editor(|editor, cx| {
12909 editor.handle_input("\nnew_line\n", cx);
12910 });
12911 executor.run_until_parked();
12912
12913 cx.assert_state_with_diff(
12914 r#"
12915 use some::mod1;
12916 use some::mod2;
12917
12918 const A: u32 = 42;
12919 const B: u32 = 42;
12920 - const C: u32 = 42;
12921 + const C: u32 = 43
12922 + new_line
12923 + ˇ
12924 const D: u32 = 42;
12925
12926
12927 fn main() {
12928 println!("hello");
12929
12930 println!("world");
12931 }"#
12932 .unindent(),
12933 );
12934}
12935
12936async fn setup_indent_guides_editor(
12937 text: &str,
12938 cx: &mut gpui::TestAppContext,
12939) -> (BufferId, EditorTestContext) {
12940 init_test(cx, |_| {});
12941
12942 let mut cx = EditorTestContext::new(cx).await;
12943
12944 let buffer_id = cx.update_editor(|editor, cx| {
12945 editor.set_text(text, cx);
12946 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12947
12948 buffer_ids[0]
12949 });
12950
12951 (buffer_id, cx)
12952}
12953
12954fn assert_indent_guides(
12955 range: Range<u32>,
12956 expected: Vec<IndentGuide>,
12957 active_indices: Option<Vec<usize>>,
12958 cx: &mut EditorTestContext,
12959) {
12960 let indent_guides = cx.update_editor(|editor, cx| {
12961 let snapshot = editor.snapshot(cx).display_snapshot;
12962 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12963 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12964 true,
12965 &snapshot,
12966 cx,
12967 );
12968
12969 indent_guides.sort_by(|a, b| {
12970 a.depth.cmp(&b.depth).then(
12971 a.start_row
12972 .cmp(&b.start_row)
12973 .then(a.end_row.cmp(&b.end_row)),
12974 )
12975 });
12976 indent_guides
12977 });
12978
12979 if let Some(expected) = active_indices {
12980 let active_indices = cx.update_editor(|editor, cx| {
12981 let snapshot = editor.snapshot(cx).display_snapshot;
12982 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12983 });
12984
12985 assert_eq!(
12986 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12987 expected,
12988 "Active indent guide indices do not match"
12989 );
12990 }
12991
12992 let expected: Vec<_> = expected
12993 .into_iter()
12994 .map(|guide| MultiBufferIndentGuide {
12995 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12996 buffer: guide,
12997 })
12998 .collect();
12999
13000 assert_eq!(indent_guides, expected, "Indent guides do not match");
13001}
13002
13003fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13004 IndentGuide {
13005 buffer_id,
13006 start_row,
13007 end_row,
13008 depth,
13009 tab_size: 4,
13010 settings: IndentGuideSettings {
13011 enabled: true,
13012 line_width: 1,
13013 active_line_width: 1,
13014 ..Default::default()
13015 },
13016 }
13017}
13018
13019#[gpui::test]
13020async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13021 let (buffer_id, mut cx) = setup_indent_guides_editor(
13022 &"
13023 fn main() {
13024 let a = 1;
13025 }"
13026 .unindent(),
13027 cx,
13028 )
13029 .await;
13030
13031 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13032}
13033
13034#[gpui::test]
13035async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13036 let (buffer_id, mut cx) = setup_indent_guides_editor(
13037 &"
13038 fn main() {
13039 let a = 1;
13040 let b = 2;
13041 }"
13042 .unindent(),
13043 cx,
13044 )
13045 .await;
13046
13047 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13048}
13049
13050#[gpui::test]
13051async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13052 let (buffer_id, mut cx) = setup_indent_guides_editor(
13053 &"
13054 fn main() {
13055 let a = 1;
13056 if a == 3 {
13057 let b = 2;
13058 } else {
13059 let c = 3;
13060 }
13061 }"
13062 .unindent(),
13063 cx,
13064 )
13065 .await;
13066
13067 assert_indent_guides(
13068 0..8,
13069 vec![
13070 indent_guide(buffer_id, 1, 6, 0),
13071 indent_guide(buffer_id, 3, 3, 1),
13072 indent_guide(buffer_id, 5, 5, 1),
13073 ],
13074 None,
13075 &mut cx,
13076 );
13077}
13078
13079#[gpui::test]
13080async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13081 let (buffer_id, mut cx) = setup_indent_guides_editor(
13082 &"
13083 fn main() {
13084 let a = 1;
13085 let b = 2;
13086 let c = 3;
13087 }"
13088 .unindent(),
13089 cx,
13090 )
13091 .await;
13092
13093 assert_indent_guides(
13094 0..5,
13095 vec![
13096 indent_guide(buffer_id, 1, 3, 0),
13097 indent_guide(buffer_id, 2, 2, 1),
13098 ],
13099 None,
13100 &mut cx,
13101 );
13102}
13103
13104#[gpui::test]
13105async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13106 let (buffer_id, mut cx) = setup_indent_guides_editor(
13107 &"
13108 fn main() {
13109 let a = 1;
13110
13111 let c = 3;
13112 }"
13113 .unindent(),
13114 cx,
13115 )
13116 .await;
13117
13118 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13119}
13120
13121#[gpui::test]
13122async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13123 let (buffer_id, mut cx) = setup_indent_guides_editor(
13124 &"
13125 fn main() {
13126 let a = 1;
13127
13128 let c = 3;
13129
13130 if a == 3 {
13131 let b = 2;
13132 } else {
13133 let c = 3;
13134 }
13135 }"
13136 .unindent(),
13137 cx,
13138 )
13139 .await;
13140
13141 assert_indent_guides(
13142 0..11,
13143 vec![
13144 indent_guide(buffer_id, 1, 9, 0),
13145 indent_guide(buffer_id, 6, 6, 1),
13146 indent_guide(buffer_id, 8, 8, 1),
13147 ],
13148 None,
13149 &mut cx,
13150 );
13151}
13152
13153#[gpui::test]
13154async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13155 let (buffer_id, mut cx) = setup_indent_guides_editor(
13156 &"
13157 fn main() {
13158 let a = 1;
13159
13160 let c = 3;
13161
13162 if a == 3 {
13163 let b = 2;
13164 } else {
13165 let c = 3;
13166 }
13167 }"
13168 .unindent(),
13169 cx,
13170 )
13171 .await;
13172
13173 assert_indent_guides(
13174 1..11,
13175 vec![
13176 indent_guide(buffer_id, 1, 9, 0),
13177 indent_guide(buffer_id, 6, 6, 1),
13178 indent_guide(buffer_id, 8, 8, 1),
13179 ],
13180 None,
13181 &mut cx,
13182 );
13183}
13184
13185#[gpui::test]
13186async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13187 let (buffer_id, mut cx) = setup_indent_guides_editor(
13188 &"
13189 fn main() {
13190 let a = 1;
13191
13192 let c = 3;
13193
13194 if a == 3 {
13195 let b = 2;
13196 } else {
13197 let c = 3;
13198 }
13199 }"
13200 .unindent(),
13201 cx,
13202 )
13203 .await;
13204
13205 assert_indent_guides(
13206 1..10,
13207 vec![
13208 indent_guide(buffer_id, 1, 9, 0),
13209 indent_guide(buffer_id, 6, 6, 1),
13210 indent_guide(buffer_id, 8, 8, 1),
13211 ],
13212 None,
13213 &mut cx,
13214 );
13215}
13216
13217#[gpui::test]
13218async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13219 let (buffer_id, mut cx) = setup_indent_guides_editor(
13220 &"
13221 block1
13222 block2
13223 block3
13224 block4
13225 block2
13226 block1
13227 block1"
13228 .unindent(),
13229 cx,
13230 )
13231 .await;
13232
13233 assert_indent_guides(
13234 1..10,
13235 vec![
13236 indent_guide(buffer_id, 1, 4, 0),
13237 indent_guide(buffer_id, 2, 3, 1),
13238 indent_guide(buffer_id, 3, 3, 2),
13239 ],
13240 None,
13241 &mut cx,
13242 );
13243}
13244
13245#[gpui::test]
13246async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13247 let (buffer_id, mut cx) = setup_indent_guides_editor(
13248 &"
13249 block1
13250 block2
13251 block3
13252
13253 block1
13254 block1"
13255 .unindent(),
13256 cx,
13257 )
13258 .await;
13259
13260 assert_indent_guides(
13261 0..6,
13262 vec![
13263 indent_guide(buffer_id, 1, 2, 0),
13264 indent_guide(buffer_id, 2, 2, 1),
13265 ],
13266 None,
13267 &mut cx,
13268 );
13269}
13270
13271#[gpui::test]
13272async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13273 let (buffer_id, mut cx) = setup_indent_guides_editor(
13274 &"
13275 block1
13276
13277
13278
13279 block2
13280 "
13281 .unindent(),
13282 cx,
13283 )
13284 .await;
13285
13286 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13287}
13288
13289#[gpui::test]
13290async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13291 let (buffer_id, mut cx) = setup_indent_guides_editor(
13292 &"
13293 def a:
13294 \tb = 3
13295 \tif True:
13296 \t\tc = 4
13297 \t\td = 5
13298 \tprint(b)
13299 "
13300 .unindent(),
13301 cx,
13302 )
13303 .await;
13304
13305 assert_indent_guides(
13306 0..6,
13307 vec![
13308 indent_guide(buffer_id, 1, 6, 0),
13309 indent_guide(buffer_id, 3, 4, 1),
13310 ],
13311 None,
13312 &mut cx,
13313 );
13314}
13315
13316#[gpui::test]
13317async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13318 let (buffer_id, mut cx) = setup_indent_guides_editor(
13319 &"
13320 fn main() {
13321 let a = 1;
13322 }"
13323 .unindent(),
13324 cx,
13325 )
13326 .await;
13327
13328 cx.update_editor(|editor, cx| {
13329 editor.change_selections(None, cx, |s| {
13330 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13331 });
13332 });
13333
13334 assert_indent_guides(
13335 0..3,
13336 vec![indent_guide(buffer_id, 1, 1, 0)],
13337 Some(vec![0]),
13338 &mut cx,
13339 );
13340}
13341
13342#[gpui::test]
13343async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13344 let (buffer_id, mut cx) = setup_indent_guides_editor(
13345 &"
13346 fn main() {
13347 if 1 == 2 {
13348 let a = 1;
13349 }
13350 }"
13351 .unindent(),
13352 cx,
13353 )
13354 .await;
13355
13356 cx.update_editor(|editor, cx| {
13357 editor.change_selections(None, cx, |s| {
13358 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13359 });
13360 });
13361
13362 assert_indent_guides(
13363 0..4,
13364 vec![
13365 indent_guide(buffer_id, 1, 3, 0),
13366 indent_guide(buffer_id, 2, 2, 1),
13367 ],
13368 Some(vec![1]),
13369 &mut cx,
13370 );
13371
13372 cx.update_editor(|editor, cx| {
13373 editor.change_selections(None, cx, |s| {
13374 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13375 });
13376 });
13377
13378 assert_indent_guides(
13379 0..4,
13380 vec![
13381 indent_guide(buffer_id, 1, 3, 0),
13382 indent_guide(buffer_id, 2, 2, 1),
13383 ],
13384 Some(vec![1]),
13385 &mut cx,
13386 );
13387
13388 cx.update_editor(|editor, cx| {
13389 editor.change_selections(None, cx, |s| {
13390 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13391 });
13392 });
13393
13394 assert_indent_guides(
13395 0..4,
13396 vec![
13397 indent_guide(buffer_id, 1, 3, 0),
13398 indent_guide(buffer_id, 2, 2, 1),
13399 ],
13400 Some(vec![0]),
13401 &mut cx,
13402 );
13403}
13404
13405#[gpui::test]
13406async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13407 let (buffer_id, mut cx) = setup_indent_guides_editor(
13408 &"
13409 fn main() {
13410 let a = 1;
13411
13412 let b = 2;
13413 }"
13414 .unindent(),
13415 cx,
13416 )
13417 .await;
13418
13419 cx.update_editor(|editor, cx| {
13420 editor.change_selections(None, cx, |s| {
13421 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13422 });
13423 });
13424
13425 assert_indent_guides(
13426 0..5,
13427 vec![indent_guide(buffer_id, 1, 3, 0)],
13428 Some(vec![0]),
13429 &mut cx,
13430 );
13431}
13432
13433#[gpui::test]
13434async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13435 let (buffer_id, mut cx) = setup_indent_guides_editor(
13436 &"
13437 def m:
13438 a = 1
13439 pass"
13440 .unindent(),
13441 cx,
13442 )
13443 .await;
13444
13445 cx.update_editor(|editor, cx| {
13446 editor.change_selections(None, cx, |s| {
13447 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13448 });
13449 });
13450
13451 assert_indent_guides(
13452 0..3,
13453 vec![indent_guide(buffer_id, 1, 2, 0)],
13454 Some(vec![0]),
13455 &mut cx,
13456 );
13457}
13458
13459#[gpui::test]
13460fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13461 init_test(cx, |_| {});
13462
13463 let editor = cx.add_window(|cx| {
13464 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13465 build_editor(buffer, cx)
13466 });
13467
13468 let render_args = Arc::new(Mutex::new(None));
13469 let snapshot = editor
13470 .update(cx, |editor, cx| {
13471 let snapshot = editor.buffer().read(cx).snapshot(cx);
13472 let range =
13473 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13474
13475 struct RenderArgs {
13476 row: MultiBufferRow,
13477 folded: bool,
13478 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13479 }
13480
13481 let crease = Crease::inline(
13482 range,
13483 FoldPlaceholder::test(),
13484 {
13485 let toggle_callback = render_args.clone();
13486 move |row, folded, callback, _cx| {
13487 *toggle_callback.lock() = Some(RenderArgs {
13488 row,
13489 folded,
13490 callback,
13491 });
13492 div()
13493 }
13494 },
13495 |_row, _folded, _cx| div(),
13496 );
13497
13498 editor.insert_creases(Some(crease), cx);
13499 let snapshot = editor.snapshot(cx);
13500 let _div =
13501 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13502 snapshot
13503 })
13504 .unwrap();
13505
13506 let render_args = render_args.lock().take().unwrap();
13507 assert_eq!(render_args.row, MultiBufferRow(1));
13508 assert!(!render_args.folded);
13509 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13510
13511 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13512 .unwrap();
13513 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13514 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13515
13516 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13517 .unwrap();
13518 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13519 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13520}
13521
13522#[gpui::test]
13523async fn test_input_text(cx: &mut gpui::TestAppContext) {
13524 init_test(cx, |_| {});
13525 let mut cx = EditorTestContext::new(cx).await;
13526
13527 cx.set_state(
13528 &r#"ˇone
13529 two
13530
13531 three
13532 fourˇ
13533 five
13534
13535 siˇx"#
13536 .unindent(),
13537 );
13538
13539 cx.dispatch_action(HandleInput(String::new()));
13540 cx.assert_editor_state(
13541 &r#"ˇone
13542 two
13543
13544 three
13545 fourˇ
13546 five
13547
13548 siˇx"#
13549 .unindent(),
13550 );
13551
13552 cx.dispatch_action(HandleInput("AAAA".to_string()));
13553 cx.assert_editor_state(
13554 &r#"AAAAˇone
13555 two
13556
13557 three
13558 fourAAAAˇ
13559 five
13560
13561 siAAAAˇx"#
13562 .unindent(),
13563 );
13564}
13565
13566#[gpui::test]
13567async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13568 init_test(cx, |_| {});
13569
13570 let mut cx = EditorTestContext::new(cx).await;
13571 cx.set_state(
13572 r#"let foo = 1;
13573let foo = 2;
13574let foo = 3;
13575let fooˇ = 4;
13576let foo = 5;
13577let foo = 6;
13578let foo = 7;
13579let foo = 8;
13580let foo = 9;
13581let foo = 10;
13582let foo = 11;
13583let foo = 12;
13584let foo = 13;
13585let foo = 14;
13586let foo = 15;"#,
13587 );
13588
13589 cx.update_editor(|e, cx| {
13590 assert_eq!(
13591 e.next_scroll_position,
13592 NextScrollCursorCenterTopBottom::Center,
13593 "Default next scroll direction is center",
13594 );
13595
13596 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13597 assert_eq!(
13598 e.next_scroll_position,
13599 NextScrollCursorCenterTopBottom::Top,
13600 "After center, next scroll direction should be top",
13601 );
13602
13603 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13604 assert_eq!(
13605 e.next_scroll_position,
13606 NextScrollCursorCenterTopBottom::Bottom,
13607 "After top, next scroll direction should be bottom",
13608 );
13609
13610 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13611 assert_eq!(
13612 e.next_scroll_position,
13613 NextScrollCursorCenterTopBottom::Center,
13614 "After bottom, scrolling should start over",
13615 );
13616
13617 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13618 assert_eq!(
13619 e.next_scroll_position,
13620 NextScrollCursorCenterTopBottom::Top,
13621 "Scrolling continues if retriggered fast enough"
13622 );
13623 });
13624
13625 cx.executor()
13626 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13627 cx.executor().run_until_parked();
13628 cx.update_editor(|e, _| {
13629 assert_eq!(
13630 e.next_scroll_position,
13631 NextScrollCursorCenterTopBottom::Center,
13632 "If scrolling is not triggered fast enough, it should reset"
13633 );
13634 });
13635}
13636
13637#[gpui::test]
13638async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13639 init_test(cx, |_| {});
13640 let mut cx = EditorLspTestContext::new_rust(
13641 lsp::ServerCapabilities {
13642 definition_provider: Some(lsp::OneOf::Left(true)),
13643 references_provider: Some(lsp::OneOf::Left(true)),
13644 ..lsp::ServerCapabilities::default()
13645 },
13646 cx,
13647 )
13648 .await;
13649
13650 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13651 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13652 move |params, _| async move {
13653 if empty_go_to_definition {
13654 Ok(None)
13655 } else {
13656 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13657 uri: params.text_document_position_params.text_document.uri,
13658 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13659 })))
13660 }
13661 },
13662 );
13663 let references =
13664 cx.lsp
13665 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13666 Ok(Some(vec![lsp::Location {
13667 uri: params.text_document_position.text_document.uri,
13668 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13669 }]))
13670 });
13671 (go_to_definition, references)
13672 };
13673
13674 cx.set_state(
13675 &r#"fn one() {
13676 let mut a = ˇtwo();
13677 }
13678
13679 fn two() {}"#
13680 .unindent(),
13681 );
13682 set_up_lsp_handlers(false, &mut cx);
13683 let navigated = cx
13684 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13685 .await
13686 .expect("Failed to navigate to definition");
13687 assert_eq!(
13688 navigated,
13689 Navigated::Yes,
13690 "Should have navigated to definition from the GetDefinition response"
13691 );
13692 cx.assert_editor_state(
13693 &r#"fn one() {
13694 let mut a = two();
13695 }
13696
13697 fn «twoˇ»() {}"#
13698 .unindent(),
13699 );
13700
13701 let editors = cx.update_workspace(|workspace, cx| {
13702 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13703 });
13704 cx.update_editor(|_, test_editor_cx| {
13705 assert_eq!(
13706 editors.len(),
13707 1,
13708 "Initially, only one, test, editor should be open in the workspace"
13709 );
13710 assert_eq!(
13711 test_editor_cx.view(),
13712 editors.last().expect("Asserted len is 1")
13713 );
13714 });
13715
13716 set_up_lsp_handlers(true, &mut cx);
13717 let navigated = cx
13718 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13719 .await
13720 .expect("Failed to navigate to lookup references");
13721 assert_eq!(
13722 navigated,
13723 Navigated::Yes,
13724 "Should have navigated to references as a fallback after empty GoToDefinition response"
13725 );
13726 // We should not change the selections in the existing file,
13727 // if opening another milti buffer with the references
13728 cx.assert_editor_state(
13729 &r#"fn one() {
13730 let mut a = two();
13731 }
13732
13733 fn «twoˇ»() {}"#
13734 .unindent(),
13735 );
13736 let editors = cx.update_workspace(|workspace, cx| {
13737 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13738 });
13739 cx.update_editor(|_, test_editor_cx| {
13740 assert_eq!(
13741 editors.len(),
13742 2,
13743 "After falling back to references search, we open a new editor with the results"
13744 );
13745 let references_fallback_text = editors
13746 .into_iter()
13747 .find(|new_editor| new_editor != test_editor_cx.view())
13748 .expect("Should have one non-test editor now")
13749 .read(test_editor_cx)
13750 .text(test_editor_cx);
13751 assert_eq!(
13752 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13753 "Should use the range from the references response and not the GoToDefinition one"
13754 );
13755 });
13756}
13757
13758#[gpui::test]
13759async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13760 init_test(cx, |_| {});
13761
13762 let language = Arc::new(Language::new(
13763 LanguageConfig::default(),
13764 Some(tree_sitter_rust::LANGUAGE.into()),
13765 ));
13766
13767 let text = r#"
13768 #[cfg(test)]
13769 mod tests() {
13770 #[test]
13771 fn runnable_1() {
13772 let a = 1;
13773 }
13774
13775 #[test]
13776 fn runnable_2() {
13777 let a = 1;
13778 let b = 2;
13779 }
13780 }
13781 "#
13782 .unindent();
13783
13784 let fs = FakeFs::new(cx.executor());
13785 fs.insert_file("/file.rs", Default::default()).await;
13786
13787 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13788 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13789 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13790 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13791 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13792
13793 let editor = cx.new_view(|cx| {
13794 Editor::new(
13795 EditorMode::Full,
13796 multi_buffer,
13797 Some(project.clone()),
13798 true,
13799 cx,
13800 )
13801 });
13802
13803 editor.update(cx, |editor, cx| {
13804 editor.tasks.insert(
13805 (buffer.read(cx).remote_id(), 3),
13806 RunnableTasks {
13807 templates: vec![],
13808 offset: MultiBufferOffset(43),
13809 column: 0,
13810 extra_variables: HashMap::default(),
13811 context_range: BufferOffset(43)..BufferOffset(85),
13812 },
13813 );
13814 editor.tasks.insert(
13815 (buffer.read(cx).remote_id(), 8),
13816 RunnableTasks {
13817 templates: vec![],
13818 offset: MultiBufferOffset(86),
13819 column: 0,
13820 extra_variables: HashMap::default(),
13821 context_range: BufferOffset(86)..BufferOffset(191),
13822 },
13823 );
13824
13825 // Test finding task when cursor is inside function body
13826 editor.change_selections(None, cx, |s| {
13827 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13828 });
13829 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13830 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13831
13832 // Test finding task when cursor is on function name
13833 editor.change_selections(None, cx, |s| {
13834 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13835 });
13836 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13837 assert_eq!(row, 8, "Should find task when cursor is on function name");
13838 });
13839}
13840
13841fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13842 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13843 point..point
13844}
13845
13846fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13847 let (text, ranges) = marked_text_ranges(marked_text, true);
13848 assert_eq!(view.text(cx), text);
13849 assert_eq!(
13850 view.selections.ranges(cx),
13851 ranges,
13852 "Assert selections are {}",
13853 marked_text
13854 );
13855}
13856
13857pub fn handle_signature_help_request(
13858 cx: &mut EditorLspTestContext,
13859 mocked_response: lsp::SignatureHelp,
13860) -> impl Future<Output = ()> {
13861 let mut request =
13862 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13863 let mocked_response = mocked_response.clone();
13864 async move { Ok(Some(mocked_response)) }
13865 });
13866
13867 async move {
13868 request.next().await;
13869 }
13870}
13871
13872/// Handle completion request passing a marked string specifying where the completion
13873/// should be triggered from using '|' character, what range should be replaced, and what completions
13874/// should be returned using '<' and '>' to delimit the range
13875pub fn handle_completion_request(
13876 cx: &mut EditorLspTestContext,
13877 marked_string: &str,
13878 completions: Vec<&'static str>,
13879 counter: Arc<AtomicUsize>,
13880) -> impl Future<Output = ()> {
13881 let complete_from_marker: TextRangeMarker = '|'.into();
13882 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13883 let (_, mut marked_ranges) = marked_text_ranges_by(
13884 marked_string,
13885 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13886 );
13887
13888 let complete_from_position =
13889 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13890 let replace_range =
13891 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13892
13893 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13894 let completions = completions.clone();
13895 counter.fetch_add(1, atomic::Ordering::Release);
13896 async move {
13897 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13898 assert_eq!(
13899 params.text_document_position.position,
13900 complete_from_position
13901 );
13902 Ok(Some(lsp::CompletionResponse::Array(
13903 completions
13904 .iter()
13905 .map(|completion_text| lsp::CompletionItem {
13906 label: completion_text.to_string(),
13907 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13908 range: replace_range,
13909 new_text: completion_text.to_string(),
13910 })),
13911 ..Default::default()
13912 })
13913 .collect(),
13914 )))
13915 }
13916 });
13917
13918 async move {
13919 request.next().await;
13920 }
13921}
13922
13923fn handle_resolve_completion_request(
13924 cx: &mut EditorLspTestContext,
13925 edits: Option<Vec<(&'static str, &'static str)>>,
13926) -> impl Future<Output = ()> {
13927 let edits = edits.map(|edits| {
13928 edits
13929 .iter()
13930 .map(|(marked_string, new_text)| {
13931 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13932 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13933 lsp::TextEdit::new(replace_range, new_text.to_string())
13934 })
13935 .collect::<Vec<_>>()
13936 });
13937
13938 let mut request =
13939 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13940 let edits = edits.clone();
13941 async move {
13942 Ok(lsp::CompletionItem {
13943 additional_text_edits: edits,
13944 ..Default::default()
13945 })
13946 }
13947 });
13948
13949 async move {
13950 request.next().await;
13951 }
13952}
13953
13954pub(crate) fn update_test_language_settings(
13955 cx: &mut TestAppContext,
13956 f: impl Fn(&mut AllLanguageSettingsContent),
13957) {
13958 cx.update(|cx| {
13959 SettingsStore::update_global(cx, |store, cx| {
13960 store.update_user_settings::<AllLanguageSettings>(cx, f);
13961 });
13962 });
13963}
13964
13965pub(crate) fn update_test_project_settings(
13966 cx: &mut TestAppContext,
13967 f: impl Fn(&mut ProjectSettings),
13968) {
13969 cx.update(|cx| {
13970 SettingsStore::update_global(cx, |store, cx| {
13971 store.update_user_settings::<ProjectSettings>(cx, f);
13972 });
13973 });
13974}
13975
13976pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13977 cx.update(|cx| {
13978 assets::Assets.load_test_fonts(cx);
13979 let store = SettingsStore::test(cx);
13980 cx.set_global(store);
13981 theme::init(theme::LoadThemes::JustBase, cx);
13982 release_channel::init(SemanticVersion::default(), cx);
13983 client::init_settings(cx);
13984 language::init(cx);
13985 Project::init_settings(cx);
13986 workspace::init_settings(cx);
13987 crate::init(cx);
13988 });
13989
13990 update_test_language_settings(cx, f);
13991}
13992
13993#[track_caller]
13994fn assert_hunk_revert(
13995 not_reverted_text_with_selections: &str,
13996 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13997 expected_reverted_text_with_selections: &str,
13998 base_text: &str,
13999 cx: &mut EditorLspTestContext,
14000) {
14001 cx.set_state(not_reverted_text_with_selections);
14002 cx.set_diff_base(base_text);
14003 cx.executor().run_until_parked();
14004
14005 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14006 let snapshot = editor.snapshot(cx);
14007 let reverted_hunk_statuses = snapshot
14008 .diff_map
14009 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14010 .map(|hunk| hunk_status(&hunk))
14011 .collect::<Vec<_>>();
14012
14013 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14014 reverted_hunk_statuses
14015 });
14016 cx.executor().run_until_parked();
14017 cx.assert_editor_state(expected_reverted_text_with_selections);
14018 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14019}