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::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use std::{
37 iter,
38 sync::atomic::{self, AtomicUsize},
39};
40use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
41use unindent::Unindent;
42use util::{
43 assert_set_eq,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new_model(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |cx| {
65 let view = cx.view().clone();
66 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
67 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
68 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
69 _ => {}
70 })
71 .detach();
72 Editor::for_buffer(buffer.clone(), None, cx)
73 }
74 });
75
76 let editor2 = cx.add_window({
77 let events = events.clone();
78 |cx| {
79 cx.subscribe(
80 &cx.view().clone(),
81 move |_, _, event: &EditorEvent, _| match event {
82 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
83 EditorEvent::BufferEdited => {
84 events.borrow_mut().push(("editor2", "buffer edited"))
85 }
86 _ => {}
87 },
88 )
89 .detach();
90 Editor::for_buffer(buffer.clone(), None, cx)
91 }
92 });
93
94 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
95
96 // Mutating editor 1 will emit an `Edited` event only for that editor.
97 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
98 assert_eq!(
99 mem::take(&mut *events.borrow_mut()),
100 [
101 ("editor1", "edited"),
102 ("editor1", "buffer edited"),
103 ("editor2", "buffer edited"),
104 ]
105 );
106
107 // Mutating editor 2 will emit an `Edited` event only for that editor.
108 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
109 assert_eq!(
110 mem::take(&mut *events.borrow_mut()),
111 [
112 ("editor2", "edited"),
113 ("editor1", "buffer edited"),
114 ("editor2", "buffer edited"),
115 ]
116 );
117
118 // Undoing on editor 1 will emit an `Edited` event only for that editor.
119 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
120 assert_eq!(
121 mem::take(&mut *events.borrow_mut()),
122 [
123 ("editor1", "edited"),
124 ("editor1", "buffer edited"),
125 ("editor2", "buffer edited"),
126 ]
127 );
128
129 // Redoing on editor 1 will emit an `Edited` event only for that editor.
130 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
131 assert_eq!(
132 mem::take(&mut *events.borrow_mut()),
133 [
134 ("editor1", "edited"),
135 ("editor1", "buffer edited"),
136 ("editor2", "buffer edited"),
137 ]
138 );
139
140 // Undoing on editor 2 will emit an `Edited` event only for that editor.
141 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
142 assert_eq!(
143 mem::take(&mut *events.borrow_mut()),
144 [
145 ("editor2", "edited"),
146 ("editor1", "buffer edited"),
147 ("editor2", "buffer edited"),
148 ]
149 );
150
151 // Redoing on editor 2 will emit an `Edited` event only for that editor.
152 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
153 assert_eq!(
154 mem::take(&mut *events.borrow_mut()),
155 [
156 ("editor2", "edited"),
157 ("editor1", "buffer edited"),
158 ("editor2", "buffer edited"),
159 ]
160 );
161
162 // No event is emitted when the mutation is a no-op.
163 _ = editor2.update(cx, |editor, cx| {
164 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
165
166 editor.backspace(&Backspace, cx);
167 });
168 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
169}
170
171#[gpui::test]
172fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
173 init_test(cx, |_| {});
174
175 let mut now = Instant::now();
176 let group_interval = Duration::from_millis(1);
177 let buffer = cx.new_model(|cx| {
178 let mut buf = language::Buffer::local("123456", cx);
179 buf.set_group_interval(group_interval);
180 buf
181 });
182 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
183 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
184
185 _ = editor.update(cx, |editor, cx| {
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
188
189 editor.insert("cd", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cd56");
192 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
193
194 editor.start_transaction_at(now, cx);
195 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
196 editor.insert("e", cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cde6");
199 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
200
201 now += group_interval + Duration::from_millis(1);
202 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
203
204 // Simulate an edit in another editor
205 buffer.update(cx, |buffer, cx| {
206 buffer.start_transaction_at(now, cx);
207 buffer.edit([(0..1, "a")], None, cx);
208 buffer.edit([(1..1, "b")], None, cx);
209 buffer.end_transaction_at(now, cx);
210 });
211
212 assert_eq!(editor.text(cx), "ab2cde6");
213 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
214
215 // Last transaction happened past the group interval in a different editor.
216 // Undo it individually and don't restore selections.
217 editor.undo(&Undo, cx);
218 assert_eq!(editor.text(cx), "12cde6");
219 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
220
221 // First two transactions happened within the group interval in this editor.
222 // Undo them together and restore selections.
223 editor.undo(&Undo, cx);
224 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
225 assert_eq!(editor.text(cx), "123456");
226 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
227
228 // Redo the first two transactions together.
229 editor.redo(&Redo, cx);
230 assert_eq!(editor.text(cx), "12cde6");
231 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
232
233 // Redo the last transaction on its own.
234 editor.redo(&Redo, cx);
235 assert_eq!(editor.text(cx), "ab2cde6");
236 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
237
238 // Test empty transactions.
239 editor.start_transaction_at(now, cx);
240 editor.end_transaction_at(now, cx);
241 editor.undo(&Undo, cx);
242 assert_eq!(editor.text(cx), "12cde6");
243 });
244}
245
246#[gpui::test]
247fn test_ime_composition(cx: &mut TestAppContext) {
248 init_test(cx, |_| {});
249
250 let buffer = cx.new_model(|cx| {
251 let mut buffer = language::Buffer::local("abcde", cx);
252 // Ensure automatic grouping doesn't occur.
253 buffer.set_group_interval(Duration::ZERO);
254 buffer
255 });
256
257 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
258 cx.add_window(|cx| {
259 let mut editor = build_editor(buffer.clone(), cx);
260
261 // Start a new IME composition.
262 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
263 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
264 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
265 assert_eq!(editor.text(cx), "äbcde");
266 assert_eq!(
267 editor.marked_text_ranges(cx),
268 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
269 );
270
271 // Finalize IME composition.
272 editor.replace_text_in_range(None, "ā", cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // IME composition edits are grouped and are undone/redone at once.
277 editor.undo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "abcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280 editor.redo(&Default::default(), cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // Start a new IME composition.
285 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
286 assert_eq!(
287 editor.marked_text_ranges(cx),
288 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
289 );
290
291 // Undoing during an IME composition cancels it.
292 editor.undo(&Default::default(), cx);
293 assert_eq!(editor.text(cx), "ābcde");
294 assert_eq!(editor.marked_text_ranges(cx), None);
295
296 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
297 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
298 assert_eq!(editor.text(cx), "ābcdè");
299 assert_eq!(
300 editor.marked_text_ranges(cx),
301 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
302 );
303
304 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
305 editor.replace_text_in_range(Some(4..999), "ę", cx);
306 assert_eq!(editor.text(cx), "ābcdę");
307 assert_eq!(editor.marked_text_ranges(cx), None);
308
309 // Start a new IME composition with multiple cursors.
310 editor.change_selections(None, cx, |s| {
311 s.select_ranges([
312 OffsetUtf16(1)..OffsetUtf16(1),
313 OffsetUtf16(3)..OffsetUtf16(3),
314 OffsetUtf16(5)..OffsetUtf16(5),
315 ])
316 });
317 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
318 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
319 assert_eq!(
320 editor.marked_text_ranges(cx),
321 Some(vec![
322 OffsetUtf16(0)..OffsetUtf16(3),
323 OffsetUtf16(4)..OffsetUtf16(7),
324 OffsetUtf16(8)..OffsetUtf16(11)
325 ])
326 );
327
328 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
329 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
330 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
331 assert_eq!(
332 editor.marked_text_ranges(cx),
333 Some(vec![
334 OffsetUtf16(1)..OffsetUtf16(2),
335 OffsetUtf16(5)..OffsetUtf16(6),
336 OffsetUtf16(9)..OffsetUtf16(10)
337 ])
338 );
339
340 // Finalize IME composition with multiple cursors.
341 editor.replace_text_in_range(Some(9..10), "2", cx);
342 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
343 assert_eq!(editor.marked_text_ranges(cx), None);
344
345 editor
346 });
347}
348
349#[gpui::test]
350fn test_selection_with_mouse(cx: &mut TestAppContext) {
351 init_test(cx, |_| {});
352
353 let editor = cx.add_window(|cx| {
354 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
355 build_editor(buffer, cx)
356 });
357
358 _ = editor.update(cx, |view, cx| {
359 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
360 });
361 assert_eq!(
362 editor
363 .update(cx, |view, cx| view.selections.display_ranges(cx))
364 .unwrap(),
365 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
366 );
367
368 _ = editor.update(cx, |view, cx| {
369 view.update_selection(
370 DisplayPoint::new(DisplayRow(3), 3),
371 0,
372 gpui::Point::<f32>::default(),
373 cx,
374 );
375 });
376
377 assert_eq!(
378 editor
379 .update(cx, |view, cx| view.selections.display_ranges(cx))
380 .unwrap(),
381 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
382 );
383
384 _ = editor.update(cx, |view, cx| {
385 view.update_selection(
386 DisplayPoint::new(DisplayRow(1), 1),
387 0,
388 gpui::Point::<f32>::default(),
389 cx,
390 );
391 });
392
393 assert_eq!(
394 editor
395 .update(cx, |view, cx| view.selections.display_ranges(cx))
396 .unwrap(),
397 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
398 );
399
400 _ = editor.update(cx, |view, cx| {
401 view.end_selection(cx);
402 view.update_selection(
403 DisplayPoint::new(DisplayRow(3), 3),
404 0,
405 gpui::Point::<f32>::default(),
406 cx,
407 );
408 });
409
410 assert_eq!(
411 editor
412 .update(cx, |view, cx| view.selections.display_ranges(cx))
413 .unwrap(),
414 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
415 );
416
417 _ = editor.update(cx, |view, cx| {
418 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
419 view.update_selection(
420 DisplayPoint::new(DisplayRow(0), 0),
421 0,
422 gpui::Point::<f32>::default(),
423 cx,
424 );
425 });
426
427 assert_eq!(
428 editor
429 .update(cx, |view, cx| view.selections.display_ranges(cx))
430 .unwrap(),
431 [
432 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
433 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
434 ]
435 );
436
437 _ = editor.update(cx, |view, cx| {
438 view.end_selection(cx);
439 });
440
441 assert_eq!(
442 editor
443 .update(cx, |view, cx| view.selections.display_ranges(cx))
444 .unwrap(),
445 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
446 );
447}
448
449#[gpui::test]
450fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
451 init_test(cx, |_| {});
452
453 let editor = cx.add_window(|cx| {
454 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
455 build_editor(buffer, cx)
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
468 });
469
470 _ = editor.update(cx, |view, cx| {
471 view.end_selection(cx);
472 });
473
474 assert_eq!(
475 editor
476 .update(cx, |view, cx| view.selections.display_ranges(cx))
477 .unwrap(),
478 [
479 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
480 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
481 ]
482 );
483
484 _ = editor.update(cx, |view, cx| {
485 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
486 });
487
488 _ = editor.update(cx, |view, cx| {
489 view.end_selection(cx);
490 });
491
492 assert_eq!(
493 editor
494 .update(cx, |view, cx| view.selections.display_ranges(cx))
495 .unwrap(),
496 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
497 );
498}
499
500#[gpui::test]
501fn test_canceling_pending_selection(cx: &mut TestAppContext) {
502 init_test(cx, |_| {});
503
504 let view = cx.add_window(|cx| {
505 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
506 build_editor(buffer, cx)
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
511 assert_eq!(
512 view.selections.display_ranges(cx),
513 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
514 );
515 });
516
517 _ = view.update(cx, |view, cx| {
518 view.update_selection(
519 DisplayPoint::new(DisplayRow(3), 3),
520 0,
521 gpui::Point::<f32>::default(),
522 cx,
523 );
524 assert_eq!(
525 view.selections.display_ranges(cx),
526 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
527 );
528 });
529
530 _ = view.update(cx, |view, cx| {
531 view.cancel(&Cancel, cx);
532 view.update_selection(
533 DisplayPoint::new(DisplayRow(1), 1),
534 0,
535 gpui::Point::<f32>::default(),
536 cx,
537 );
538 assert_eq!(
539 view.selections.display_ranges(cx),
540 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
541 );
542 });
543}
544
545#[gpui::test]
546fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
547 init_test(cx, |_| {});
548
549 let view = cx.add_window(|cx| {
550 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
551 build_editor(buffer, cx)
552 });
553
554 _ = view.update(cx, |view, cx| {
555 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
556 assert_eq!(
557 view.selections.display_ranges(cx),
558 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
559 );
560
561 view.move_down(&Default::default(), cx);
562 assert_eq!(
563 view.selections.display_ranges(cx),
564 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
565 );
566
567 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
568 assert_eq!(
569 view.selections.display_ranges(cx),
570 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
571 );
572
573 view.move_up(&Default::default(), cx);
574 assert_eq!(
575 view.selections.display_ranges(cx),
576 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
577 );
578 });
579}
580
581#[gpui::test]
582fn test_clone(cx: &mut TestAppContext) {
583 init_test(cx, |_| {});
584
585 let (text, selection_ranges) = marked_text_ranges(
586 indoc! {"
587 one
588 two
589 threeˇ
590 four
591 fiveˇ
592 "},
593 true,
594 );
595
596 let editor = cx.add_window(|cx| {
597 let buffer = MultiBuffer::build_simple(&text, cx);
598 build_editor(buffer, cx)
599 });
600
601 _ = editor.update(cx, |editor, cx| {
602 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
603 editor.fold_creases(
604 vec![
605 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
606 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
607 ],
608 true,
609 cx,
610 );
611 });
612
613 let cloned_editor = editor
614 .update(cx, |editor, cx| {
615 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
616 })
617 .unwrap()
618 .unwrap();
619
620 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
621 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
622
623 assert_eq!(
624 cloned_editor
625 .update(cx, |e, cx| e.display_text(cx))
626 .unwrap(),
627 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
628 );
629 assert_eq!(
630 cloned_snapshot
631 .folds_in_range(0..text.len())
632 .collect::<Vec<_>>(),
633 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
638 .unwrap(),
639 editor
640 .update(cx, |editor, cx| editor.selections.ranges(cx))
641 .unwrap()
642 );
643 assert_set_eq!(
644 cloned_editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap(),
647 editor
648 .update(cx, |e, cx| e.selections.display_ranges(cx))
649 .unwrap()
650 );
651}
652
653#[gpui::test]
654async fn test_navigation_history(cx: &mut TestAppContext) {
655 init_test(cx, |_| {});
656
657 use workspace::item::Item;
658
659 let fs = FakeFs::new(cx.executor());
660 let project = Project::test(fs, [], cx).await;
661 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
662 let pane = workspace
663 .update(cx, |workspace, _| workspace.active_pane().clone())
664 .unwrap();
665
666 _ = workspace.update(cx, |_v, cx| {
667 cx.new_view(|cx| {
668 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
669 let mut editor = build_editor(buffer.clone(), cx);
670 let handle = cx.view();
671 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
672
673 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
674 editor.nav_history.as_mut().unwrap().pop_backward(cx)
675 }
676
677 // Move the cursor a small distance.
678 // Nothing is added to the navigation history.
679 editor.change_selections(None, cx, |s| {
680 s.select_display_ranges([
681 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
682 ])
683 });
684 editor.change_selections(None, cx, |s| {
685 s.select_display_ranges([
686 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
687 ])
688 });
689 assert!(pop_history(&mut editor, cx).is_none());
690
691 // Move the cursor a large distance.
692 // The history can jump back to the previous position.
693 editor.change_selections(None, cx, |s| {
694 s.select_display_ranges([
695 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
696 ])
697 });
698 let nav_entry = pop_history(&mut editor, cx).unwrap();
699 editor.navigate(nav_entry.data.unwrap(), cx);
700 assert_eq!(nav_entry.item.id(), cx.entity_id());
701 assert_eq!(
702 editor.selections.display_ranges(cx),
703 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
704 );
705 assert!(pop_history(&mut editor, cx).is_none());
706
707 // Move the cursor a small distance via the mouse.
708 // Nothing is added to the navigation history.
709 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
710 editor.end_selection(cx);
711 assert_eq!(
712 editor.selections.display_ranges(cx),
713 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
714 );
715 assert!(pop_history(&mut editor, cx).is_none());
716
717 // Move the cursor a large distance via the mouse.
718 // The history can jump back to the previous position.
719 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
720 editor.end_selection(cx);
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
724 );
725 let nav_entry = pop_history(&mut editor, cx).unwrap();
726 editor.navigate(nav_entry.data.unwrap(), cx);
727 assert_eq!(nav_entry.item.id(), cx.entity_id());
728 assert_eq!(
729 editor.selections.display_ranges(cx),
730 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
731 );
732 assert!(pop_history(&mut editor, cx).is_none());
733
734 // Set scroll position to check later
735 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
736 let original_scroll_position = editor.scroll_manager.anchor();
737
738 // Jump to the end of the document and adjust scroll
739 editor.move_to_end(&MoveToEnd, cx);
740 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
741 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 let nav_entry = pop_history(&mut editor, cx).unwrap();
744 editor.navigate(nav_entry.data.unwrap(), cx);
745 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
746
747 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
748 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
749 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
750 let invalid_point = Point::new(9999, 0);
751 editor.navigate(
752 Box::new(NavigationData {
753 cursor_anchor: invalid_anchor,
754 cursor_position: invalid_point,
755 scroll_anchor: ScrollAnchor {
756 anchor: invalid_anchor,
757 offset: Default::default(),
758 },
759 scroll_top_row: invalid_point.row,
760 }),
761 cx,
762 );
763 assert_eq!(
764 editor.selections.display_ranges(cx),
765 &[editor.max_point(cx)..editor.max_point(cx)]
766 );
767 assert_eq!(
768 editor.scroll_position(cx),
769 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
770 );
771
772 editor
773 })
774 });
775}
776
777#[gpui::test]
778fn test_cancel(cx: &mut TestAppContext) {
779 init_test(cx, |_| {});
780
781 let view = cx.add_window(|cx| {
782 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
783 build_editor(buffer, cx)
784 });
785
786 _ = view.update(cx, |view, cx| {
787 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
788 view.update_selection(
789 DisplayPoint::new(DisplayRow(1), 1),
790 0,
791 gpui::Point::<f32>::default(),
792 cx,
793 );
794 view.end_selection(cx);
795
796 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
797 view.update_selection(
798 DisplayPoint::new(DisplayRow(0), 3),
799 0,
800 gpui::Point::<f32>::default(),
801 cx,
802 );
803 view.end_selection(cx);
804 assert_eq!(
805 view.selections.display_ranges(cx),
806 [
807 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
808 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
809 ]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820
821 _ = view.update(cx, |view, cx| {
822 view.cancel(&Cancel, cx);
823 assert_eq!(
824 view.selections.display_ranges(cx),
825 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
826 );
827 });
828}
829
830#[gpui::test]
831fn test_fold_action(cx: &mut TestAppContext) {
832 init_test(cx, |_| {});
833
834 let view = cx.add_window(|cx| {
835 let buffer = MultiBuffer::build_simple(
836 &"
837 impl Foo {
838 // Hello!
839
840 fn a() {
841 1
842 }
843
844 fn b() {
845 2
846 }
847
848 fn c() {
849 3
850 }
851 }
852 "
853 .unindent(),
854 cx,
855 );
856 build_editor(buffer.clone(), cx)
857 });
858
859 _ = view.update(cx, |view, cx| {
860 view.change_selections(None, cx, |s| {
861 s.select_display_ranges([
862 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
863 ]);
864 });
865 view.fold(&Fold, cx);
866 assert_eq!(
867 view.display_text(cx),
868 "
869 impl Foo {
870 // Hello!
871
872 fn a() {
873 1
874 }
875
876 fn b() {⋯
877 }
878
879 fn c() {⋯
880 }
881 }
882 "
883 .unindent(),
884 );
885
886 view.fold(&Fold, cx);
887 assert_eq!(
888 view.display_text(cx),
889 "
890 impl Foo {⋯
891 }
892 "
893 .unindent(),
894 );
895
896 view.unfold_lines(&UnfoldLines, cx);
897 assert_eq!(
898 view.display_text(cx),
899 "
900 impl Foo {
901 // Hello!
902
903 fn a() {
904 1
905 }
906
907 fn b() {⋯
908 }
909
910 fn c() {⋯
911 }
912 }
913 "
914 .unindent(),
915 );
916
917 view.unfold_lines(&UnfoldLines, cx);
918 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
919 });
920}
921
922#[gpui::test]
923fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
924 init_test(cx, |_| {});
925
926 let view = cx.add_window(|cx| {
927 let buffer = MultiBuffer::build_simple(
928 &"
929 class Foo:
930 # Hello!
931
932 def a():
933 print(1)
934
935 def b():
936 print(2)
937
938 def c():
939 print(3)
940 "
941 .unindent(),
942 cx,
943 );
944 build_editor(buffer.clone(), cx)
945 });
946
947 _ = view.update(cx, |view, cx| {
948 view.change_selections(None, cx, |s| {
949 s.select_display_ranges([
950 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
951 ]);
952 });
953 view.fold(&Fold, cx);
954 assert_eq!(
955 view.display_text(cx),
956 "
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():⋯
964
965 def c():⋯
966 "
967 .unindent(),
968 );
969
970 view.fold(&Fold, cx);
971 assert_eq!(
972 view.display_text(cx),
973 "
974 class Foo:⋯
975 "
976 .unindent(),
977 );
978
979 view.unfold_lines(&UnfoldLines, cx);
980 assert_eq!(
981 view.display_text(cx),
982 "
983 class Foo:
984 # Hello!
985
986 def a():
987 print(1)
988
989 def b():⋯
990
991 def c():⋯
992 "
993 .unindent(),
994 );
995
996 view.unfold_lines(&UnfoldLines, cx);
997 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
998 });
999}
1000
1001#[gpui::test]
1002fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1003 init_test(cx, |_| {});
1004
1005 let view = cx.add_window(|cx| {
1006 let buffer = MultiBuffer::build_simple(
1007 &"
1008 class Foo:
1009 # Hello!
1010
1011 def a():
1012 print(1)
1013
1014 def b():
1015 print(2)
1016
1017
1018 def c():
1019 print(3)
1020
1021
1022 "
1023 .unindent(),
1024 cx,
1025 );
1026 build_editor(buffer.clone(), cx)
1027 });
1028
1029 _ = view.update(cx, |view, cx| {
1030 view.change_selections(None, cx, |s| {
1031 s.select_display_ranges([
1032 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1033 ]);
1034 });
1035 view.fold(&Fold, cx);
1036 assert_eq!(
1037 view.display_text(cx),
1038 "
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():⋯
1046
1047
1048 def c():⋯
1049
1050
1051 "
1052 .unindent(),
1053 );
1054
1055 view.fold(&Fold, cx);
1056 assert_eq!(
1057 view.display_text(cx),
1058 "
1059 class Foo:⋯
1060
1061
1062 "
1063 .unindent(),
1064 );
1065
1066 view.unfold_lines(&UnfoldLines, cx);
1067 assert_eq!(
1068 view.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 view.unfold_lines(&UnfoldLines, cx);
1087 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1088 });
1089}
1090
1091#[gpui::test]
1092fn test_fold_at_level(cx: &mut TestAppContext) {
1093 init_test(cx, |_| {});
1094
1095 let view = cx.add_window(|cx| {
1096 let buffer = MultiBuffer::build_simple(
1097 &"
1098 class Foo:
1099 # Hello!
1100
1101 def a():
1102 print(1)
1103
1104 def b():
1105 print(2)
1106
1107
1108 class Bar:
1109 # World!
1110
1111 def a():
1112 print(1)
1113
1114 def b():
1115 print(2)
1116
1117
1118 "
1119 .unindent(),
1120 cx,
1121 );
1122 build_editor(buffer.clone(), cx)
1123 });
1124
1125 _ = view.update(cx, |view, cx| {
1126 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1127 assert_eq!(
1128 view.display_text(cx),
1129 "
1130 class Foo:
1131 # Hello!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 class Bar:
1139 # World!
1140
1141 def a():⋯
1142
1143 def b():⋯
1144
1145
1146 "
1147 .unindent(),
1148 );
1149
1150 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1151 assert_eq!(
1152 view.display_text(cx),
1153 "
1154 class Foo:⋯
1155
1156
1157 class Bar:⋯
1158
1159
1160 "
1161 .unindent(),
1162 );
1163
1164 view.unfold_all(&UnfoldAll, cx);
1165 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1166 assert_eq!(
1167 view.display_text(cx),
1168 "
1169 class Foo:
1170 # Hello!
1171
1172 def a():
1173 print(1)
1174
1175 def b():
1176 print(2)
1177
1178
1179 class Bar:
1180 # World!
1181
1182 def a():
1183 print(1)
1184
1185 def b():
1186 print(2)
1187
1188
1189 "
1190 .unindent(),
1191 );
1192
1193 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1194 });
1195}
1196
1197#[gpui::test]
1198fn test_move_cursor(cx: &mut TestAppContext) {
1199 init_test(cx, |_| {});
1200
1201 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1202 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1203
1204 buffer.update(cx, |buffer, cx| {
1205 buffer.edit(
1206 vec![
1207 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1208 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1209 ],
1210 None,
1211 cx,
1212 );
1213 });
1214 _ = view.update(cx, |view, cx| {
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1218 );
1219
1220 view.move_down(&MoveDown, cx);
1221 assert_eq!(
1222 view.selections.display_ranges(cx),
1223 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1224 );
1225
1226 view.move_right(&MoveRight, cx);
1227 assert_eq!(
1228 view.selections.display_ranges(cx),
1229 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1230 );
1231
1232 view.move_left(&MoveLeft, cx);
1233 assert_eq!(
1234 view.selections.display_ranges(cx),
1235 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1236 );
1237
1238 view.move_up(&MoveUp, cx);
1239 assert_eq!(
1240 view.selections.display_ranges(cx),
1241 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1242 );
1243
1244 view.move_to_end(&MoveToEnd, cx);
1245 assert_eq!(
1246 view.selections.display_ranges(cx),
1247 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1248 );
1249
1250 view.move_to_beginning(&MoveToBeginning, cx);
1251 assert_eq!(
1252 view.selections.display_ranges(cx),
1253 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1254 );
1255
1256 view.change_selections(None, cx, |s| {
1257 s.select_display_ranges([
1258 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1259 ]);
1260 });
1261 view.select_to_beginning(&SelectToBeginning, cx);
1262 assert_eq!(
1263 view.selections.display_ranges(cx),
1264 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1265 );
1266
1267 view.select_to_end(&SelectToEnd, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1271 );
1272 });
1273}
1274
1275// TODO: Re-enable this test
1276#[cfg(target_os = "macos")]
1277#[gpui::test]
1278fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1279 init_test(cx, |_| {});
1280
1281 let view = cx.add_window(|cx| {
1282 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1283 build_editor(buffer.clone(), cx)
1284 });
1285
1286 assert_eq!('ⓐ'.len_utf8(), 3);
1287 assert_eq!('α'.len_utf8(), 2);
1288
1289 _ = view.update(cx, |view, cx| {
1290 view.fold_creases(
1291 vec![
1292 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1293 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1294 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1295 ],
1296 true,
1297 cx,
1298 );
1299 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1300
1301 view.move_right(&MoveRight, cx);
1302 assert_eq!(
1303 view.selections.display_ranges(cx),
1304 &[empty_range(0, "ⓐ".len())]
1305 );
1306 view.move_right(&MoveRight, cx);
1307 assert_eq!(
1308 view.selections.display_ranges(cx),
1309 &[empty_range(0, "ⓐⓑ".len())]
1310 );
1311 view.move_right(&MoveRight, cx);
1312 assert_eq!(
1313 view.selections.display_ranges(cx),
1314 &[empty_range(0, "ⓐⓑ⋯".len())]
1315 );
1316
1317 view.move_down(&MoveDown, cx);
1318 assert_eq!(
1319 view.selections.display_ranges(cx),
1320 &[empty_range(1, "ab⋯e".len())]
1321 );
1322 view.move_left(&MoveLeft, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(1, "ab⋯".len())]
1326 );
1327 view.move_left(&MoveLeft, cx);
1328 assert_eq!(
1329 view.selections.display_ranges(cx),
1330 &[empty_range(1, "ab".len())]
1331 );
1332 view.move_left(&MoveLeft, cx);
1333 assert_eq!(
1334 view.selections.display_ranges(cx),
1335 &[empty_range(1, "a".len())]
1336 );
1337
1338 view.move_down(&MoveDown, cx);
1339 assert_eq!(
1340 view.selections.display_ranges(cx),
1341 &[empty_range(2, "α".len())]
1342 );
1343 view.move_right(&MoveRight, cx);
1344 assert_eq!(
1345 view.selections.display_ranges(cx),
1346 &[empty_range(2, "αβ".len())]
1347 );
1348 view.move_right(&MoveRight, cx);
1349 assert_eq!(
1350 view.selections.display_ranges(cx),
1351 &[empty_range(2, "αβ⋯".len())]
1352 );
1353 view.move_right(&MoveRight, cx);
1354 assert_eq!(
1355 view.selections.display_ranges(cx),
1356 &[empty_range(2, "αβ⋯ε".len())]
1357 );
1358
1359 view.move_up(&MoveUp, cx);
1360 assert_eq!(
1361 view.selections.display_ranges(cx),
1362 &[empty_range(1, "ab⋯e".len())]
1363 );
1364 view.move_down(&MoveDown, cx);
1365 assert_eq!(
1366 view.selections.display_ranges(cx),
1367 &[empty_range(2, "αβ⋯ε".len())]
1368 );
1369 view.move_up(&MoveUp, cx);
1370 assert_eq!(
1371 view.selections.display_ranges(cx),
1372 &[empty_range(1, "ab⋯e".len())]
1373 );
1374
1375 view.move_up(&MoveUp, cx);
1376 assert_eq!(
1377 view.selections.display_ranges(cx),
1378 &[empty_range(0, "ⓐⓑ".len())]
1379 );
1380 view.move_left(&MoveLeft, cx);
1381 assert_eq!(
1382 view.selections.display_ranges(cx),
1383 &[empty_range(0, "ⓐ".len())]
1384 );
1385 view.move_left(&MoveLeft, cx);
1386 assert_eq!(
1387 view.selections.display_ranges(cx),
1388 &[empty_range(0, "".len())]
1389 );
1390 });
1391}
1392
1393#[gpui::test]
1394fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1395 init_test(cx, |_| {});
1396
1397 let view = cx.add_window(|cx| {
1398 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1399 build_editor(buffer.clone(), cx)
1400 });
1401 _ = view.update(cx, |view, cx| {
1402 view.change_selections(None, cx, |s| {
1403 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1404 });
1405
1406 // moving above start of document should move selection to start of document,
1407 // but the next move down should still be at the original goal_x
1408 view.move_up(&MoveUp, cx);
1409 assert_eq!(
1410 view.selections.display_ranges(cx),
1411 &[empty_range(0, "".len())]
1412 );
1413
1414 view.move_down(&MoveDown, cx);
1415 assert_eq!(
1416 view.selections.display_ranges(cx),
1417 &[empty_range(1, "abcd".len())]
1418 );
1419
1420 view.move_down(&MoveDown, cx);
1421 assert_eq!(
1422 view.selections.display_ranges(cx),
1423 &[empty_range(2, "αβγ".len())]
1424 );
1425
1426 view.move_down(&MoveDown, cx);
1427 assert_eq!(
1428 view.selections.display_ranges(cx),
1429 &[empty_range(3, "abcd".len())]
1430 );
1431
1432 view.move_down(&MoveDown, cx);
1433 assert_eq!(
1434 view.selections.display_ranges(cx),
1435 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1436 );
1437
1438 // moving past end of document should not change goal_x
1439 view.move_down(&MoveDown, cx);
1440 assert_eq!(
1441 view.selections.display_ranges(cx),
1442 &[empty_range(5, "".len())]
1443 );
1444
1445 view.move_down(&MoveDown, cx);
1446 assert_eq!(
1447 view.selections.display_ranges(cx),
1448 &[empty_range(5, "".len())]
1449 );
1450
1451 view.move_up(&MoveUp, cx);
1452 assert_eq!(
1453 view.selections.display_ranges(cx),
1454 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1455 );
1456
1457 view.move_up(&MoveUp, cx);
1458 assert_eq!(
1459 view.selections.display_ranges(cx),
1460 &[empty_range(3, "abcd".len())]
1461 );
1462
1463 view.move_up(&MoveUp, cx);
1464 assert_eq!(
1465 view.selections.display_ranges(cx),
1466 &[empty_range(2, "αβγ".len())]
1467 );
1468 });
1469}
1470
1471#[gpui::test]
1472fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1473 init_test(cx, |_| {});
1474 let move_to_beg = MoveToBeginningOfLine {
1475 stop_at_soft_wraps: true,
1476 };
1477
1478 let move_to_end = MoveToEndOfLine {
1479 stop_at_soft_wraps: true,
1480 };
1481
1482 let view = cx.add_window(|cx| {
1483 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1484 build_editor(buffer, cx)
1485 });
1486 _ = view.update(cx, |view, cx| {
1487 view.change_selections(None, cx, |s| {
1488 s.select_display_ranges([
1489 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1490 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1491 ]);
1492 });
1493 });
1494
1495 _ = view.update(cx, |view, cx| {
1496 view.move_to_beginning_of_line(&move_to_beg, cx);
1497 assert_eq!(
1498 view.selections.display_ranges(cx),
1499 &[
1500 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1501 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1502 ]
1503 );
1504 });
1505
1506 _ = view.update(cx, |view, cx| {
1507 view.move_to_beginning_of_line(&move_to_beg, cx);
1508 assert_eq!(
1509 view.selections.display_ranges(cx),
1510 &[
1511 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1512 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1513 ]
1514 );
1515 });
1516
1517 _ = view.update(cx, |view, cx| {
1518 view.move_to_beginning_of_line(&move_to_beg, cx);
1519 assert_eq!(
1520 view.selections.display_ranges(cx),
1521 &[
1522 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1523 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1524 ]
1525 );
1526 });
1527
1528 _ = view.update(cx, |view, cx| {
1529 view.move_to_end_of_line(&move_to_end, cx);
1530 assert_eq!(
1531 view.selections.display_ranges(cx),
1532 &[
1533 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1534 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1535 ]
1536 );
1537 });
1538
1539 // Moving to the end of line again is a no-op.
1540 _ = view.update(cx, |view, cx| {
1541 view.move_to_end_of_line(&move_to_end, cx);
1542 assert_eq!(
1543 view.selections.display_ranges(cx),
1544 &[
1545 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1546 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1547 ]
1548 );
1549 });
1550
1551 _ = view.update(cx, |view, cx| {
1552 view.move_left(&MoveLeft, cx);
1553 view.select_to_beginning_of_line(
1554 &SelectToBeginningOfLine {
1555 stop_at_soft_wraps: true,
1556 },
1557 cx,
1558 );
1559 assert_eq!(
1560 view.selections.display_ranges(cx),
1561 &[
1562 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1563 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1564 ]
1565 );
1566 });
1567
1568 _ = view.update(cx, |view, cx| {
1569 view.select_to_beginning_of_line(
1570 &SelectToBeginningOfLine {
1571 stop_at_soft_wraps: true,
1572 },
1573 cx,
1574 );
1575 assert_eq!(
1576 view.selections.display_ranges(cx),
1577 &[
1578 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1579 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1580 ]
1581 );
1582 });
1583
1584 _ = view.update(cx, |view, cx| {
1585 view.select_to_beginning_of_line(
1586 &SelectToBeginningOfLine {
1587 stop_at_soft_wraps: true,
1588 },
1589 cx,
1590 );
1591 assert_eq!(
1592 view.selections.display_ranges(cx),
1593 &[
1594 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1595 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1596 ]
1597 );
1598 });
1599
1600 _ = view.update(cx, |view, cx| {
1601 view.select_to_end_of_line(
1602 &SelectToEndOfLine {
1603 stop_at_soft_wraps: true,
1604 },
1605 cx,
1606 );
1607 assert_eq!(
1608 view.selections.display_ranges(cx),
1609 &[
1610 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1611 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1612 ]
1613 );
1614 });
1615
1616 _ = view.update(cx, |view, cx| {
1617 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1618 assert_eq!(view.display_text(cx), "ab\n de");
1619 assert_eq!(
1620 view.selections.display_ranges(cx),
1621 &[
1622 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1623 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1624 ]
1625 );
1626 });
1627
1628 _ = view.update(cx, |view, cx| {
1629 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1630 assert_eq!(view.display_text(cx), "\n");
1631 assert_eq!(
1632 view.selections.display_ranges(cx),
1633 &[
1634 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1635 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1636 ]
1637 );
1638 });
1639}
1640
1641#[gpui::test]
1642fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1643 init_test(cx, |_| {});
1644 let move_to_beg = MoveToBeginningOfLine {
1645 stop_at_soft_wraps: false,
1646 };
1647
1648 let move_to_end = MoveToEndOfLine {
1649 stop_at_soft_wraps: false,
1650 };
1651
1652 let view = cx.add_window(|cx| {
1653 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1654 build_editor(buffer, cx)
1655 });
1656
1657 _ = view.update(cx, |view, cx| {
1658 view.set_wrap_width(Some(140.0.into()), cx);
1659
1660 // We expect the following lines after wrapping
1661 // ```
1662 // thequickbrownfox
1663 // jumpedoverthelazydo
1664 // gs
1665 // ```
1666 // The final `gs` was soft-wrapped onto a new line.
1667 assert_eq!(
1668 "thequickbrownfox\njumpedoverthelaz\nydogs",
1669 view.display_text(cx),
1670 );
1671
1672 // First, let's assert behavior on the first line, that was not soft-wrapped.
1673 // Start the cursor at the `k` on the first line
1674 view.change_selections(None, cx, |s| {
1675 s.select_display_ranges([
1676 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1677 ]);
1678 });
1679
1680 // Moving to the beginning of the line should put us at the beginning of the line.
1681 view.move_to_beginning_of_line(&move_to_beg, cx);
1682 assert_eq!(
1683 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1684 view.selections.display_ranges(cx)
1685 );
1686
1687 // Moving to the end of the line should put us at the end of the line.
1688 view.move_to_end_of_line(&move_to_end, cx);
1689 assert_eq!(
1690 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1691 view.selections.display_ranges(cx)
1692 );
1693
1694 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1695 // Start the cursor at the last line (`y` that was wrapped to a new line)
1696 view.change_selections(None, cx, |s| {
1697 s.select_display_ranges([
1698 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1699 ]);
1700 });
1701
1702 // Moving to the beginning of the line should put us at the start of the second line of
1703 // display text, i.e., the `j`.
1704 view.move_to_beginning_of_line(&move_to_beg, cx);
1705 assert_eq!(
1706 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1707 view.selections.display_ranges(cx)
1708 );
1709
1710 // Moving to the beginning of the line again should be a no-op.
1711 view.move_to_beginning_of_line(&move_to_beg, cx);
1712 assert_eq!(
1713 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1714 view.selections.display_ranges(cx)
1715 );
1716
1717 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1718 // next display line.
1719 view.move_to_end_of_line(&move_to_end, cx);
1720 assert_eq!(
1721 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1722 view.selections.display_ranges(cx)
1723 );
1724
1725 // Moving to the end of the line again should be a no-op.
1726 view.move_to_end_of_line(&move_to_end, cx);
1727 assert_eq!(
1728 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1729 view.selections.display_ranges(cx)
1730 );
1731 });
1732}
1733
1734#[gpui::test]
1735fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1736 init_test(cx, |_| {});
1737
1738 let view = cx.add_window(|cx| {
1739 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1740 build_editor(buffer, cx)
1741 });
1742 _ = view.update(cx, |view, cx| {
1743 view.change_selections(None, cx, |s| {
1744 s.select_display_ranges([
1745 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1746 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1747 ])
1748 });
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_previous_word_start(&MoveToPreviousWordStart, 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_to_next_word_end(&MoveToNextWordEnd, cx);
1772 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1773
1774 view.move_right(&MoveRight, cx);
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_previous_word_start(&SelectToPreviousWordStart, cx);
1779 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1780
1781 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1782 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let view = cx.add_window(|cx| {
1791 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1792 build_editor(buffer, cx)
1793 });
1794
1795 _ = view.update(cx, |view, cx| {
1796 view.set_wrap_width(Some(140.0.into()), cx);
1797 assert_eq!(
1798 view.display_text(cx),
1799 "use one::{\n two::three::\n four::five\n};"
1800 );
1801
1802 view.change_selections(None, cx, |s| {
1803 s.select_display_ranges([
1804 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1805 ]);
1806 });
1807
1808 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1809 assert_eq!(
1810 view.selections.display_ranges(cx),
1811 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1812 );
1813
1814 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1815 assert_eq!(
1816 view.selections.display_ranges(cx),
1817 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1818 );
1819
1820 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1821 assert_eq!(
1822 view.selections.display_ranges(cx),
1823 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1824 );
1825
1826 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1827 assert_eq!(
1828 view.selections.display_ranges(cx),
1829 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1830 );
1831
1832 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1833 assert_eq!(
1834 view.selections.display_ranges(cx),
1835 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1836 );
1837
1838 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1839 assert_eq!(
1840 view.selections.display_ranges(cx),
1841 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1842 );
1843 });
1844}
1845
1846#[gpui::test]
1847async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1848 init_test(cx, |_| {});
1849 let mut cx = EditorTestContext::new(cx).await;
1850
1851 let line_height = cx.editor(|editor, cx| {
1852 editor
1853 .style()
1854 .unwrap()
1855 .text
1856 .line_height_in_pixels(cx.rem_size())
1857 });
1858 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1859
1860 cx.set_state(
1861 &r#"ˇone
1862 two
1863
1864 three
1865 fourˇ
1866 five
1867
1868 six"#
1869 .unindent(),
1870 );
1871
1872 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1873 cx.assert_editor_state(
1874 &r#"one
1875 two
1876 ˇ
1877 three
1878 four
1879 five
1880 ˇ
1881 six"#
1882 .unindent(),
1883 );
1884
1885 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1886 cx.assert_editor_state(
1887 &r#"one
1888 two
1889
1890 three
1891 four
1892 five
1893 ˇ
1894 sixˇ"#
1895 .unindent(),
1896 );
1897
1898 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1899 cx.assert_editor_state(
1900 &r#"one
1901 two
1902
1903 three
1904 four
1905 five
1906
1907 sixˇ"#
1908 .unindent(),
1909 );
1910
1911 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1912 cx.assert_editor_state(
1913 &r#"one
1914 two
1915
1916 three
1917 four
1918 five
1919 ˇ
1920 six"#
1921 .unindent(),
1922 );
1923
1924 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1925 cx.assert_editor_state(
1926 &r#"one
1927 two
1928 ˇ
1929 three
1930 four
1931 five
1932
1933 six"#
1934 .unindent(),
1935 );
1936
1937 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1938 cx.assert_editor_state(
1939 &r#"ˇone
1940 two
1941
1942 three
1943 four
1944 five
1945
1946 six"#
1947 .unindent(),
1948 );
1949}
1950
1951#[gpui::test]
1952async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1953 init_test(cx, |_| {});
1954 let mut cx = EditorTestContext::new(cx).await;
1955 let line_height = cx.editor(|editor, cx| {
1956 editor
1957 .style()
1958 .unwrap()
1959 .text
1960 .line_height_in_pixels(cx.rem_size())
1961 });
1962 let window = cx.window;
1963 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1964
1965 cx.set_state(
1966 r#"ˇone
1967 two
1968 three
1969 four
1970 five
1971 six
1972 seven
1973 eight
1974 nine
1975 ten
1976 "#,
1977 );
1978
1979 cx.update_editor(|editor, cx| {
1980 assert_eq!(
1981 editor.snapshot(cx).scroll_position(),
1982 gpui::Point::new(0., 0.)
1983 );
1984 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1985 assert_eq!(
1986 editor.snapshot(cx).scroll_position(),
1987 gpui::Point::new(0., 3.)
1988 );
1989 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1990 assert_eq!(
1991 editor.snapshot(cx).scroll_position(),
1992 gpui::Point::new(0., 6.)
1993 );
1994 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1995 assert_eq!(
1996 editor.snapshot(cx).scroll_position(),
1997 gpui::Point::new(0., 3.)
1998 );
1999
2000 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
2001 assert_eq!(
2002 editor.snapshot(cx).scroll_position(),
2003 gpui::Point::new(0., 1.)
2004 );
2005 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2006 assert_eq!(
2007 editor.snapshot(cx).scroll_position(),
2008 gpui::Point::new(0., 3.)
2009 );
2010 });
2011}
2012
2013#[gpui::test]
2014async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2015 init_test(cx, |_| {});
2016 let mut cx = EditorTestContext::new(cx).await;
2017
2018 let line_height = cx.update_editor(|editor, cx| {
2019 editor.set_vertical_scroll_margin(2, cx);
2020 editor
2021 .style()
2022 .unwrap()
2023 .text
2024 .line_height_in_pixels(cx.rem_size())
2025 });
2026 let window = cx.window;
2027 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2028
2029 cx.set_state(
2030 r#"ˇone
2031 two
2032 three
2033 four
2034 five
2035 six
2036 seven
2037 eight
2038 nine
2039 ten
2040 "#,
2041 );
2042 cx.update_editor(|editor, cx| {
2043 assert_eq!(
2044 editor.snapshot(cx).scroll_position(),
2045 gpui::Point::new(0., 0.0)
2046 );
2047 });
2048
2049 // Add a cursor below the visible area. Since both cursors cannot fit
2050 // on screen, the editor autoscrolls to reveal the newest cursor, and
2051 // allows the vertical scroll margin below that cursor.
2052 cx.update_editor(|editor, cx| {
2053 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2054 selections.select_ranges([
2055 Point::new(0, 0)..Point::new(0, 0),
2056 Point::new(6, 0)..Point::new(6, 0),
2057 ]);
2058 })
2059 });
2060 cx.update_editor(|editor, cx| {
2061 assert_eq!(
2062 editor.snapshot(cx).scroll_position(),
2063 gpui::Point::new(0., 3.0)
2064 );
2065 });
2066
2067 // Move down. The editor cursor scrolls down to track the newest cursor.
2068 cx.update_editor(|editor, cx| {
2069 editor.move_down(&Default::default(), cx);
2070 });
2071 cx.update_editor(|editor, cx| {
2072 assert_eq!(
2073 editor.snapshot(cx).scroll_position(),
2074 gpui::Point::new(0., 4.0)
2075 );
2076 });
2077
2078 // Add a cursor above the visible area. Since both cursors fit on screen,
2079 // the editor scrolls to show both.
2080 cx.update_editor(|editor, cx| {
2081 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2082 selections.select_ranges([
2083 Point::new(1, 0)..Point::new(1, 0),
2084 Point::new(6, 0)..Point::new(6, 0),
2085 ]);
2086 })
2087 });
2088 cx.update_editor(|editor, cx| {
2089 assert_eq!(
2090 editor.snapshot(cx).scroll_position(),
2091 gpui::Point::new(0., 1.0)
2092 );
2093 });
2094}
2095
2096#[gpui::test]
2097async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2098 init_test(cx, |_| {});
2099 let mut cx = EditorTestContext::new(cx).await;
2100
2101 let line_height = cx.editor(|editor, cx| {
2102 editor
2103 .style()
2104 .unwrap()
2105 .text
2106 .line_height_in_pixels(cx.rem_size())
2107 });
2108 let window = cx.window;
2109 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2110 cx.set_state(
2111 &r#"
2112 ˇone
2113 two
2114 threeˇ
2115 four
2116 five
2117 six
2118 seven
2119 eight
2120 nine
2121 ten
2122 "#
2123 .unindent(),
2124 );
2125
2126 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2127 cx.assert_editor_state(
2128 &r#"
2129 one
2130 two
2131 three
2132 ˇfour
2133 five
2134 sixˇ
2135 seven
2136 eight
2137 nine
2138 ten
2139 "#
2140 .unindent(),
2141 );
2142
2143 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2144 cx.assert_editor_state(
2145 &r#"
2146 one
2147 two
2148 three
2149 four
2150 five
2151 six
2152 ˇseven
2153 eight
2154 nineˇ
2155 ten
2156 "#
2157 .unindent(),
2158 );
2159
2160 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2161 cx.assert_editor_state(
2162 &r#"
2163 one
2164 two
2165 three
2166 ˇfour
2167 five
2168 sixˇ
2169 seven
2170 eight
2171 nine
2172 ten
2173 "#
2174 .unindent(),
2175 );
2176
2177 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2178 cx.assert_editor_state(
2179 &r#"
2180 ˇone
2181 two
2182 threeˇ
2183 four
2184 five
2185 six
2186 seven
2187 eight
2188 nine
2189 ten
2190 "#
2191 .unindent(),
2192 );
2193
2194 // Test select collapsing
2195 cx.update_editor(|editor, cx| {
2196 editor.move_page_down(&MovePageDown::default(), cx);
2197 editor.move_page_down(&MovePageDown::default(), cx);
2198 editor.move_page_down(&MovePageDown::default(), cx);
2199 });
2200 cx.assert_editor_state(
2201 &r#"
2202 one
2203 two
2204 three
2205 four
2206 five
2207 six
2208 seven
2209 eight
2210 nine
2211 ˇten
2212 ˇ"#
2213 .unindent(),
2214 );
2215}
2216
2217#[gpui::test]
2218async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2219 init_test(cx, |_| {});
2220 let mut cx = EditorTestContext::new(cx).await;
2221 cx.set_state("one «two threeˇ» four");
2222 cx.update_editor(|editor, cx| {
2223 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2224 assert_eq!(editor.text(cx), " four");
2225 });
2226}
2227
2228#[gpui::test]
2229fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2230 init_test(cx, |_| {});
2231
2232 let view = cx.add_window(|cx| {
2233 let buffer = MultiBuffer::build_simple("one two three four", cx);
2234 build_editor(buffer.clone(), cx)
2235 });
2236
2237 _ = view.update(cx, |view, cx| {
2238 view.change_selections(None, cx, |s| {
2239 s.select_display_ranges([
2240 // an empty selection - the preceding word fragment is deleted
2241 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2242 // characters selected - they are deleted
2243 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2244 ])
2245 });
2246 view.delete_to_previous_word_start(
2247 &DeleteToPreviousWordStart {
2248 ignore_newlines: false,
2249 },
2250 cx,
2251 );
2252 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2253 });
2254
2255 _ = view.update(cx, |view, cx| {
2256 view.change_selections(None, cx, |s| {
2257 s.select_display_ranges([
2258 // an empty selection - the following word fragment is deleted
2259 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2260 // characters selected - they are deleted
2261 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2262 ])
2263 });
2264 view.delete_to_next_word_end(
2265 &DeleteToNextWordEnd {
2266 ignore_newlines: false,
2267 },
2268 cx,
2269 );
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_prev_word_start = DeleteToPreviousWordStart {
2283 ignore_newlines: false,
2284 };
2285 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2293 ])
2294 });
2295 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2296 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2297 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2298 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2299 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2300 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2301 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2302 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2303 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2304 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2305 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2307 });
2308}
2309
2310#[gpui::test]
2311fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2312 init_test(cx, |_| {});
2313
2314 let view = cx.add_window(|cx| {
2315 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2316 build_editor(buffer.clone(), cx)
2317 });
2318 let del_to_next_word_end = DeleteToNextWordEnd {
2319 ignore_newlines: false,
2320 };
2321 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2322 ignore_newlines: true,
2323 };
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2329 ])
2330 });
2331 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2332 assert_eq!(
2333 view.buffer.read(cx).read(cx).text(),
2334 "one\n two\nthree\n four"
2335 );
2336 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2337 assert_eq!(
2338 view.buffer.read(cx).read(cx).text(),
2339 "\n two\nthree\n four"
2340 );
2341 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2342 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2343 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2344 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2345 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2346 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2347 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2348 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2349 });
2350}
2351
2352#[gpui::test]
2353fn test_newline(cx: &mut TestAppContext) {
2354 init_test(cx, |_| {});
2355
2356 let view = cx.add_window(|cx| {
2357 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2358 build_editor(buffer.clone(), cx)
2359 });
2360
2361 _ = view.update(cx, |view, cx| {
2362 view.change_selections(None, cx, |s| {
2363 s.select_display_ranges([
2364 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2365 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2366 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2367 ])
2368 });
2369
2370 view.newline(&Newline, cx);
2371 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2372 });
2373}
2374
2375#[gpui::test]
2376fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2377 init_test(cx, |_| {});
2378
2379 let editor = cx.add_window(|cx| {
2380 let buffer = MultiBuffer::build_simple(
2381 "
2382 a
2383 b(
2384 X
2385 )
2386 c(
2387 X
2388 )
2389 "
2390 .unindent()
2391 .as_str(),
2392 cx,
2393 );
2394 let mut editor = build_editor(buffer.clone(), cx);
2395 editor.change_selections(None, cx, |s| {
2396 s.select_ranges([
2397 Point::new(2, 4)..Point::new(2, 5),
2398 Point::new(5, 4)..Point::new(5, 5),
2399 ])
2400 });
2401 editor
2402 });
2403
2404 _ = editor.update(cx, |editor, cx| {
2405 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2406 editor.buffer.update(cx, |buffer, cx| {
2407 buffer.edit(
2408 [
2409 (Point::new(1, 2)..Point::new(3, 0), ""),
2410 (Point::new(4, 2)..Point::new(6, 0), ""),
2411 ],
2412 None,
2413 cx,
2414 );
2415 assert_eq!(
2416 buffer.read(cx).text(),
2417 "
2418 a
2419 b()
2420 c()
2421 "
2422 .unindent()
2423 );
2424 });
2425 assert_eq!(
2426 editor.selections.ranges(cx),
2427 &[
2428 Point::new(1, 2)..Point::new(1, 2),
2429 Point::new(2, 2)..Point::new(2, 2),
2430 ],
2431 );
2432
2433 editor.newline(&Newline, cx);
2434 assert_eq!(
2435 editor.text(cx),
2436 "
2437 a
2438 b(
2439 )
2440 c(
2441 )
2442 "
2443 .unindent()
2444 );
2445
2446 // The selections are moved after the inserted newlines
2447 assert_eq!(
2448 editor.selections.ranges(cx),
2449 &[
2450 Point::new(2, 0)..Point::new(2, 0),
2451 Point::new(4, 0)..Point::new(4, 0),
2452 ],
2453 );
2454 });
2455}
2456
2457#[gpui::test]
2458async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2459 init_test(cx, |settings| {
2460 settings.defaults.tab_size = NonZeroU32::new(4)
2461 });
2462
2463 let language = Arc::new(
2464 Language::new(
2465 LanguageConfig::default(),
2466 Some(tree_sitter_rust::LANGUAGE.into()),
2467 )
2468 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2469 .unwrap(),
2470 );
2471
2472 let mut cx = EditorTestContext::new(cx).await;
2473 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2474 cx.set_state(indoc! {"
2475 const a: ˇA = (
2476 (ˇ
2477 «const_functionˇ»(ˇ),
2478 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2479 )ˇ
2480 ˇ);ˇ
2481 "});
2482
2483 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2484 cx.assert_editor_state(indoc! {"
2485 ˇ
2486 const a: A = (
2487 ˇ
2488 (
2489 ˇ
2490 ˇ
2491 const_function(),
2492 ˇ
2493 ˇ
2494 ˇ
2495 ˇ
2496 something_else,
2497 ˇ
2498 )
2499 ˇ
2500 ˇ
2501 );
2502 "});
2503}
2504
2505#[gpui::test]
2506async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2507 init_test(cx, |settings| {
2508 settings.defaults.tab_size = NonZeroU32::new(4)
2509 });
2510
2511 let language = Arc::new(
2512 Language::new(
2513 LanguageConfig::default(),
2514 Some(tree_sitter_rust::LANGUAGE.into()),
2515 )
2516 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2517 .unwrap(),
2518 );
2519
2520 let mut cx = EditorTestContext::new(cx).await;
2521 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2522 cx.set_state(indoc! {"
2523 const a: ˇA = (
2524 (ˇ
2525 «const_functionˇ»(ˇ),
2526 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2527 )ˇ
2528 ˇ);ˇ
2529 "});
2530
2531 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2532 cx.assert_editor_state(indoc! {"
2533 const a: A = (
2534 ˇ
2535 (
2536 ˇ
2537 const_function(),
2538 ˇ
2539 ˇ
2540 something_else,
2541 ˇ
2542 ˇ
2543 ˇ
2544 ˇ
2545 )
2546 ˇ
2547 );
2548 ˇ
2549 ˇ
2550 "});
2551}
2552
2553#[gpui::test]
2554async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2555 init_test(cx, |settings| {
2556 settings.defaults.tab_size = NonZeroU32::new(4)
2557 });
2558
2559 let language = Arc::new(Language::new(
2560 LanguageConfig {
2561 line_comments: vec!["//".into()],
2562 ..LanguageConfig::default()
2563 },
2564 None,
2565 ));
2566 {
2567 let mut cx = EditorTestContext::new(cx).await;
2568 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2569 cx.set_state(indoc! {"
2570 // Fooˇ
2571 "});
2572
2573 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2574 cx.assert_editor_state(indoc! {"
2575 // Foo
2576 //ˇ
2577 "});
2578 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2579 cx.set_state(indoc! {"
2580 ˇ// Foo
2581 "});
2582 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2583 cx.assert_editor_state(indoc! {"
2584
2585 ˇ// Foo
2586 "});
2587 }
2588 // Ensure that comment continuations can be disabled.
2589 update_test_language_settings(cx, |settings| {
2590 settings.defaults.extend_comment_on_newline = Some(false);
2591 });
2592 let mut cx = EditorTestContext::new(cx).await;
2593 cx.set_state(indoc! {"
2594 // Fooˇ
2595 "});
2596 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2597 cx.assert_editor_state(indoc! {"
2598 // Foo
2599 ˇ
2600 "});
2601}
2602
2603#[gpui::test]
2604fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2605 init_test(cx, |_| {});
2606
2607 let editor = cx.add_window(|cx| {
2608 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2609 let mut editor = build_editor(buffer.clone(), cx);
2610 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2611 editor
2612 });
2613
2614 _ = editor.update(cx, |editor, cx| {
2615 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2616 editor.buffer.update(cx, |buffer, cx| {
2617 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2618 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2619 });
2620 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2621
2622 editor.insert("Z", cx);
2623 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2624
2625 // The selections are moved after the inserted characters
2626 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2627 });
2628}
2629
2630#[gpui::test]
2631async fn test_tab(cx: &mut gpui::TestAppContext) {
2632 init_test(cx, |settings| {
2633 settings.defaults.tab_size = NonZeroU32::new(3)
2634 });
2635
2636 let mut cx = EditorTestContext::new(cx).await;
2637 cx.set_state(indoc! {"
2638 ˇabˇc
2639 ˇ🏀ˇ🏀ˇefg
2640 dˇ
2641 "});
2642 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2643 cx.assert_editor_state(indoc! {"
2644 ˇab ˇc
2645 ˇ🏀 ˇ🏀 ˇefg
2646 d ˇ
2647 "});
2648
2649 cx.set_state(indoc! {"
2650 a
2651 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 a
2656 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2657 "});
2658}
2659
2660#[gpui::test]
2661async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2662 init_test(cx, |_| {});
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 let language = Arc::new(
2666 Language::new(
2667 LanguageConfig::default(),
2668 Some(tree_sitter_rust::LANGUAGE.into()),
2669 )
2670 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2671 .unwrap(),
2672 );
2673 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2674
2675 // cursors that are already at the suggested indent level insert
2676 // a soft tab. cursors that are to the left of the suggested indent
2677 // auto-indent their line.
2678 cx.set_state(indoc! {"
2679 ˇ
2680 const a: B = (
2681 c(
2682 d(
2683 ˇ
2684 )
2685 ˇ
2686 ˇ )
2687 );
2688 "});
2689 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2690 cx.assert_editor_state(indoc! {"
2691 ˇ
2692 const a: B = (
2693 c(
2694 d(
2695 ˇ
2696 )
2697 ˇ
2698 ˇ)
2699 );
2700 "});
2701
2702 // handle auto-indent when there are multiple cursors on the same line
2703 cx.set_state(indoc! {"
2704 const a: B = (
2705 c(
2706 ˇ ˇ
2707 ˇ )
2708 );
2709 "});
2710 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2711 cx.assert_editor_state(indoc! {"
2712 const a: B = (
2713 c(
2714 ˇ
2715 ˇ)
2716 );
2717 "});
2718}
2719
2720#[gpui::test]
2721async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2722 init_test(cx, |settings| {
2723 settings.defaults.tab_size = NonZeroU32::new(4)
2724 });
2725
2726 let language = Arc::new(
2727 Language::new(
2728 LanguageConfig::default(),
2729 Some(tree_sitter_rust::LANGUAGE.into()),
2730 )
2731 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2732 .unwrap(),
2733 );
2734
2735 let mut cx = EditorTestContext::new(cx).await;
2736 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2737 cx.set_state(indoc! {"
2738 fn a() {
2739 if b {
2740 \t ˇc
2741 }
2742 }
2743 "});
2744
2745 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2746 cx.assert_editor_state(indoc! {"
2747 fn a() {
2748 if b {
2749 ˇc
2750 }
2751 }
2752 "});
2753}
2754
2755#[gpui::test]
2756async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2757 init_test(cx, |settings| {
2758 settings.defaults.tab_size = NonZeroU32::new(4);
2759 });
2760
2761 let mut cx = EditorTestContext::new(cx).await;
2762
2763 cx.set_state(indoc! {"
2764 «oneˇ» «twoˇ»
2765 three
2766 four
2767 "});
2768 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2769 cx.assert_editor_state(indoc! {"
2770 «oneˇ» «twoˇ»
2771 three
2772 four
2773 "});
2774
2775 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2776 cx.assert_editor_state(indoc! {"
2777 «oneˇ» «twoˇ»
2778 three
2779 four
2780 "});
2781
2782 // select across line ending
2783 cx.set_state(indoc! {"
2784 one two
2785 t«hree
2786 ˇ» four
2787 "});
2788 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2789 cx.assert_editor_state(indoc! {"
2790 one two
2791 t«hree
2792 ˇ» four
2793 "});
2794
2795 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2796 cx.assert_editor_state(indoc! {"
2797 one two
2798 t«hree
2799 ˇ» four
2800 "});
2801
2802 // Ensure that indenting/outdenting works when the cursor is at column 0.
2803 cx.set_state(indoc! {"
2804 one two
2805 ˇthree
2806 four
2807 "});
2808 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2809 cx.assert_editor_state(indoc! {"
2810 one two
2811 ˇthree
2812 four
2813 "});
2814
2815 cx.set_state(indoc! {"
2816 one two
2817 ˇ three
2818 four
2819 "});
2820 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2821 cx.assert_editor_state(indoc! {"
2822 one two
2823 ˇthree
2824 four
2825 "});
2826}
2827
2828#[gpui::test]
2829async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2830 init_test(cx, |settings| {
2831 settings.defaults.hard_tabs = Some(true);
2832 });
2833
2834 let mut cx = EditorTestContext::new(cx).await;
2835
2836 // select two ranges on one line
2837 cx.set_state(indoc! {"
2838 «oneˇ» «twoˇ»
2839 three
2840 four
2841 "});
2842 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2843 cx.assert_editor_state(indoc! {"
2844 \t«oneˇ» «twoˇ»
2845 three
2846 four
2847 "});
2848 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2849 cx.assert_editor_state(indoc! {"
2850 \t\t«oneˇ» «twoˇ»
2851 three
2852 four
2853 "});
2854 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2855 cx.assert_editor_state(indoc! {"
2856 \t«oneˇ» «twoˇ»
2857 three
2858 four
2859 "});
2860 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2861 cx.assert_editor_state(indoc! {"
2862 «oneˇ» «twoˇ»
2863 three
2864 four
2865 "});
2866
2867 // select across a line ending
2868 cx.set_state(indoc! {"
2869 one two
2870 t«hree
2871 ˇ»four
2872 "});
2873 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2874 cx.assert_editor_state(indoc! {"
2875 one two
2876 \tt«hree
2877 ˇ»four
2878 "});
2879 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2880 cx.assert_editor_state(indoc! {"
2881 one two
2882 \t\tt«hree
2883 ˇ»four
2884 "});
2885 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2886 cx.assert_editor_state(indoc! {"
2887 one two
2888 \tt«hree
2889 ˇ»four
2890 "});
2891 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2892 cx.assert_editor_state(indoc! {"
2893 one two
2894 t«hree
2895 ˇ»four
2896 "});
2897
2898 // Ensure that indenting/outdenting works when the cursor is at column 0.
2899 cx.set_state(indoc! {"
2900 one two
2901 ˇthree
2902 four
2903 "});
2904 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2905 cx.assert_editor_state(indoc! {"
2906 one two
2907 ˇthree
2908 four
2909 "});
2910 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2911 cx.assert_editor_state(indoc! {"
2912 one two
2913 \tˇthree
2914 four
2915 "});
2916 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2917 cx.assert_editor_state(indoc! {"
2918 one two
2919 ˇthree
2920 four
2921 "});
2922}
2923
2924#[gpui::test]
2925fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2926 init_test(cx, |settings| {
2927 settings.languages.extend([
2928 (
2929 "TOML".into(),
2930 LanguageSettingsContent {
2931 tab_size: NonZeroU32::new(2),
2932 ..Default::default()
2933 },
2934 ),
2935 (
2936 "Rust".into(),
2937 LanguageSettingsContent {
2938 tab_size: NonZeroU32::new(4),
2939 ..Default::default()
2940 },
2941 ),
2942 ]);
2943 });
2944
2945 let toml_language = Arc::new(Language::new(
2946 LanguageConfig {
2947 name: "TOML".into(),
2948 ..Default::default()
2949 },
2950 None,
2951 ));
2952 let rust_language = Arc::new(Language::new(
2953 LanguageConfig {
2954 name: "Rust".into(),
2955 ..Default::default()
2956 },
2957 None,
2958 ));
2959
2960 let toml_buffer =
2961 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2962 let rust_buffer = cx.new_model(|cx| {
2963 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2964 });
2965 let multibuffer = cx.new_model(|cx| {
2966 let mut multibuffer = MultiBuffer::new(ReadWrite);
2967 multibuffer.push_excerpts(
2968 toml_buffer.clone(),
2969 [ExcerptRange {
2970 context: Point::new(0, 0)..Point::new(2, 0),
2971 primary: None,
2972 }],
2973 cx,
2974 );
2975 multibuffer.push_excerpts(
2976 rust_buffer.clone(),
2977 [ExcerptRange {
2978 context: Point::new(0, 0)..Point::new(1, 0),
2979 primary: None,
2980 }],
2981 cx,
2982 );
2983 multibuffer
2984 });
2985
2986 cx.add_window(|cx| {
2987 let mut editor = build_editor(multibuffer, cx);
2988
2989 assert_eq!(
2990 editor.text(cx),
2991 indoc! {"
2992 a = 1
2993 b = 2
2994
2995 const c: usize = 3;
2996 "}
2997 );
2998
2999 select_ranges(
3000 &mut editor,
3001 indoc! {"
3002 «aˇ» = 1
3003 b = 2
3004
3005 «const c:ˇ» usize = 3;
3006 "},
3007 cx,
3008 );
3009
3010 editor.tab(&Tab, cx);
3011 assert_text_with_selections(
3012 &mut editor,
3013 indoc! {"
3014 «aˇ» = 1
3015 b = 2
3016
3017 «const c:ˇ» usize = 3;
3018 "},
3019 cx,
3020 );
3021 editor.tab_prev(&TabPrev, cx);
3022 assert_text_with_selections(
3023 &mut editor,
3024 indoc! {"
3025 «aˇ» = 1
3026 b = 2
3027
3028 «const c:ˇ» usize = 3;
3029 "},
3030 cx,
3031 );
3032
3033 editor
3034 });
3035}
3036
3037#[gpui::test]
3038async fn test_backspace(cx: &mut gpui::TestAppContext) {
3039 init_test(cx, |_| {});
3040
3041 let mut cx = EditorTestContext::new(cx).await;
3042
3043 // Basic backspace
3044 cx.set_state(indoc! {"
3045 onˇe two three
3046 fou«rˇ» five six
3047 seven «ˇeight nine
3048 »ten
3049 "});
3050 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3051 cx.assert_editor_state(indoc! {"
3052 oˇe two three
3053 fouˇ five six
3054 seven ˇten
3055 "});
3056
3057 // Test backspace inside and around indents
3058 cx.set_state(indoc! {"
3059 zero
3060 ˇone
3061 ˇtwo
3062 ˇ ˇ ˇ three
3063 ˇ ˇ four
3064 "});
3065 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3066 cx.assert_editor_state(indoc! {"
3067 zero
3068 ˇone
3069 ˇtwo
3070 ˇ threeˇ four
3071 "});
3072
3073 // Test backspace with line_mode set to true
3074 cx.update_editor(|e, _| e.selections.line_mode = true);
3075 cx.set_state(indoc! {"
3076 The ˇquick ˇbrown
3077 fox jumps over
3078 the lazy dog
3079 ˇThe qu«ick bˇ»rown"});
3080 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3081 cx.assert_editor_state(indoc! {"
3082 ˇfox jumps over
3083 the lazy dogˇ"});
3084}
3085
3086#[gpui::test]
3087async fn test_delete(cx: &mut gpui::TestAppContext) {
3088 init_test(cx, |_| {});
3089
3090 let mut cx = EditorTestContext::new(cx).await;
3091 cx.set_state(indoc! {"
3092 onˇe two three
3093 fou«rˇ» five six
3094 seven «ˇeight nine
3095 »ten
3096 "});
3097 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3098 cx.assert_editor_state(indoc! {"
3099 onˇ two three
3100 fouˇ five six
3101 seven ˇten
3102 "});
3103
3104 // Test backspace with line_mode set to true
3105 cx.update_editor(|e, _| e.selections.line_mode = true);
3106 cx.set_state(indoc! {"
3107 The ˇquick ˇbrown
3108 fox «ˇjum»ps over
3109 the lazy dog
3110 ˇThe qu«ick bˇ»rown"});
3111 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3112 cx.assert_editor_state("ˇthe lazy dogˇ");
3113}
3114
3115#[gpui::test]
3116fn test_delete_line(cx: &mut TestAppContext) {
3117 init_test(cx, |_| {});
3118
3119 let view = cx.add_window(|cx| {
3120 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3121 build_editor(buffer, cx)
3122 });
3123 _ = view.update(cx, |view, cx| {
3124 view.change_selections(None, cx, |s| {
3125 s.select_display_ranges([
3126 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3127 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3128 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3129 ])
3130 });
3131 view.delete_line(&DeleteLine, cx);
3132 assert_eq!(view.display_text(cx), "ghi");
3133 assert_eq!(
3134 view.selections.display_ranges(cx),
3135 vec![
3136 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3137 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3138 ]
3139 );
3140 });
3141
3142 let view = cx.add_window(|cx| {
3143 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3144 build_editor(buffer, cx)
3145 });
3146 _ = view.update(cx, |view, cx| {
3147 view.change_selections(None, cx, |s| {
3148 s.select_display_ranges([
3149 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3150 ])
3151 });
3152 view.delete_line(&DeleteLine, cx);
3153 assert_eq!(view.display_text(cx), "ghi\n");
3154 assert_eq!(
3155 view.selections.display_ranges(cx),
3156 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3157 );
3158 });
3159}
3160
3161#[gpui::test]
3162fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3163 init_test(cx, |_| {});
3164
3165 cx.add_window(|cx| {
3166 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3167 let mut editor = build_editor(buffer.clone(), cx);
3168 let buffer = buffer.read(cx).as_singleton().unwrap();
3169
3170 assert_eq!(
3171 editor.selections.ranges::<Point>(cx),
3172 &[Point::new(0, 0)..Point::new(0, 0)]
3173 );
3174
3175 // When on single line, replace newline at end by space
3176 editor.join_lines(&JoinLines, cx);
3177 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3178 assert_eq!(
3179 editor.selections.ranges::<Point>(cx),
3180 &[Point::new(0, 3)..Point::new(0, 3)]
3181 );
3182
3183 // When multiple lines are selected, remove newlines that are spanned by the selection
3184 editor.change_selections(None, cx, |s| {
3185 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3186 });
3187 editor.join_lines(&JoinLines, cx);
3188 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3189 assert_eq!(
3190 editor.selections.ranges::<Point>(cx),
3191 &[Point::new(0, 11)..Point::new(0, 11)]
3192 );
3193
3194 // Undo should be transactional
3195 editor.undo(&Undo, cx);
3196 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3197 assert_eq!(
3198 editor.selections.ranges::<Point>(cx),
3199 &[Point::new(0, 5)..Point::new(2, 2)]
3200 );
3201
3202 // When joining an empty line don't insert a space
3203 editor.change_selections(None, cx, |s| {
3204 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3205 });
3206 editor.join_lines(&JoinLines, cx);
3207 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3208 assert_eq!(
3209 editor.selections.ranges::<Point>(cx),
3210 [Point::new(2, 3)..Point::new(2, 3)]
3211 );
3212
3213 // We can remove trailing newlines
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3216 assert_eq!(
3217 editor.selections.ranges::<Point>(cx),
3218 [Point::new(2, 3)..Point::new(2, 3)]
3219 );
3220
3221 // We don't blow up on the last line
3222 editor.join_lines(&JoinLines, cx);
3223 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3224 assert_eq!(
3225 editor.selections.ranges::<Point>(cx),
3226 [Point::new(2, 3)..Point::new(2, 3)]
3227 );
3228
3229 // reset to test indentation
3230 editor.buffer.update(cx, |buffer, cx| {
3231 buffer.edit(
3232 [
3233 (Point::new(1, 0)..Point::new(1, 2), " "),
3234 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3235 ],
3236 None,
3237 cx,
3238 )
3239 });
3240
3241 // We remove any leading spaces
3242 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3243 editor.change_selections(None, cx, |s| {
3244 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3245 });
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3248
3249 // We don't insert a space for a line containing only spaces
3250 editor.join_lines(&JoinLines, cx);
3251 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3252
3253 // We ignore any leading tabs
3254 editor.join_lines(&JoinLines, cx);
3255 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3256
3257 editor
3258 });
3259}
3260
3261#[gpui::test]
3262fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3263 init_test(cx, |_| {});
3264
3265 cx.add_window(|cx| {
3266 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3267 let mut editor = build_editor(buffer.clone(), cx);
3268 let buffer = buffer.read(cx).as_singleton().unwrap();
3269
3270 editor.change_selections(None, cx, |s| {
3271 s.select_ranges([
3272 Point::new(0, 2)..Point::new(1, 1),
3273 Point::new(1, 2)..Point::new(1, 2),
3274 Point::new(3, 1)..Point::new(3, 2),
3275 ])
3276 });
3277
3278 editor.join_lines(&JoinLines, cx);
3279 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3280
3281 assert_eq!(
3282 editor.selections.ranges::<Point>(cx),
3283 [
3284 Point::new(0, 7)..Point::new(0, 7),
3285 Point::new(1, 3)..Point::new(1, 3)
3286 ]
3287 );
3288 editor
3289 });
3290}
3291
3292#[gpui::test]
3293async fn test_join_lines_with_git_diff_base(
3294 executor: BackgroundExecutor,
3295 cx: &mut gpui::TestAppContext,
3296) {
3297 init_test(cx, |_| {});
3298
3299 let mut cx = EditorTestContext::new(cx).await;
3300
3301 let diff_base = r#"
3302 Line 0
3303 Line 1
3304 Line 2
3305 Line 3
3306 "#
3307 .unindent();
3308
3309 cx.set_state(
3310 &r#"
3311 ˇLine 0
3312 Line 1
3313 Line 2
3314 Line 3
3315 "#
3316 .unindent(),
3317 );
3318
3319 cx.set_diff_base(&diff_base);
3320 executor.run_until_parked();
3321
3322 // Join lines
3323 cx.update_editor(|editor, cx| {
3324 editor.join_lines(&JoinLines, cx);
3325 });
3326 executor.run_until_parked();
3327
3328 cx.assert_editor_state(
3329 &r#"
3330 Line 0ˇ Line 1
3331 Line 2
3332 Line 3
3333 "#
3334 .unindent(),
3335 );
3336 // Join again
3337 cx.update_editor(|editor, cx| {
3338 editor.join_lines(&JoinLines, cx);
3339 });
3340 executor.run_until_parked();
3341
3342 cx.assert_editor_state(
3343 &r#"
3344 Line 0 Line 1ˇ Line 2
3345 Line 3
3346 "#
3347 .unindent(),
3348 );
3349}
3350
3351#[gpui::test]
3352async fn test_custom_newlines_cause_no_false_positive_diffs(
3353 executor: BackgroundExecutor,
3354 cx: &mut gpui::TestAppContext,
3355) {
3356 init_test(cx, |_| {});
3357 let mut cx = EditorTestContext::new(cx).await;
3358 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3359 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3360 executor.run_until_parked();
3361
3362 cx.update_editor(|editor, cx| {
3363 let snapshot = editor.snapshot(cx);
3364 assert_eq!(
3365 snapshot
3366 .diff_map
3367 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3368 .collect::<Vec<_>>(),
3369 Vec::new(),
3370 "Should not have any diffs for files with custom newlines"
3371 );
3372 });
3373}
3374
3375#[gpui::test]
3376async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3377 init_test(cx, |_| {});
3378
3379 let mut cx = EditorTestContext::new(cx).await;
3380
3381 // Test sort_lines_case_insensitive()
3382 cx.set_state(indoc! {"
3383 «z
3384 y
3385 x
3386 Z
3387 Y
3388 Xˇ»
3389 "});
3390 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3391 cx.assert_editor_state(indoc! {"
3392 «x
3393 X
3394 y
3395 Y
3396 z
3397 Zˇ»
3398 "});
3399
3400 // Test reverse_lines()
3401 cx.set_state(indoc! {"
3402 «5
3403 4
3404 3
3405 2
3406 1ˇ»
3407 "});
3408 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3409 cx.assert_editor_state(indoc! {"
3410 «1
3411 2
3412 3
3413 4
3414 5ˇ»
3415 "});
3416
3417 // Skip testing shuffle_line()
3418
3419 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3420 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3421
3422 // Don't manipulate when cursor is on single line, but expand the selection
3423 cx.set_state(indoc! {"
3424 ddˇdd
3425 ccc
3426 bb
3427 a
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «ddddˇ»
3432 ccc
3433 bb
3434 a
3435 "});
3436
3437 // Basic manipulate case
3438 // Start selection moves to column 0
3439 // End of selection shrinks to fit shorter line
3440 cx.set_state(indoc! {"
3441 dd«d
3442 ccc
3443 bb
3444 aaaaaˇ»
3445 "});
3446 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3447 cx.assert_editor_state(indoc! {"
3448 «aaaaa
3449 bb
3450 ccc
3451 dddˇ»
3452 "});
3453
3454 // Manipulate case with newlines
3455 cx.set_state(indoc! {"
3456 dd«d
3457 ccc
3458
3459 bb
3460 aaaaa
3461
3462 ˇ»
3463 "});
3464 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3465 cx.assert_editor_state(indoc! {"
3466 «
3467
3468 aaaaa
3469 bb
3470 ccc
3471 dddˇ»
3472
3473 "});
3474
3475 // Adding new line
3476 cx.set_state(indoc! {"
3477 aa«a
3478 bbˇ»b
3479 "});
3480 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3481 cx.assert_editor_state(indoc! {"
3482 «aaa
3483 bbb
3484 added_lineˇ»
3485 "});
3486
3487 // Removing line
3488 cx.set_state(indoc! {"
3489 aa«a
3490 bbbˇ»
3491 "});
3492 cx.update_editor(|e, cx| {
3493 e.manipulate_lines(cx, |lines| {
3494 lines.pop();
3495 })
3496 });
3497 cx.assert_editor_state(indoc! {"
3498 «aaaˇ»
3499 "});
3500
3501 // Removing all lines
3502 cx.set_state(indoc! {"
3503 aa«a
3504 bbbˇ»
3505 "});
3506 cx.update_editor(|e, cx| {
3507 e.manipulate_lines(cx, |lines| {
3508 lines.drain(..);
3509 })
3510 });
3511 cx.assert_editor_state(indoc! {"
3512 ˇ
3513 "});
3514}
3515
3516#[gpui::test]
3517async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3518 init_test(cx, |_| {});
3519
3520 let mut cx = EditorTestContext::new(cx).await;
3521
3522 // Consider continuous selection as single selection
3523 cx.set_state(indoc! {"
3524 Aaa«aa
3525 cˇ»c«c
3526 bb
3527 aaaˇ»aa
3528 "});
3529 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3530 cx.assert_editor_state(indoc! {"
3531 «Aaaaa
3532 ccc
3533 bb
3534 aaaaaˇ»
3535 "});
3536
3537 cx.set_state(indoc! {"
3538 Aaa«aa
3539 cˇ»c«c
3540 bb
3541 aaaˇ»aa
3542 "});
3543 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3544 cx.assert_editor_state(indoc! {"
3545 «Aaaaa
3546 ccc
3547 bbˇ»
3548 "});
3549
3550 // Consider non continuous selection as distinct dedup operations
3551 cx.set_state(indoc! {"
3552 «aaaaa
3553 bb
3554 aaaaa
3555 aaaaaˇ»
3556
3557 aaa«aaˇ»
3558 "});
3559 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3560 cx.assert_editor_state(indoc! {"
3561 «aaaaa
3562 bbˇ»
3563
3564 «aaaaaˇ»
3565 "});
3566}
3567
3568#[gpui::test]
3569async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3570 init_test(cx, |_| {});
3571
3572 let mut cx = EditorTestContext::new(cx).await;
3573
3574 cx.set_state(indoc! {"
3575 «Aaa
3576 aAa
3577 Aaaˇ»
3578 "});
3579 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3580 cx.assert_editor_state(indoc! {"
3581 «Aaa
3582 aAaˇ»
3583 "});
3584
3585 cx.set_state(indoc! {"
3586 «Aaa
3587 aAa
3588 aaAˇ»
3589 "});
3590 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3591 cx.assert_editor_state(indoc! {"
3592 «Aaaˇ»
3593 "});
3594}
3595
3596#[gpui::test]
3597async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3598 init_test(cx, |_| {});
3599
3600 let mut cx = EditorTestContext::new(cx).await;
3601
3602 // Manipulate with multiple selections on a single line
3603 cx.set_state(indoc! {"
3604 dd«dd
3605 cˇ»c«c
3606 bb
3607 aaaˇ»aa
3608 "});
3609 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3610 cx.assert_editor_state(indoc! {"
3611 «aaaaa
3612 bb
3613 ccc
3614 ddddˇ»
3615 "});
3616
3617 // Manipulate with multiple disjoin selections
3618 cx.set_state(indoc! {"
3619 5«
3620 4
3621 3
3622 2
3623 1ˇ»
3624
3625 dd«dd
3626 ccc
3627 bb
3628 aaaˇ»aa
3629 "});
3630 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3631 cx.assert_editor_state(indoc! {"
3632 «1
3633 2
3634 3
3635 4
3636 5ˇ»
3637
3638 «aaaaa
3639 bb
3640 ccc
3641 ddddˇ»
3642 "});
3643
3644 // Adding lines on each selection
3645 cx.set_state(indoc! {"
3646 2«
3647 1ˇ»
3648
3649 bb«bb
3650 aaaˇ»aa
3651 "});
3652 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3653 cx.assert_editor_state(indoc! {"
3654 «2
3655 1
3656 added lineˇ»
3657
3658 «bbbb
3659 aaaaa
3660 added lineˇ»
3661 "});
3662
3663 // Removing lines on each selection
3664 cx.set_state(indoc! {"
3665 2«
3666 1ˇ»
3667
3668 bb«bb
3669 aaaˇ»aa
3670 "});
3671 cx.update_editor(|e, cx| {
3672 e.manipulate_lines(cx, |lines| {
3673 lines.pop();
3674 })
3675 });
3676 cx.assert_editor_state(indoc! {"
3677 «2ˇ»
3678
3679 «bbbbˇ»
3680 "});
3681}
3682
3683#[gpui::test]
3684async fn test_manipulate_text(cx: &mut TestAppContext) {
3685 init_test(cx, |_| {});
3686
3687 let mut cx = EditorTestContext::new(cx).await;
3688
3689 // Test convert_to_upper_case()
3690 cx.set_state(indoc! {"
3691 «hello worldˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «HELLO WORLDˇ»
3696 "});
3697
3698 // Test convert_to_lower_case()
3699 cx.set_state(indoc! {"
3700 «HELLO WORLDˇ»
3701 "});
3702 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3703 cx.assert_editor_state(indoc! {"
3704 «hello worldˇ»
3705 "});
3706
3707 // Test multiple line, single selection case
3708 cx.set_state(indoc! {"
3709 «The quick brown
3710 fox jumps over
3711 the lazy dogˇ»
3712 "});
3713 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3714 cx.assert_editor_state(indoc! {"
3715 «The Quick Brown
3716 Fox Jumps Over
3717 The Lazy Dogˇ»
3718 "});
3719
3720 // Test multiple line, single selection case
3721 cx.set_state(indoc! {"
3722 «The quick brown
3723 fox jumps over
3724 the lazy dogˇ»
3725 "});
3726 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3727 cx.assert_editor_state(indoc! {"
3728 «TheQuickBrown
3729 FoxJumpsOver
3730 TheLazyDogˇ»
3731 "});
3732
3733 // From here on out, test more complex cases of manipulate_text()
3734
3735 // Test no selection case - should affect words cursors are in
3736 // Cursor at beginning, middle, and end of word
3737 cx.set_state(indoc! {"
3738 ˇhello big beauˇtiful worldˇ
3739 "});
3740 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3741 cx.assert_editor_state(indoc! {"
3742 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3743 "});
3744
3745 // Test multiple selections on a single line and across multiple lines
3746 cx.set_state(indoc! {"
3747 «Theˇ» quick «brown
3748 foxˇ» jumps «overˇ»
3749 the «lazyˇ» dog
3750 "});
3751 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3752 cx.assert_editor_state(indoc! {"
3753 «THEˇ» quick «BROWN
3754 FOXˇ» jumps «OVERˇ»
3755 the «LAZYˇ» dog
3756 "});
3757
3758 // Test case where text length grows
3759 cx.set_state(indoc! {"
3760 «tschüߡ»
3761 "});
3762 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3763 cx.assert_editor_state(indoc! {"
3764 «TSCHÜSSˇ»
3765 "});
3766
3767 // Test to make sure we don't crash when text shrinks
3768 cx.set_state(indoc! {"
3769 aaa_bbbˇ
3770 "});
3771 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3772 cx.assert_editor_state(indoc! {"
3773 «aaaBbbˇ»
3774 "});
3775
3776 // Test to make sure we all aware of the fact that each word can grow and shrink
3777 // Final selections should be aware of this fact
3778 cx.set_state(indoc! {"
3779 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3780 "});
3781 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3782 cx.assert_editor_state(indoc! {"
3783 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3784 "});
3785
3786 cx.set_state(indoc! {"
3787 «hElLo, WoRld!ˇ»
3788 "});
3789 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3790 cx.assert_editor_state(indoc! {"
3791 «HeLlO, wOrLD!ˇ»
3792 "});
3793}
3794
3795#[gpui::test]
3796fn test_duplicate_line(cx: &mut TestAppContext) {
3797 init_test(cx, |_| {});
3798
3799 let view = cx.add_window(|cx| {
3800 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3801 build_editor(buffer, cx)
3802 });
3803 _ = view.update(cx, |view, cx| {
3804 view.change_selections(None, cx, |s| {
3805 s.select_display_ranges([
3806 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3807 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3808 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3809 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3810 ])
3811 });
3812 view.duplicate_line_down(&DuplicateLineDown, cx);
3813 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3814 assert_eq!(
3815 view.selections.display_ranges(cx),
3816 vec![
3817 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3818 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3819 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3820 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3821 ]
3822 );
3823 });
3824
3825 let view = cx.add_window(|cx| {
3826 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3827 build_editor(buffer, cx)
3828 });
3829 _ = view.update(cx, |view, cx| {
3830 view.change_selections(None, cx, |s| {
3831 s.select_display_ranges([
3832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3834 ])
3835 });
3836 view.duplicate_line_down(&DuplicateLineDown, cx);
3837 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3838 assert_eq!(
3839 view.selections.display_ranges(cx),
3840 vec![
3841 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3842 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3843 ]
3844 );
3845 });
3846
3847 // With `move_upwards` the selections stay in place, except for
3848 // the lines inserted above them
3849 let view = cx.add_window(|cx| {
3850 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3851 build_editor(buffer, cx)
3852 });
3853 _ = view.update(cx, |view, cx| {
3854 view.change_selections(None, cx, |s| {
3855 s.select_display_ranges([
3856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3857 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3859 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3860 ])
3861 });
3862 view.duplicate_line_up(&DuplicateLineUp, cx);
3863 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3864 assert_eq!(
3865 view.selections.display_ranges(cx),
3866 vec![
3867 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3868 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3869 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3870 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3871 ]
3872 );
3873 });
3874
3875 let view = cx.add_window(|cx| {
3876 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3877 build_editor(buffer, cx)
3878 });
3879 _ = view.update(cx, |view, cx| {
3880 view.change_selections(None, cx, |s| {
3881 s.select_display_ranges([
3882 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3883 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3884 ])
3885 });
3886 view.duplicate_line_up(&DuplicateLineUp, cx);
3887 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3888 assert_eq!(
3889 view.selections.display_ranges(cx),
3890 vec![
3891 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3892 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3893 ]
3894 );
3895 });
3896
3897 let view = cx.add_window(|cx| {
3898 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3899 build_editor(buffer, cx)
3900 });
3901 _ = view.update(cx, |view, cx| {
3902 view.change_selections(None, cx, |s| {
3903 s.select_display_ranges([
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3905 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3906 ])
3907 });
3908 view.duplicate_selection(&DuplicateSelection, cx);
3909 assert_eq!(view.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
3910 assert_eq!(
3911 view.selections.display_ranges(cx),
3912 vec![
3913 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3914 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
3915 ]
3916 );
3917 });
3918}
3919
3920#[gpui::test]
3921fn test_move_line_up_down(cx: &mut TestAppContext) {
3922 init_test(cx, |_| {});
3923
3924 let view = cx.add_window(|cx| {
3925 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3926 build_editor(buffer, cx)
3927 });
3928 _ = view.update(cx, |view, cx| {
3929 view.fold_creases(
3930 vec![
3931 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3932 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3933 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3934 ],
3935 true,
3936 cx,
3937 );
3938 view.change_selections(None, cx, |s| {
3939 s.select_display_ranges([
3940 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3941 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3942 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3943 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3944 ])
3945 });
3946 assert_eq!(
3947 view.display_text(cx),
3948 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950
3951 view.move_line_up(&MoveLineUp, cx);
3952 assert_eq!(
3953 view.display_text(cx),
3954 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3955 );
3956 assert_eq!(
3957 view.selections.display_ranges(cx),
3958 vec![
3959 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3960 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3961 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3962 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3963 ]
3964 );
3965 });
3966
3967 _ = view.update(cx, |view, cx| {
3968 view.move_line_down(&MoveLineDown, cx);
3969 assert_eq!(
3970 view.display_text(cx),
3971 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3972 );
3973 assert_eq!(
3974 view.selections.display_ranges(cx),
3975 vec![
3976 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3977 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3978 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3979 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3980 ]
3981 );
3982 });
3983
3984 _ = view.update(cx, |view, cx| {
3985 view.move_line_down(&MoveLineDown, cx);
3986 assert_eq!(
3987 view.display_text(cx),
3988 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3989 );
3990 assert_eq!(
3991 view.selections.display_ranges(cx),
3992 vec![
3993 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3994 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3995 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3996 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3997 ]
3998 );
3999 });
4000
4001 _ = view.update(cx, |view, cx| {
4002 view.move_line_up(&MoveLineUp, cx);
4003 assert_eq!(
4004 view.display_text(cx),
4005 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4006 );
4007 assert_eq!(
4008 view.selections.display_ranges(cx),
4009 vec![
4010 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4011 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4012 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4013 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4014 ]
4015 );
4016 });
4017}
4018
4019#[gpui::test]
4020fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4021 init_test(cx, |_| {});
4022
4023 let editor = cx.add_window(|cx| {
4024 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4025 build_editor(buffer, cx)
4026 });
4027 _ = editor.update(cx, |editor, cx| {
4028 let snapshot = editor.buffer.read(cx).snapshot(cx);
4029 editor.insert_blocks(
4030 [BlockProperties {
4031 style: BlockStyle::Fixed,
4032 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4033 height: 1,
4034 render: Arc::new(|_| div().into_any()),
4035 priority: 0,
4036 }],
4037 Some(Autoscroll::fit()),
4038 cx,
4039 );
4040 editor.change_selections(None, cx, |s| {
4041 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4042 });
4043 editor.move_line_down(&MoveLineDown, cx);
4044 });
4045}
4046
4047#[gpui::test]
4048async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4049 init_test(cx, |_| {});
4050
4051 let mut cx = EditorTestContext::new(cx).await;
4052 cx.set_state(
4053 &"
4054 ˇzero
4055 one
4056 two
4057 three
4058 four
4059 five
4060 "
4061 .unindent(),
4062 );
4063
4064 // Create a four-line block that replaces three lines of text.
4065 cx.update_editor(|editor, cx| {
4066 let snapshot = editor.snapshot(cx);
4067 let snapshot = &snapshot.buffer_snapshot;
4068 let placement = BlockPlacement::Replace(
4069 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4070 );
4071 editor.insert_blocks(
4072 [BlockProperties {
4073 placement,
4074 height: 4,
4075 style: BlockStyle::Sticky,
4076 render: Arc::new(|_| gpui::div().into_any_element()),
4077 priority: 0,
4078 }],
4079 None,
4080 cx,
4081 );
4082 });
4083
4084 // Move down so that the cursor touches the block.
4085 cx.update_editor(|editor, cx| {
4086 editor.move_down(&Default::default(), cx);
4087 });
4088 cx.assert_editor_state(
4089 &"
4090 zero
4091 «one
4092 two
4093 threeˇ»
4094 four
4095 five
4096 "
4097 .unindent(),
4098 );
4099
4100 // Move down past the block.
4101 cx.update_editor(|editor, cx| {
4102 editor.move_down(&Default::default(), cx);
4103 });
4104 cx.assert_editor_state(
4105 &"
4106 zero
4107 one
4108 two
4109 three
4110 ˇfour
4111 five
4112 "
4113 .unindent(),
4114 );
4115}
4116
4117#[gpui::test]
4118fn test_transpose(cx: &mut TestAppContext) {
4119 init_test(cx, |_| {});
4120
4121 _ = cx.add_window(|cx| {
4122 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4123 editor.set_style(EditorStyle::default(), cx);
4124 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bac");
4127 assert_eq!(editor.selections.ranges(cx), [2..2]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bca");
4131 assert_eq!(editor.selections.ranges(cx), [3..3]);
4132
4133 editor.transpose(&Default::default(), cx);
4134 assert_eq!(editor.text(cx), "bac");
4135 assert_eq!(editor.selections.ranges(cx), [3..3]);
4136
4137 editor
4138 });
4139
4140 _ = cx.add_window(|cx| {
4141 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4142 editor.set_style(EditorStyle::default(), cx);
4143 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "acb\nde");
4146 assert_eq!(editor.selections.ranges(cx), [3..3]);
4147
4148 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "acbd\ne");
4151 assert_eq!(editor.selections.ranges(cx), [5..5]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "acbde\n");
4155 assert_eq!(editor.selections.ranges(cx), [6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "acbd\ne");
4159 assert_eq!(editor.selections.ranges(cx), [6..6]);
4160
4161 editor
4162 });
4163
4164 _ = cx.add_window(|cx| {
4165 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4166 editor.set_style(EditorStyle::default(), cx);
4167 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4168 editor.transpose(&Default::default(), cx);
4169 assert_eq!(editor.text(cx), "bacd\ne");
4170 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4171
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "bcade\n");
4174 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "bcda\ne");
4178 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "bcade\n");
4182 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4183
4184 editor.transpose(&Default::default(), cx);
4185 assert_eq!(editor.text(cx), "bcaed\n");
4186 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4187
4188 editor
4189 });
4190
4191 _ = cx.add_window(|cx| {
4192 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4193 editor.set_style(EditorStyle::default(), cx);
4194 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4195 editor.transpose(&Default::default(), cx);
4196 assert_eq!(editor.text(cx), "🏀🍐✋");
4197 assert_eq!(editor.selections.ranges(cx), [8..8]);
4198
4199 editor.transpose(&Default::default(), cx);
4200 assert_eq!(editor.text(cx), "🏀✋🍐");
4201 assert_eq!(editor.selections.ranges(cx), [11..11]);
4202
4203 editor.transpose(&Default::default(), cx);
4204 assert_eq!(editor.text(cx), "🏀🍐✋");
4205 assert_eq!(editor.selections.ranges(cx), [11..11]);
4206
4207 editor
4208 });
4209}
4210
4211#[gpui::test]
4212async fn test_rewrap(cx: &mut TestAppContext) {
4213 init_test(cx, |_| {});
4214
4215 let mut cx = EditorTestContext::new(cx).await;
4216
4217 let language_with_c_comments = Arc::new(Language::new(
4218 LanguageConfig {
4219 line_comments: vec!["// ".into()],
4220 ..LanguageConfig::default()
4221 },
4222 None,
4223 ));
4224 let language_with_pound_comments = Arc::new(Language::new(
4225 LanguageConfig {
4226 line_comments: vec!["# ".into()],
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231 let markdown_language = Arc::new(Language::new(
4232 LanguageConfig {
4233 name: "Markdown".into(),
4234 ..LanguageConfig::default()
4235 },
4236 None,
4237 ));
4238 let language_with_doc_comments = Arc::new(Language::new(
4239 LanguageConfig {
4240 line_comments: vec!["// ".into(), "/// ".into()],
4241 ..LanguageConfig::default()
4242 },
4243 Some(tree_sitter_rust::LANGUAGE.into()),
4244 ));
4245
4246 let plaintext_language = Arc::new(Language::new(
4247 LanguageConfig {
4248 name: "Plain Text".into(),
4249 ..LanguageConfig::default()
4250 },
4251 None,
4252 ));
4253
4254 assert_rewrap(
4255 indoc! {"
4256 // ˇ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.
4257 "},
4258 indoc! {"
4259 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4260 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4261 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4262 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4263 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4264 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4265 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4266 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4267 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4268 // porttitor id. Aliquam id accumsan eros.
4269 "},
4270 language_with_c_comments.clone(),
4271 &mut cx,
4272 );
4273
4274 // Test that rewrapping works inside of a selection
4275 assert_rewrap(
4276 indoc! {"
4277 «// 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.ˇ»
4278 "},
4279 indoc! {"
4280 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4281 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4282 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4283 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4284 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4285 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4286 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4287 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4288 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4289 // porttitor id. Aliquam id accumsan eros.ˇ»
4290 "},
4291 language_with_c_comments.clone(),
4292 &mut cx,
4293 );
4294
4295 // Test that cursors that expand to the same region are collapsed.
4296 assert_rewrap(
4297 indoc! {"
4298 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4299 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4300 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4301 // ˇ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.
4302 "},
4303 indoc! {"
4304 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4305 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4306 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4307 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4308 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4309 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4310 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4311 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4312 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4313 // porttitor id. Aliquam id accumsan eros.
4314 "},
4315 language_with_c_comments.clone(),
4316 &mut cx,
4317 );
4318
4319 // Test that non-contiguous selections are treated separately.
4320 assert_rewrap(
4321 indoc! {"
4322 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4323 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4324 //
4325 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4326 // ˇ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.
4327 "},
4328 indoc! {"
4329 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4330 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4331 // auctor, eu lacinia sapien scelerisque.
4332 //
4333 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4334 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4335 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4336 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4337 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4338 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4339 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4340 "},
4341 language_with_c_comments.clone(),
4342 &mut cx,
4343 );
4344
4345 // Test that different comment prefixes are supported.
4346 assert_rewrap(
4347 indoc! {"
4348 # ˇ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.
4349 "},
4350 indoc! {"
4351 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4352 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4353 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4354 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4355 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4356 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4357 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4358 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4359 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4360 # accumsan eros.
4361 "},
4362 language_with_pound_comments.clone(),
4363 &mut cx,
4364 );
4365
4366 // Test that rewrapping is ignored outside of comments in most languages.
4367 assert_rewrap(
4368 indoc! {"
4369 /// Adds two numbers.
4370 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4371 fn add(a: u32, b: u32) -> u32 {
4372 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ˇ
4373 }
4374 "},
4375 indoc! {"
4376 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4377 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4378 fn add(a: u32, b: u32) -> u32 {
4379 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ˇ
4380 }
4381 "},
4382 language_with_doc_comments.clone(),
4383 &mut cx,
4384 );
4385
4386 // Test that rewrapping works in Markdown and Plain Text languages.
4387 assert_rewrap(
4388 indoc! {"
4389 # Hello
4390
4391 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.
4392 "},
4393 indoc! {"
4394 # Hello
4395
4396 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4397 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4398 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4399 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4400 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4401 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4402 Integer sit amet scelerisque nisi.
4403 "},
4404 markdown_language,
4405 &mut cx,
4406 );
4407
4408 assert_rewrap(
4409 indoc! {"
4410 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.
4411 "},
4412 indoc! {"
4413 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4414 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4415 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4416 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4417 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4418 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4419 Integer sit amet scelerisque nisi.
4420 "},
4421 plaintext_language,
4422 &mut cx,
4423 );
4424
4425 // Test rewrapping unaligned comments in a selection.
4426 assert_rewrap(
4427 indoc! {"
4428 fn foo() {
4429 if true {
4430 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4431 // Praesent semper egestas tellus id dignissim.ˇ»
4432 do_something();
4433 } else {
4434 //
4435 }
4436 }
4437 "},
4438 indoc! {"
4439 fn foo() {
4440 if true {
4441 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4442 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4443 // egestas tellus id dignissim.ˇ»
4444 do_something();
4445 } else {
4446 //
4447 }
4448 }
4449 "},
4450 language_with_doc_comments.clone(),
4451 &mut cx,
4452 );
4453
4454 assert_rewrap(
4455 indoc! {"
4456 fn foo() {
4457 if true {
4458 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4459 // Praesent semper egestas tellus id dignissim.»
4460 do_something();
4461 } else {
4462 //
4463 }
4464
4465 }
4466 "},
4467 indoc! {"
4468 fn foo() {
4469 if true {
4470 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4471 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4472 // egestas tellus id dignissim.»
4473 do_something();
4474 } else {
4475 //
4476 }
4477
4478 }
4479 "},
4480 language_with_doc_comments.clone(),
4481 &mut cx,
4482 );
4483
4484 #[track_caller]
4485 fn assert_rewrap(
4486 unwrapped_text: &str,
4487 wrapped_text: &str,
4488 language: Arc<Language>,
4489 cx: &mut EditorTestContext,
4490 ) {
4491 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4492 cx.set_state(unwrapped_text);
4493 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4494 cx.assert_editor_state(wrapped_text);
4495 }
4496}
4497
4498#[gpui::test]
4499async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4500 init_test(cx, |_| {});
4501
4502 let mut cx = EditorTestContext::new(cx).await;
4503
4504 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4505 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4506 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4507
4508 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4509 cx.set_state("two ˇfour ˇsix ˇ");
4510 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4511 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4512
4513 // Paste again but with only two cursors. Since the number of cursors doesn't
4514 // match the number of slices in the clipboard, the entire clipboard text
4515 // is pasted at each cursor.
4516 cx.set_state("ˇtwo one✅ four three six five ˇ");
4517 cx.update_editor(|e, cx| {
4518 e.handle_input("( ", cx);
4519 e.paste(&Paste, cx);
4520 e.handle_input(") ", cx);
4521 });
4522 cx.assert_editor_state(
4523 &([
4524 "( one✅ ",
4525 "three ",
4526 "five ) ˇtwo one✅ four three six five ( one✅ ",
4527 "three ",
4528 "five ) ˇ",
4529 ]
4530 .join("\n")),
4531 );
4532
4533 // Cut with three selections, one of which is full-line.
4534 cx.set_state(indoc! {"
4535 1«2ˇ»3
4536 4ˇ567
4537 «8ˇ»9"});
4538 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4539 cx.assert_editor_state(indoc! {"
4540 1ˇ3
4541 ˇ9"});
4542
4543 // Paste with three selections, noticing how the copied selection that was full-line
4544 // gets inserted before the second cursor.
4545 cx.set_state(indoc! {"
4546 1ˇ3
4547 9ˇ
4548 «oˇ»ne"});
4549 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4550 cx.assert_editor_state(indoc! {"
4551 12ˇ3
4552 4567
4553 9ˇ
4554 8ˇne"});
4555
4556 // Copy with a single cursor only, which writes the whole line into the clipboard.
4557 cx.set_state(indoc! {"
4558 The quick brown
4559 fox juˇmps over
4560 the lazy dog"});
4561 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4562 assert_eq!(
4563 cx.read_from_clipboard()
4564 .and_then(|item| item.text().as_deref().map(str::to_string)),
4565 Some("fox jumps over\n".to_string())
4566 );
4567
4568 // Paste with three selections, noticing how the copied full-line selection is inserted
4569 // before the empty selections but replaces the selection that is non-empty.
4570 cx.set_state(indoc! {"
4571 Tˇhe quick brown
4572 «foˇ»x jumps over
4573 tˇhe lazy dog"});
4574 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4575 cx.assert_editor_state(indoc! {"
4576 fox jumps over
4577 Tˇhe quick brown
4578 fox jumps over
4579 ˇx jumps over
4580 fox jumps over
4581 tˇhe lazy dog"});
4582}
4583
4584#[gpui::test]
4585async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4586 init_test(cx, |_| {});
4587
4588 let mut cx = EditorTestContext::new(cx).await;
4589 let language = Arc::new(Language::new(
4590 LanguageConfig::default(),
4591 Some(tree_sitter_rust::LANGUAGE.into()),
4592 ));
4593 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4594
4595 // Cut an indented block, without the leading whitespace.
4596 cx.set_state(indoc! {"
4597 const a: B = (
4598 c(),
4599 «d(
4600 e,
4601 f
4602 )ˇ»
4603 );
4604 "});
4605 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4606 cx.assert_editor_state(indoc! {"
4607 const a: B = (
4608 c(),
4609 ˇ
4610 );
4611 "});
4612
4613 // Paste it at the same position.
4614 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4615 cx.assert_editor_state(indoc! {"
4616 const a: B = (
4617 c(),
4618 d(
4619 e,
4620 f
4621 )ˇ
4622 );
4623 "});
4624
4625 // Paste it at a line with a lower indent level.
4626 cx.set_state(indoc! {"
4627 ˇ
4628 const a: B = (
4629 c(),
4630 );
4631 "});
4632 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4633 cx.assert_editor_state(indoc! {"
4634 d(
4635 e,
4636 f
4637 )ˇ
4638 const a: B = (
4639 c(),
4640 );
4641 "});
4642
4643 // Cut an indented block, with the leading whitespace.
4644 cx.set_state(indoc! {"
4645 const a: B = (
4646 c(),
4647 « d(
4648 e,
4649 f
4650 )
4651 ˇ»);
4652 "});
4653 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4654 cx.assert_editor_state(indoc! {"
4655 const a: B = (
4656 c(),
4657 ˇ);
4658 "});
4659
4660 // Paste it at the same position.
4661 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4662 cx.assert_editor_state(indoc! {"
4663 const a: B = (
4664 c(),
4665 d(
4666 e,
4667 f
4668 )
4669 ˇ);
4670 "});
4671
4672 // Paste it at a line with a higher indent level.
4673 cx.set_state(indoc! {"
4674 const a: B = (
4675 c(),
4676 d(
4677 e,
4678 fˇ
4679 )
4680 );
4681 "});
4682 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4683 cx.assert_editor_state(indoc! {"
4684 const a: B = (
4685 c(),
4686 d(
4687 e,
4688 f d(
4689 e,
4690 f
4691 )
4692 ˇ
4693 )
4694 );
4695 "});
4696}
4697
4698#[gpui::test]
4699fn test_select_all(cx: &mut TestAppContext) {
4700 init_test(cx, |_| {});
4701
4702 let view = cx.add_window(|cx| {
4703 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4704 build_editor(buffer, cx)
4705 });
4706 _ = view.update(cx, |view, cx| {
4707 view.select_all(&SelectAll, cx);
4708 assert_eq!(
4709 view.selections.display_ranges(cx),
4710 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4711 );
4712 });
4713}
4714
4715#[gpui::test]
4716fn test_select_line(cx: &mut TestAppContext) {
4717 init_test(cx, |_| {});
4718
4719 let view = cx.add_window(|cx| {
4720 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4721 build_editor(buffer, cx)
4722 });
4723 _ = view.update(cx, |view, cx| {
4724 view.change_selections(None, cx, |s| {
4725 s.select_display_ranges([
4726 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4727 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4728 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4729 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4730 ])
4731 });
4732 view.select_line(&SelectLine, cx);
4733 assert_eq!(
4734 view.selections.display_ranges(cx),
4735 vec![
4736 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4737 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4738 ]
4739 );
4740 });
4741
4742 _ = view.update(cx, |view, cx| {
4743 view.select_line(&SelectLine, cx);
4744 assert_eq!(
4745 view.selections.display_ranges(cx),
4746 vec![
4747 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4748 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4749 ]
4750 );
4751 });
4752
4753 _ = view.update(cx, |view, cx| {
4754 view.select_line(&SelectLine, cx);
4755 assert_eq!(
4756 view.selections.display_ranges(cx),
4757 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4758 );
4759 });
4760}
4761
4762#[gpui::test]
4763fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4764 init_test(cx, |_| {});
4765
4766 let view = cx.add_window(|cx| {
4767 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4768 build_editor(buffer, cx)
4769 });
4770 _ = view.update(cx, |view, cx| {
4771 view.fold_creases(
4772 vec![
4773 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4774 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4775 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4776 ],
4777 true,
4778 cx,
4779 );
4780 view.change_selections(None, cx, |s| {
4781 s.select_display_ranges([
4782 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4783 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4785 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4786 ])
4787 });
4788 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4789 });
4790
4791 _ = view.update(cx, |view, cx| {
4792 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4793 assert_eq!(
4794 view.display_text(cx),
4795 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4796 );
4797 assert_eq!(
4798 view.selections.display_ranges(cx),
4799 [
4800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4801 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4802 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4803 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4804 ]
4805 );
4806 });
4807
4808 _ = view.update(cx, |view, cx| {
4809 view.change_selections(None, cx, |s| {
4810 s.select_display_ranges([
4811 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4812 ])
4813 });
4814 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4815 assert_eq!(
4816 view.display_text(cx),
4817 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4818 );
4819 assert_eq!(
4820 view.selections.display_ranges(cx),
4821 [
4822 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4823 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4824 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4825 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4826 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4827 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4828 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4829 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4830 ]
4831 );
4832 });
4833}
4834
4835#[gpui::test]
4836async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4837 init_test(cx, |_| {});
4838
4839 let mut cx = EditorTestContext::new(cx).await;
4840
4841 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4842 cx.set_state(indoc!(
4843 r#"abc
4844 defˇghi
4845
4846 jk
4847 nlmo
4848 "#
4849 ));
4850
4851 cx.update_editor(|editor, cx| {
4852 editor.add_selection_above(&Default::default(), cx);
4853 });
4854
4855 cx.assert_editor_state(indoc!(
4856 r#"abcˇ
4857 defˇghi
4858
4859 jk
4860 nlmo
4861 "#
4862 ));
4863
4864 cx.update_editor(|editor, cx| {
4865 editor.add_selection_above(&Default::default(), cx);
4866 });
4867
4868 cx.assert_editor_state(indoc!(
4869 r#"abcˇ
4870 defˇghi
4871
4872 jk
4873 nlmo
4874 "#
4875 ));
4876
4877 cx.update_editor(|view, cx| {
4878 view.add_selection_below(&Default::default(), cx);
4879 });
4880
4881 cx.assert_editor_state(indoc!(
4882 r#"abc
4883 defˇghi
4884
4885 jk
4886 nlmo
4887 "#
4888 ));
4889
4890 cx.update_editor(|view, cx| {
4891 view.undo_selection(&Default::default(), cx);
4892 });
4893
4894 cx.assert_editor_state(indoc!(
4895 r#"abcˇ
4896 defˇghi
4897
4898 jk
4899 nlmo
4900 "#
4901 ));
4902
4903 cx.update_editor(|view, cx| {
4904 view.redo_selection(&Default::default(), cx);
4905 });
4906
4907 cx.assert_editor_state(indoc!(
4908 r#"abc
4909 defˇghi
4910
4911 jk
4912 nlmo
4913 "#
4914 ));
4915
4916 cx.update_editor(|view, cx| {
4917 view.add_selection_below(&Default::default(), cx);
4918 });
4919
4920 cx.assert_editor_state(indoc!(
4921 r#"abc
4922 defˇghi
4923
4924 jk
4925 nlmˇo
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ˇghi
4936
4937 jk
4938 nlmˇo
4939 "#
4940 ));
4941
4942 // change selections
4943 cx.set_state(indoc!(
4944 r#"abc
4945 def«ˇg»hi
4946
4947 jk
4948 nlmo
4949 "#
4950 ));
4951
4952 cx.update_editor(|view, cx| {
4953 view.add_selection_below(&Default::default(), cx);
4954 });
4955
4956 cx.assert_editor_state(indoc!(
4957 r#"abc
4958 def«ˇg»hi
4959
4960 jk
4961 nlm«ˇo»
4962 "#
4963 ));
4964
4965 cx.update_editor(|view, cx| {
4966 view.add_selection_below(&Default::default(), cx);
4967 });
4968
4969 cx.assert_editor_state(indoc!(
4970 r#"abc
4971 def«ˇg»hi
4972
4973 jk
4974 nlm«ˇo»
4975 "#
4976 ));
4977
4978 cx.update_editor(|view, cx| {
4979 view.add_selection_above(&Default::default(), cx);
4980 });
4981
4982 cx.assert_editor_state(indoc!(
4983 r#"abc
4984 def«ˇg»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_above(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"abc
4997 def«ˇg»hi
4998
4999 jk
5000 nlmo
5001 "#
5002 ));
5003
5004 // Change selections again
5005 cx.set_state(indoc!(
5006 r#"a«bc
5007 defgˇ»hi
5008
5009 jk
5010 nlmo
5011 "#
5012 ));
5013
5014 cx.update_editor(|view, cx| {
5015 view.add_selection_below(&Default::default(), cx);
5016 });
5017
5018 cx.assert_editor_state(indoc!(
5019 r#"a«bcˇ»
5020 d«efgˇ»hi
5021
5022 j«kˇ»
5023 nlmo
5024 "#
5025 ));
5026
5027 cx.update_editor(|view, cx| {
5028 view.add_selection_below(&Default::default(), cx);
5029 });
5030 cx.assert_editor_state(indoc!(
5031 r#"a«bcˇ»
5032 d«efgˇ»hi
5033
5034 j«kˇ»
5035 n«lmoˇ»
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«efgˇ»hi
5045
5046 j«kˇ»
5047 nlmo
5048 "#
5049 ));
5050
5051 // Change selections again
5052 cx.set_state(indoc!(
5053 r#"abc
5054 d«ˇefghi
5055
5056 jk
5057 nlm»o
5058 "#
5059 ));
5060
5061 cx.update_editor(|view, cx| {
5062 view.add_selection_above(&Default::default(), cx);
5063 });
5064
5065 cx.assert_editor_state(indoc!(
5066 r#"a«ˇbc»
5067 d«ˇef»ghi
5068
5069 j«ˇk»
5070 n«ˇlm»o
5071 "#
5072 ));
5073
5074 cx.update_editor(|view, cx| {
5075 view.add_selection_below(&Default::default(), cx);
5076 });
5077
5078 cx.assert_editor_state(indoc!(
5079 r#"abc
5080 d«ˇef»ghi
5081
5082 j«ˇk»
5083 n«ˇlm»o
5084 "#
5085 ));
5086}
5087
5088#[gpui::test]
5089async fn test_select_next(cx: &mut gpui::TestAppContext) {
5090 init_test(cx, |_| {});
5091
5092 let mut cx = EditorTestContext::new(cx).await;
5093 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5094
5095 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5096 .unwrap();
5097 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5098
5099 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5100 .unwrap();
5101 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5102
5103 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5104 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5105
5106 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5107 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5108
5109 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5110 .unwrap();
5111 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5112
5113 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5114 .unwrap();
5115 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5116}
5117
5118#[gpui::test]
5119async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5120 init_test(cx, |_| {});
5121
5122 let mut cx = EditorTestContext::new(cx).await;
5123 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5124
5125 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5126 .unwrap();
5127 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5128}
5129
5130#[gpui::test]
5131async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5132 init_test(cx, |_| {});
5133
5134 let mut cx = EditorTestContext::new(cx).await;
5135 cx.set_state(
5136 r#"let foo = 2;
5137lˇet foo = 2;
5138let fooˇ = 2;
5139let foo = 2;
5140let foo = ˇ2;"#,
5141 );
5142
5143 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5144 .unwrap();
5145 cx.assert_editor_state(
5146 r#"let foo = 2;
5147«letˇ» foo = 2;
5148let «fooˇ» = 2;
5149let foo = 2;
5150let foo = «2ˇ»;"#,
5151 );
5152
5153 // noop for multiple selections with different contents
5154 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5155 .unwrap();
5156 cx.assert_editor_state(
5157 r#"let foo = 2;
5158«letˇ» foo = 2;
5159let «fooˇ» = 2;
5160let foo = 2;
5161let foo = «2ˇ»;"#,
5162 );
5163}
5164
5165#[gpui::test]
5166async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5167 init_test(cx, |_| {});
5168
5169 let mut cx =
5170 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5171
5172 cx.assert_editor_state(indoc! {"
5173 ˇbbb
5174 ccc
5175
5176 bbb
5177 ccc
5178 "});
5179 cx.dispatch_action(SelectPrevious::default());
5180 cx.assert_editor_state(indoc! {"
5181 «bbbˇ»
5182 ccc
5183
5184 bbb
5185 ccc
5186 "});
5187 cx.dispatch_action(SelectPrevious::default());
5188 cx.assert_editor_state(indoc! {"
5189 «bbbˇ»
5190 ccc
5191
5192 «bbbˇ»
5193 ccc
5194 "});
5195}
5196
5197#[gpui::test]
5198async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5199 init_test(cx, |_| {});
5200
5201 let mut cx = EditorTestContext::new(cx).await;
5202 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5203
5204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5205 .unwrap();
5206 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5207
5208 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5209 .unwrap();
5210 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5211
5212 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5213 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5214
5215 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5216 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5217
5218 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5219 .unwrap();
5220 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5221
5222 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5223 .unwrap();
5224 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5225
5226 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5227 .unwrap();
5228 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5229}
5230
5231#[gpui::test]
5232async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5233 init_test(cx, |_| {});
5234
5235 let mut cx = EditorTestContext::new(cx).await;
5236 cx.set_state(
5237 r#"let foo = 2;
5238lˇet foo = 2;
5239let fooˇ = 2;
5240let foo = 2;
5241let foo = ˇ2;"#,
5242 );
5243
5244 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5245 .unwrap();
5246 cx.assert_editor_state(
5247 r#"let foo = 2;
5248«letˇ» foo = 2;
5249let «fooˇ» = 2;
5250let foo = 2;
5251let foo = «2ˇ»;"#,
5252 );
5253
5254 // noop for multiple selections with different contents
5255 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5256 .unwrap();
5257 cx.assert_editor_state(
5258 r#"let foo = 2;
5259«letˇ» foo = 2;
5260let «fooˇ» = 2;
5261let foo = 2;
5262let foo = «2ˇ»;"#,
5263 );
5264}
5265
5266#[gpui::test]
5267async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5268 init_test(cx, |_| {});
5269
5270 let mut cx = EditorTestContext::new(cx).await;
5271 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5272
5273 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5274 .unwrap();
5275 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5276
5277 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5278 .unwrap();
5279 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5280
5281 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5282 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5283
5284 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5285 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5286
5287 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5288 .unwrap();
5289 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5290
5291 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5292 .unwrap();
5293 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5294}
5295
5296#[gpui::test]
5297async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5298 init_test(cx, |_| {});
5299
5300 let language = Arc::new(Language::new(
5301 LanguageConfig::default(),
5302 Some(tree_sitter_rust::LANGUAGE.into()),
5303 ));
5304
5305 let text = r#"
5306 use mod1::mod2::{mod3, mod4};
5307
5308 fn fn_1(param1: bool, param2: &str) {
5309 let var1 = "text";
5310 }
5311 "#
5312 .unindent();
5313
5314 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5315 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5316 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5317
5318 editor
5319 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5320 .await;
5321
5322 editor.update(cx, |view, cx| {
5323 view.change_selections(None, cx, |s| {
5324 s.select_display_ranges([
5325 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5326 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5327 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5328 ]);
5329 });
5330 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5331 });
5332 editor.update(cx, |editor, cx| {
5333 assert_text_with_selections(
5334 editor,
5335 indoc! {r#"
5336 use mod1::mod2::{mod3, «mod4ˇ»};
5337
5338 fn fn_1«ˇ(param1: bool, param2: &str)» {
5339 let var1 = "«textˇ»";
5340 }
5341 "#},
5342 cx,
5343 );
5344 });
5345
5346 editor.update(cx, |view, cx| {
5347 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5348 });
5349 editor.update(cx, |editor, cx| {
5350 assert_text_with_selections(
5351 editor,
5352 indoc! {r#"
5353 use mod1::mod2::«{mod3, mod4}ˇ»;
5354
5355 «ˇfn fn_1(param1: bool, param2: &str) {
5356 let var1 = "text";
5357 }»
5358 "#},
5359 cx,
5360 );
5361 });
5362
5363 editor.update(cx, |view, cx| {
5364 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5365 });
5366 assert_eq!(
5367 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5368 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5369 );
5370
5371 // Trying to expand the selected syntax node one more time has no effect.
5372 editor.update(cx, |view, cx| {
5373 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5374 });
5375 assert_eq!(
5376 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5377 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5378 );
5379
5380 editor.update(cx, |view, cx| {
5381 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5382 });
5383 editor.update(cx, |editor, cx| {
5384 assert_text_with_selections(
5385 editor,
5386 indoc! {r#"
5387 use mod1::mod2::«{mod3, mod4}ˇ»;
5388
5389 «ˇfn fn_1(param1: bool, param2: &str) {
5390 let var1 = "text";
5391 }»
5392 "#},
5393 cx,
5394 );
5395 });
5396
5397 editor.update(cx, |view, cx| {
5398 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5399 });
5400 editor.update(cx, |editor, cx| {
5401 assert_text_with_selections(
5402 editor,
5403 indoc! {r#"
5404 use mod1::mod2::{mod3, «mod4ˇ»};
5405
5406 fn fn_1«ˇ(param1: bool, param2: &str)» {
5407 let var1 = "«textˇ»";
5408 }
5409 "#},
5410 cx,
5411 );
5412 });
5413
5414 editor.update(cx, |view, cx| {
5415 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5416 });
5417 editor.update(cx, |editor, cx| {
5418 assert_text_with_selections(
5419 editor,
5420 indoc! {r#"
5421 use mod1::mod2::{mod3, mo«ˇ»d4};
5422
5423 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5424 let var1 = "te«ˇ»xt";
5425 }
5426 "#},
5427 cx,
5428 );
5429 });
5430
5431 // Trying to shrink the selected syntax node one more time has no effect.
5432 editor.update(cx, |view, cx| {
5433 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5434 });
5435 editor.update(cx, |editor, cx| {
5436 assert_text_with_selections(
5437 editor,
5438 indoc! {r#"
5439 use mod1::mod2::{mod3, mo«ˇ»d4};
5440
5441 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5442 let var1 = "te«ˇ»xt";
5443 }
5444 "#},
5445 cx,
5446 );
5447 });
5448
5449 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5450 // a fold.
5451 editor.update(cx, |view, cx| {
5452 view.fold_creases(
5453 vec![
5454 Crease::simple(
5455 Point::new(0, 21)..Point::new(0, 24),
5456 FoldPlaceholder::test(),
5457 ),
5458 Crease::simple(
5459 Point::new(3, 20)..Point::new(3, 22),
5460 FoldPlaceholder::test(),
5461 ),
5462 ],
5463 true,
5464 cx,
5465 );
5466 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5467 });
5468 editor.update(cx, |editor, cx| {
5469 assert_text_with_selections(
5470 editor,
5471 indoc! {r#"
5472 use mod1::mod2::«{mod3, mod4}ˇ»;
5473
5474 fn fn_1«ˇ(param1: bool, param2: &str)» {
5475 «let var1 = "text";ˇ»
5476 }
5477 "#},
5478 cx,
5479 );
5480 });
5481}
5482
5483#[gpui::test]
5484async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5485 init_test(cx, |_| {});
5486
5487 let language = Arc::new(
5488 Language::new(
5489 LanguageConfig {
5490 brackets: BracketPairConfig {
5491 pairs: vec![
5492 BracketPair {
5493 start: "{".to_string(),
5494 end: "}".to_string(),
5495 close: false,
5496 surround: false,
5497 newline: true,
5498 },
5499 BracketPair {
5500 start: "(".to_string(),
5501 end: ")".to_string(),
5502 close: false,
5503 surround: false,
5504 newline: true,
5505 },
5506 ],
5507 ..Default::default()
5508 },
5509 ..Default::default()
5510 },
5511 Some(tree_sitter_rust::LANGUAGE.into()),
5512 )
5513 .with_indents_query(
5514 r#"
5515 (_ "(" ")" @end) @indent
5516 (_ "{" "}" @end) @indent
5517 "#,
5518 )
5519 .unwrap(),
5520 );
5521
5522 let text = "fn a() {}";
5523
5524 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5525 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5526 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5527 editor
5528 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5529 .await;
5530
5531 editor.update(cx, |editor, cx| {
5532 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5533 editor.newline(&Newline, cx);
5534 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5535 assert_eq!(
5536 editor.selections.ranges(cx),
5537 &[
5538 Point::new(1, 4)..Point::new(1, 4),
5539 Point::new(3, 4)..Point::new(3, 4),
5540 Point::new(5, 0)..Point::new(5, 0)
5541 ]
5542 );
5543 });
5544}
5545
5546#[gpui::test]
5547async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5548 init_test(cx, |_| {});
5549
5550 {
5551 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5552 cx.set_state(indoc! {"
5553 impl A {
5554
5555 fn b() {}
5556
5557 «fn c() {
5558
5559 }ˇ»
5560 }
5561 "});
5562
5563 cx.update_editor(|editor, cx| {
5564 editor.autoindent(&Default::default(), cx);
5565 });
5566
5567 cx.assert_editor_state(indoc! {"
5568 impl A {
5569
5570 fn b() {}
5571
5572 «fn c() {
5573
5574 }ˇ»
5575 }
5576 "});
5577 }
5578
5579 {
5580 let mut cx = EditorTestContext::new_multibuffer(
5581 cx,
5582 [indoc! { "
5583 impl A {
5584 «
5585 // a
5586 fn b(){}
5587 »
5588 «
5589 }
5590 fn c(){}
5591 »
5592 "}],
5593 );
5594
5595 let buffer = cx.update_editor(|editor, cx| {
5596 let buffer = editor.buffer().update(cx, |buffer, _| {
5597 buffer.all_buffers().iter().next().unwrap().clone()
5598 });
5599 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5600 buffer
5601 });
5602
5603 cx.run_until_parked();
5604 cx.update_editor(|editor, cx| {
5605 editor.select_all(&Default::default(), cx);
5606 editor.autoindent(&Default::default(), cx)
5607 });
5608 cx.run_until_parked();
5609
5610 cx.update(|cx| {
5611 pretty_assertions::assert_eq!(
5612 buffer.read(cx).text(),
5613 indoc! { "
5614 impl A {
5615
5616 // a
5617 fn b(){}
5618
5619
5620 }
5621 fn c(){}
5622
5623 " }
5624 )
5625 });
5626 }
5627}
5628
5629#[gpui::test]
5630async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5631 init_test(cx, |_| {});
5632
5633 let mut cx = EditorTestContext::new(cx).await;
5634
5635 let language = Arc::new(Language::new(
5636 LanguageConfig {
5637 brackets: BracketPairConfig {
5638 pairs: vec![
5639 BracketPair {
5640 start: "{".to_string(),
5641 end: "}".to_string(),
5642 close: true,
5643 surround: true,
5644 newline: true,
5645 },
5646 BracketPair {
5647 start: "(".to_string(),
5648 end: ")".to_string(),
5649 close: true,
5650 surround: true,
5651 newline: true,
5652 },
5653 BracketPair {
5654 start: "/*".to_string(),
5655 end: " */".to_string(),
5656 close: true,
5657 surround: true,
5658 newline: true,
5659 },
5660 BracketPair {
5661 start: "[".to_string(),
5662 end: "]".to_string(),
5663 close: false,
5664 surround: false,
5665 newline: true,
5666 },
5667 BracketPair {
5668 start: "\"".to_string(),
5669 end: "\"".to_string(),
5670 close: true,
5671 surround: true,
5672 newline: false,
5673 },
5674 BracketPair {
5675 start: "<".to_string(),
5676 end: ">".to_string(),
5677 close: false,
5678 surround: true,
5679 newline: true,
5680 },
5681 ],
5682 ..Default::default()
5683 },
5684 autoclose_before: "})]".to_string(),
5685 ..Default::default()
5686 },
5687 Some(tree_sitter_rust::LANGUAGE.into()),
5688 ));
5689
5690 cx.language_registry().add(language.clone());
5691 cx.update_buffer(|buffer, cx| {
5692 buffer.set_language(Some(language), cx);
5693 });
5694
5695 cx.set_state(
5696 &r#"
5697 🏀ˇ
5698 εˇ
5699 ❤️ˇ
5700 "#
5701 .unindent(),
5702 );
5703
5704 // autoclose multiple nested brackets at multiple cursors
5705 cx.update_editor(|view, cx| {
5706 view.handle_input("{", cx);
5707 view.handle_input("{", cx);
5708 view.handle_input("{", cx);
5709 });
5710 cx.assert_editor_state(
5711 &"
5712 🏀{{{ˇ}}}
5713 ε{{{ˇ}}}
5714 ❤️{{{ˇ}}}
5715 "
5716 .unindent(),
5717 );
5718
5719 // insert a different closing bracket
5720 cx.update_editor(|view, cx| {
5721 view.handle_input(")", cx);
5722 });
5723 cx.assert_editor_state(
5724 &"
5725 🏀{{{)ˇ}}}
5726 ε{{{)ˇ}}}
5727 ❤️{{{)ˇ}}}
5728 "
5729 .unindent(),
5730 );
5731
5732 // skip over the auto-closed brackets when typing a closing bracket
5733 cx.update_editor(|view, cx| {
5734 view.move_right(&MoveRight, cx);
5735 view.handle_input("}", cx);
5736 view.handle_input("}", cx);
5737 view.handle_input("}", cx);
5738 });
5739 cx.assert_editor_state(
5740 &"
5741 🏀{{{)}}}}ˇ
5742 ε{{{)}}}}ˇ
5743 ❤️{{{)}}}}ˇ
5744 "
5745 .unindent(),
5746 );
5747
5748 // autoclose multi-character pairs
5749 cx.set_state(
5750 &"
5751 ˇ
5752 ˇ
5753 "
5754 .unindent(),
5755 );
5756 cx.update_editor(|view, cx| {
5757 view.handle_input("/", cx);
5758 view.handle_input("*", cx);
5759 });
5760 cx.assert_editor_state(
5761 &"
5762 /*ˇ */
5763 /*ˇ */
5764 "
5765 .unindent(),
5766 );
5767
5768 // one cursor autocloses a multi-character pair, one cursor
5769 // does not autoclose.
5770 cx.set_state(
5771 &"
5772 /ˇ
5773 ˇ
5774 "
5775 .unindent(),
5776 );
5777 cx.update_editor(|view, cx| view.handle_input("*", cx));
5778 cx.assert_editor_state(
5779 &"
5780 /*ˇ */
5781 *ˇ
5782 "
5783 .unindent(),
5784 );
5785
5786 // Don't autoclose if the next character isn't whitespace and isn't
5787 // listed in the language's "autoclose_before" section.
5788 cx.set_state("ˇa b");
5789 cx.update_editor(|view, cx| view.handle_input("{", cx));
5790 cx.assert_editor_state("{ˇa b");
5791
5792 // Don't autoclose if `close` is false for the bracket pair
5793 cx.set_state("ˇ");
5794 cx.update_editor(|view, cx| view.handle_input("[", cx));
5795 cx.assert_editor_state("[ˇ");
5796
5797 // Surround with brackets if text is selected
5798 cx.set_state("«aˇ» b");
5799 cx.update_editor(|view, cx| view.handle_input("{", cx));
5800 cx.assert_editor_state("{«aˇ»} b");
5801
5802 // Autclose pair where the start and end characters are the same
5803 cx.set_state("aˇ");
5804 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5805 cx.assert_editor_state("a\"ˇ\"");
5806 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5807 cx.assert_editor_state("a\"\"ˇ");
5808
5809 // Don't autoclose pair if autoclose is disabled
5810 cx.set_state("ˇ");
5811 cx.update_editor(|view, cx| view.handle_input("<", cx));
5812 cx.assert_editor_state("<ˇ");
5813
5814 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5815 cx.set_state("«aˇ» b");
5816 cx.update_editor(|view, cx| view.handle_input("<", cx));
5817 cx.assert_editor_state("<«aˇ»> b");
5818}
5819
5820#[gpui::test]
5821async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5822 init_test(cx, |settings| {
5823 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5824 });
5825
5826 let mut cx = EditorTestContext::new(cx).await;
5827
5828 let language = Arc::new(Language::new(
5829 LanguageConfig {
5830 brackets: BracketPairConfig {
5831 pairs: vec![
5832 BracketPair {
5833 start: "{".to_string(),
5834 end: "}".to_string(),
5835 close: true,
5836 surround: true,
5837 newline: true,
5838 },
5839 BracketPair {
5840 start: "(".to_string(),
5841 end: ")".to_string(),
5842 close: true,
5843 surround: true,
5844 newline: true,
5845 },
5846 BracketPair {
5847 start: "[".to_string(),
5848 end: "]".to_string(),
5849 close: false,
5850 surround: false,
5851 newline: true,
5852 },
5853 ],
5854 ..Default::default()
5855 },
5856 autoclose_before: "})]".to_string(),
5857 ..Default::default()
5858 },
5859 Some(tree_sitter_rust::LANGUAGE.into()),
5860 ));
5861
5862 cx.language_registry().add(language.clone());
5863 cx.update_buffer(|buffer, cx| {
5864 buffer.set_language(Some(language), cx);
5865 });
5866
5867 cx.set_state(
5868 &"
5869 ˇ
5870 ˇ
5871 ˇ
5872 "
5873 .unindent(),
5874 );
5875
5876 // ensure only matching closing brackets are skipped over
5877 cx.update_editor(|view, cx| {
5878 view.handle_input("}", cx);
5879 view.move_left(&MoveLeft, cx);
5880 view.handle_input(")", cx);
5881 view.move_left(&MoveLeft, cx);
5882 });
5883 cx.assert_editor_state(
5884 &"
5885 ˇ)}
5886 ˇ)}
5887 ˇ)}
5888 "
5889 .unindent(),
5890 );
5891
5892 // skip-over closing brackets at multiple cursors
5893 cx.update_editor(|view, cx| {
5894 view.handle_input(")", cx);
5895 view.handle_input("}", cx);
5896 });
5897 cx.assert_editor_state(
5898 &"
5899 )}ˇ
5900 )}ˇ
5901 )}ˇ
5902 "
5903 .unindent(),
5904 );
5905
5906 // ignore non-close brackets
5907 cx.update_editor(|view, cx| {
5908 view.handle_input("]", cx);
5909 view.move_left(&MoveLeft, cx);
5910 view.handle_input("]", cx);
5911 });
5912 cx.assert_editor_state(
5913 &"
5914 )}]ˇ]
5915 )}]ˇ]
5916 )}]ˇ]
5917 "
5918 .unindent(),
5919 );
5920}
5921
5922#[gpui::test]
5923async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5924 init_test(cx, |_| {});
5925
5926 let mut cx = EditorTestContext::new(cx).await;
5927
5928 let html_language = Arc::new(
5929 Language::new(
5930 LanguageConfig {
5931 name: "HTML".into(),
5932 brackets: BracketPairConfig {
5933 pairs: vec![
5934 BracketPair {
5935 start: "<".into(),
5936 end: ">".into(),
5937 close: true,
5938 ..Default::default()
5939 },
5940 BracketPair {
5941 start: "{".into(),
5942 end: "}".into(),
5943 close: true,
5944 ..Default::default()
5945 },
5946 BracketPair {
5947 start: "(".into(),
5948 end: ")".into(),
5949 close: true,
5950 ..Default::default()
5951 },
5952 ],
5953 ..Default::default()
5954 },
5955 autoclose_before: "})]>".into(),
5956 ..Default::default()
5957 },
5958 Some(tree_sitter_html::language()),
5959 )
5960 .with_injection_query(
5961 r#"
5962 (script_element
5963 (raw_text) @injection.content
5964 (#set! injection.language "javascript"))
5965 "#,
5966 )
5967 .unwrap(),
5968 );
5969
5970 let javascript_language = Arc::new(Language::new(
5971 LanguageConfig {
5972 name: "JavaScript".into(),
5973 brackets: BracketPairConfig {
5974 pairs: vec![
5975 BracketPair {
5976 start: "/*".into(),
5977 end: " */".into(),
5978 close: true,
5979 ..Default::default()
5980 },
5981 BracketPair {
5982 start: "{".into(),
5983 end: "}".into(),
5984 close: true,
5985 ..Default::default()
5986 },
5987 BracketPair {
5988 start: "(".into(),
5989 end: ")".into(),
5990 close: true,
5991 ..Default::default()
5992 },
5993 ],
5994 ..Default::default()
5995 },
5996 autoclose_before: "})]>".into(),
5997 ..Default::default()
5998 },
5999 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6000 ));
6001
6002 cx.language_registry().add(html_language.clone());
6003 cx.language_registry().add(javascript_language.clone());
6004
6005 cx.update_buffer(|buffer, cx| {
6006 buffer.set_language(Some(html_language), cx);
6007 });
6008
6009 cx.set_state(
6010 &r#"
6011 <body>ˇ
6012 <script>
6013 var x = 1;ˇ
6014 </script>
6015 </body>ˇ
6016 "#
6017 .unindent(),
6018 );
6019
6020 // Precondition: different languages are active at different locations.
6021 cx.update_editor(|editor, cx| {
6022 let snapshot = editor.snapshot(cx);
6023 let cursors = editor.selections.ranges::<usize>(cx);
6024 let languages = cursors
6025 .iter()
6026 .map(|c| snapshot.language_at(c.start).unwrap().name())
6027 .collect::<Vec<_>>();
6028 assert_eq!(
6029 languages,
6030 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6031 );
6032 });
6033
6034 // Angle brackets autoclose in HTML, but not JavaScript.
6035 cx.update_editor(|editor, cx| {
6036 editor.handle_input("<", cx);
6037 editor.handle_input("a", cx);
6038 });
6039 cx.assert_editor_state(
6040 &r#"
6041 <body><aˇ>
6042 <script>
6043 var x = 1;<aˇ
6044 </script>
6045 </body><aˇ>
6046 "#
6047 .unindent(),
6048 );
6049
6050 // Curly braces and parens autoclose in both HTML and JavaScript.
6051 cx.update_editor(|editor, cx| {
6052 editor.handle_input(" b=", cx);
6053 editor.handle_input("{", cx);
6054 editor.handle_input("c", cx);
6055 editor.handle_input("(", cx);
6056 });
6057 cx.assert_editor_state(
6058 &r#"
6059 <body><a b={c(ˇ)}>
6060 <script>
6061 var x = 1;<a b={c(ˇ)}
6062 </script>
6063 </body><a b={c(ˇ)}>
6064 "#
6065 .unindent(),
6066 );
6067
6068 // Brackets that were already autoclosed are skipped.
6069 cx.update_editor(|editor, cx| {
6070 editor.handle_input(")", cx);
6071 editor.handle_input("d", cx);
6072 editor.handle_input("}", cx);
6073 });
6074 cx.assert_editor_state(
6075 &r#"
6076 <body><a b={c()d}ˇ>
6077 <script>
6078 var x = 1;<a b={c()d}ˇ
6079 </script>
6080 </body><a b={c()d}ˇ>
6081 "#
6082 .unindent(),
6083 );
6084 cx.update_editor(|editor, cx| {
6085 editor.handle_input(">", cx);
6086 });
6087 cx.assert_editor_state(
6088 &r#"
6089 <body><a b={c()d}>ˇ
6090 <script>
6091 var x = 1;<a b={c()d}>ˇ
6092 </script>
6093 </body><a b={c()d}>ˇ
6094 "#
6095 .unindent(),
6096 );
6097
6098 // Reset
6099 cx.set_state(
6100 &r#"
6101 <body>ˇ
6102 <script>
6103 var x = 1;ˇ
6104 </script>
6105 </body>ˇ
6106 "#
6107 .unindent(),
6108 );
6109
6110 cx.update_editor(|editor, cx| {
6111 editor.handle_input("<", cx);
6112 });
6113 cx.assert_editor_state(
6114 &r#"
6115 <body><ˇ>
6116 <script>
6117 var x = 1;<ˇ
6118 </script>
6119 </body><ˇ>
6120 "#
6121 .unindent(),
6122 );
6123
6124 // When backspacing, the closing angle brackets are removed.
6125 cx.update_editor(|editor, cx| {
6126 editor.backspace(&Backspace, cx);
6127 });
6128 cx.assert_editor_state(
6129 &r#"
6130 <body>ˇ
6131 <script>
6132 var x = 1;ˇ
6133 </script>
6134 </body>ˇ
6135 "#
6136 .unindent(),
6137 );
6138
6139 // Block comments autoclose in JavaScript, but not HTML.
6140 cx.update_editor(|editor, cx| {
6141 editor.handle_input("/", cx);
6142 editor.handle_input("*", cx);
6143 });
6144 cx.assert_editor_state(
6145 &r#"
6146 <body>/*ˇ
6147 <script>
6148 var x = 1;/*ˇ */
6149 </script>
6150 </body>/*ˇ
6151 "#
6152 .unindent(),
6153 );
6154}
6155
6156#[gpui::test]
6157async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6158 init_test(cx, |_| {});
6159
6160 let mut cx = EditorTestContext::new(cx).await;
6161
6162 let rust_language = Arc::new(
6163 Language::new(
6164 LanguageConfig {
6165 name: "Rust".into(),
6166 brackets: serde_json::from_value(json!([
6167 { "start": "{", "end": "}", "close": true, "newline": true },
6168 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6169 ]))
6170 .unwrap(),
6171 autoclose_before: "})]>".into(),
6172 ..Default::default()
6173 },
6174 Some(tree_sitter_rust::LANGUAGE.into()),
6175 )
6176 .with_override_query("(string_literal) @string")
6177 .unwrap(),
6178 );
6179
6180 cx.language_registry().add(rust_language.clone());
6181 cx.update_buffer(|buffer, cx| {
6182 buffer.set_language(Some(rust_language), cx);
6183 });
6184
6185 cx.set_state(
6186 &r#"
6187 let x = ˇ
6188 "#
6189 .unindent(),
6190 );
6191
6192 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6193 cx.update_editor(|editor, cx| {
6194 editor.handle_input("\"", cx);
6195 });
6196 cx.assert_editor_state(
6197 &r#"
6198 let x = "ˇ"
6199 "#
6200 .unindent(),
6201 );
6202
6203 // Inserting another quotation mark. The cursor moves across the existing
6204 // automatically-inserted quotation mark.
6205 cx.update_editor(|editor, cx| {
6206 editor.handle_input("\"", cx);
6207 });
6208 cx.assert_editor_state(
6209 &r#"
6210 let x = ""ˇ
6211 "#
6212 .unindent(),
6213 );
6214
6215 // Reset
6216 cx.set_state(
6217 &r#"
6218 let x = ˇ
6219 "#
6220 .unindent(),
6221 );
6222
6223 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6224 cx.update_editor(|editor, cx| {
6225 editor.handle_input("\"", cx);
6226 editor.handle_input(" ", cx);
6227 editor.move_left(&Default::default(), cx);
6228 editor.handle_input("\\", cx);
6229 editor.handle_input("\"", cx);
6230 });
6231 cx.assert_editor_state(
6232 &r#"
6233 let x = "\"ˇ "
6234 "#
6235 .unindent(),
6236 );
6237
6238 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6239 // mark. Nothing is inserted.
6240 cx.update_editor(|editor, cx| {
6241 editor.move_right(&Default::default(), cx);
6242 editor.handle_input("\"", cx);
6243 });
6244 cx.assert_editor_state(
6245 &r#"
6246 let x = "\" "ˇ
6247 "#
6248 .unindent(),
6249 );
6250}
6251
6252#[gpui::test]
6253async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6254 init_test(cx, |_| {});
6255
6256 let language = Arc::new(Language::new(
6257 LanguageConfig {
6258 brackets: BracketPairConfig {
6259 pairs: vec![
6260 BracketPair {
6261 start: "{".to_string(),
6262 end: "}".to_string(),
6263 close: true,
6264 surround: true,
6265 newline: true,
6266 },
6267 BracketPair {
6268 start: "/* ".to_string(),
6269 end: "*/".to_string(),
6270 close: true,
6271 surround: true,
6272 ..Default::default()
6273 },
6274 ],
6275 ..Default::default()
6276 },
6277 ..Default::default()
6278 },
6279 Some(tree_sitter_rust::LANGUAGE.into()),
6280 ));
6281
6282 let text = r#"
6283 a
6284 b
6285 c
6286 "#
6287 .unindent();
6288
6289 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6290 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6291 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6292 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6293 .await;
6294
6295 view.update(cx, |view, cx| {
6296 view.change_selections(None, cx, |s| {
6297 s.select_display_ranges([
6298 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6299 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6300 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6301 ])
6302 });
6303
6304 view.handle_input("{", cx);
6305 view.handle_input("{", cx);
6306 view.handle_input("{", cx);
6307 assert_eq!(
6308 view.text(cx),
6309 "
6310 {{{a}}}
6311 {{{b}}}
6312 {{{c}}}
6313 "
6314 .unindent()
6315 );
6316 assert_eq!(
6317 view.selections.display_ranges(cx),
6318 [
6319 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6320 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6321 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6322 ]
6323 );
6324
6325 view.undo(&Undo, cx);
6326 view.undo(&Undo, cx);
6327 view.undo(&Undo, cx);
6328 assert_eq!(
6329 view.text(cx),
6330 "
6331 a
6332 b
6333 c
6334 "
6335 .unindent()
6336 );
6337 assert_eq!(
6338 view.selections.display_ranges(cx),
6339 [
6340 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6341 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6342 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6343 ]
6344 );
6345
6346 // Ensure inserting the first character of a multi-byte bracket pair
6347 // doesn't surround the selections with the bracket.
6348 view.handle_input("/", cx);
6349 assert_eq!(
6350 view.text(cx),
6351 "
6352 /
6353 /
6354 /
6355 "
6356 .unindent()
6357 );
6358 assert_eq!(
6359 view.selections.display_ranges(cx),
6360 [
6361 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6362 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6363 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6364 ]
6365 );
6366
6367 view.undo(&Undo, cx);
6368 assert_eq!(
6369 view.text(cx),
6370 "
6371 a
6372 b
6373 c
6374 "
6375 .unindent()
6376 );
6377 assert_eq!(
6378 view.selections.display_ranges(cx),
6379 [
6380 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6381 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6382 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6383 ]
6384 );
6385
6386 // Ensure inserting the last character of a multi-byte bracket pair
6387 // doesn't surround the selections with the bracket.
6388 view.handle_input("*", cx);
6389 assert_eq!(
6390 view.text(cx),
6391 "
6392 *
6393 *
6394 *
6395 "
6396 .unindent()
6397 );
6398 assert_eq!(
6399 view.selections.display_ranges(cx),
6400 [
6401 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6402 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6403 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6404 ]
6405 );
6406 });
6407}
6408
6409#[gpui::test]
6410async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6411 init_test(cx, |_| {});
6412
6413 let language = Arc::new(Language::new(
6414 LanguageConfig {
6415 brackets: BracketPairConfig {
6416 pairs: vec![BracketPair {
6417 start: "{".to_string(),
6418 end: "}".to_string(),
6419 close: true,
6420 surround: true,
6421 newline: true,
6422 }],
6423 ..Default::default()
6424 },
6425 autoclose_before: "}".to_string(),
6426 ..Default::default()
6427 },
6428 Some(tree_sitter_rust::LANGUAGE.into()),
6429 ));
6430
6431 let text = r#"
6432 a
6433 b
6434 c
6435 "#
6436 .unindent();
6437
6438 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6439 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6440 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6441 editor
6442 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6443 .await;
6444
6445 editor.update(cx, |editor, cx| {
6446 editor.change_selections(None, cx, |s| {
6447 s.select_ranges([
6448 Point::new(0, 1)..Point::new(0, 1),
6449 Point::new(1, 1)..Point::new(1, 1),
6450 Point::new(2, 1)..Point::new(2, 1),
6451 ])
6452 });
6453
6454 editor.handle_input("{", cx);
6455 editor.handle_input("{", cx);
6456 editor.handle_input("_", cx);
6457 assert_eq!(
6458 editor.text(cx),
6459 "
6460 a{{_}}
6461 b{{_}}
6462 c{{_}}
6463 "
6464 .unindent()
6465 );
6466 assert_eq!(
6467 editor.selections.ranges::<Point>(cx),
6468 [
6469 Point::new(0, 4)..Point::new(0, 4),
6470 Point::new(1, 4)..Point::new(1, 4),
6471 Point::new(2, 4)..Point::new(2, 4)
6472 ]
6473 );
6474
6475 editor.backspace(&Default::default(), cx);
6476 editor.backspace(&Default::default(), cx);
6477 assert_eq!(
6478 editor.text(cx),
6479 "
6480 a{}
6481 b{}
6482 c{}
6483 "
6484 .unindent()
6485 );
6486 assert_eq!(
6487 editor.selections.ranges::<Point>(cx),
6488 [
6489 Point::new(0, 2)..Point::new(0, 2),
6490 Point::new(1, 2)..Point::new(1, 2),
6491 Point::new(2, 2)..Point::new(2, 2)
6492 ]
6493 );
6494
6495 editor.delete_to_previous_word_start(&Default::default(), cx);
6496 assert_eq!(
6497 editor.text(cx),
6498 "
6499 a
6500 b
6501 c
6502 "
6503 .unindent()
6504 );
6505 assert_eq!(
6506 editor.selections.ranges::<Point>(cx),
6507 [
6508 Point::new(0, 1)..Point::new(0, 1),
6509 Point::new(1, 1)..Point::new(1, 1),
6510 Point::new(2, 1)..Point::new(2, 1)
6511 ]
6512 );
6513 });
6514}
6515
6516#[gpui::test]
6517async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6518 init_test(cx, |settings| {
6519 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6520 });
6521
6522 let mut cx = EditorTestContext::new(cx).await;
6523
6524 let language = Arc::new(Language::new(
6525 LanguageConfig {
6526 brackets: BracketPairConfig {
6527 pairs: vec![
6528 BracketPair {
6529 start: "{".to_string(),
6530 end: "}".to_string(),
6531 close: true,
6532 surround: true,
6533 newline: true,
6534 },
6535 BracketPair {
6536 start: "(".to_string(),
6537 end: ")".to_string(),
6538 close: true,
6539 surround: true,
6540 newline: true,
6541 },
6542 BracketPair {
6543 start: "[".to_string(),
6544 end: "]".to_string(),
6545 close: false,
6546 surround: true,
6547 newline: true,
6548 },
6549 ],
6550 ..Default::default()
6551 },
6552 autoclose_before: "})]".to_string(),
6553 ..Default::default()
6554 },
6555 Some(tree_sitter_rust::LANGUAGE.into()),
6556 ));
6557
6558 cx.language_registry().add(language.clone());
6559 cx.update_buffer(|buffer, cx| {
6560 buffer.set_language(Some(language), cx);
6561 });
6562
6563 cx.set_state(
6564 &"
6565 {(ˇ)}
6566 [[ˇ]]
6567 {(ˇ)}
6568 "
6569 .unindent(),
6570 );
6571
6572 cx.update_editor(|view, cx| {
6573 view.backspace(&Default::default(), cx);
6574 view.backspace(&Default::default(), cx);
6575 });
6576
6577 cx.assert_editor_state(
6578 &"
6579 ˇ
6580 ˇ]]
6581 ˇ
6582 "
6583 .unindent(),
6584 );
6585
6586 cx.update_editor(|view, cx| {
6587 view.handle_input("{", cx);
6588 view.handle_input("{", cx);
6589 view.move_right(&MoveRight, cx);
6590 view.move_right(&MoveRight, cx);
6591 view.move_left(&MoveLeft, cx);
6592 view.move_left(&MoveLeft, cx);
6593 view.backspace(&Default::default(), cx);
6594 });
6595
6596 cx.assert_editor_state(
6597 &"
6598 {ˇ}
6599 {ˇ}]]
6600 {ˇ}
6601 "
6602 .unindent(),
6603 );
6604
6605 cx.update_editor(|view, cx| {
6606 view.backspace(&Default::default(), cx);
6607 });
6608
6609 cx.assert_editor_state(
6610 &"
6611 ˇ
6612 ˇ]]
6613 ˇ
6614 "
6615 .unindent(),
6616 );
6617}
6618
6619#[gpui::test]
6620async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6621 init_test(cx, |_| {});
6622
6623 let language = Arc::new(Language::new(
6624 LanguageConfig::default(),
6625 Some(tree_sitter_rust::LANGUAGE.into()),
6626 ));
6627
6628 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6629 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6630 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6631 editor
6632 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6633 .await;
6634
6635 editor.update(cx, |editor, cx| {
6636 editor.set_auto_replace_emoji_shortcode(true);
6637
6638 editor.handle_input("Hello ", cx);
6639 editor.handle_input(":wave", cx);
6640 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6641
6642 editor.handle_input(":", cx);
6643 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6644
6645 editor.handle_input(" :smile", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6647
6648 editor.handle_input(":", cx);
6649 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6650
6651 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6652 editor.handle_input(":wave", cx);
6653 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6654
6655 editor.handle_input(":", cx);
6656 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6657
6658 editor.handle_input(":1", cx);
6659 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6660
6661 editor.handle_input(":", cx);
6662 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6663
6664 // Ensure shortcode does not get replaced when it is part of a word
6665 editor.handle_input(" Test:wave", cx);
6666 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6667
6668 editor.handle_input(":", cx);
6669 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6670
6671 editor.set_auto_replace_emoji_shortcode(false);
6672
6673 // Ensure shortcode does not get replaced when auto replace is off
6674 editor.handle_input(" :wave", cx);
6675 assert_eq!(
6676 editor.text(cx),
6677 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6678 );
6679
6680 editor.handle_input(":", cx);
6681 assert_eq!(
6682 editor.text(cx),
6683 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6684 );
6685 });
6686}
6687
6688#[gpui::test]
6689async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6690 init_test(cx, |_| {});
6691
6692 let (text, insertion_ranges) = marked_text_ranges(
6693 indoc! {"
6694 ˇ
6695 "},
6696 false,
6697 );
6698
6699 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6700 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6701
6702 _ = editor.update(cx, |editor, cx| {
6703 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6704
6705 editor
6706 .insert_snippet(&insertion_ranges, snippet, cx)
6707 .unwrap();
6708
6709 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6710 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6711 assert_eq!(editor.text(cx), expected_text);
6712 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6713 }
6714
6715 assert(
6716 editor,
6717 cx,
6718 indoc! {"
6719 type «» =•
6720 "},
6721 );
6722
6723 assert!(editor.context_menu_visible(), "There should be a matches");
6724 });
6725}
6726
6727#[gpui::test]
6728async fn test_snippets(cx: &mut gpui::TestAppContext) {
6729 init_test(cx, |_| {});
6730
6731 let (text, insertion_ranges) = marked_text_ranges(
6732 indoc! {"
6733 a.ˇ b
6734 a.ˇ b
6735 a.ˇ b
6736 "},
6737 false,
6738 );
6739
6740 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6741 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6742
6743 editor.update(cx, |editor, cx| {
6744 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6745
6746 editor
6747 .insert_snippet(&insertion_ranges, snippet, cx)
6748 .unwrap();
6749
6750 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6751 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6752 assert_eq!(editor.text(cx), expected_text);
6753 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6754 }
6755
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 // Can't move earlier than the first tab stop
6767 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6768 assert(
6769 editor,
6770 cx,
6771 indoc! {"
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 a.f(«one», two, «three») b
6775 "},
6776 );
6777
6778 assert!(editor.move_to_next_snippet_tabstop(cx));
6779 assert(
6780 editor,
6781 cx,
6782 indoc! {"
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 a.f(one, «two», three) b
6786 "},
6787 );
6788
6789 editor.move_to_prev_snippet_tabstop(cx);
6790 assert(
6791 editor,
6792 cx,
6793 indoc! {"
6794 a.f(«one», two, «three») b
6795 a.f(«one», two, «three») b
6796 a.f(«one», two, «three») b
6797 "},
6798 );
6799
6800 assert!(editor.move_to_next_snippet_tabstop(cx));
6801 assert(
6802 editor,
6803 cx,
6804 indoc! {"
6805 a.f(one, «two», three) b
6806 a.f(one, «two», three) b
6807 a.f(one, «two», three) b
6808 "},
6809 );
6810 assert!(editor.move_to_next_snippet_tabstop(cx));
6811 assert(
6812 editor,
6813 cx,
6814 indoc! {"
6815 a.f(one, two, three)ˇ b
6816 a.f(one, two, three)ˇ b
6817 a.f(one, two, three)ˇ b
6818 "},
6819 );
6820
6821 // As soon as the last tab stop is reached, snippet state is gone
6822 editor.move_to_prev_snippet_tabstop(cx);
6823 assert(
6824 editor,
6825 cx,
6826 indoc! {"
6827 a.f(one, two, three)ˇ b
6828 a.f(one, two, three)ˇ b
6829 a.f(one, two, three)ˇ b
6830 "},
6831 );
6832 });
6833}
6834
6835#[gpui::test]
6836async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6837 init_test(cx, |_| {});
6838
6839 let fs = FakeFs::new(cx.executor());
6840 fs.insert_file("/file.rs", Default::default()).await;
6841
6842 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6843
6844 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6845 language_registry.add(rust_lang());
6846 let mut fake_servers = language_registry.register_fake_lsp(
6847 "Rust",
6848 FakeLspAdapter {
6849 capabilities: lsp::ServerCapabilities {
6850 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6851 ..Default::default()
6852 },
6853 ..Default::default()
6854 },
6855 );
6856
6857 let buffer = project
6858 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6859 .await
6860 .unwrap();
6861
6862 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6863 let (editor, cx) =
6864 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6865 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6866 assert!(cx.read(|cx| editor.is_dirty(cx)));
6867
6868 cx.executor().start_waiting();
6869 let fake_server = fake_servers.next().await.unwrap();
6870
6871 let save = editor
6872 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6873 .unwrap();
6874 fake_server
6875 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6876 assert_eq!(
6877 params.text_document.uri,
6878 lsp::Url::from_file_path("/file.rs").unwrap()
6879 );
6880 assert_eq!(params.options.tab_size, 4);
6881 Ok(Some(vec![lsp::TextEdit::new(
6882 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6883 ", ".to_string(),
6884 )]))
6885 })
6886 .next()
6887 .await;
6888 cx.executor().start_waiting();
6889 save.await;
6890
6891 assert_eq!(
6892 editor.update(cx, |editor, cx| editor.text(cx)),
6893 "one, two\nthree\n"
6894 );
6895 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6896
6897 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6898 assert!(cx.read(|cx| editor.is_dirty(cx)));
6899
6900 // Ensure we can still save even if formatting hangs.
6901 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6902 assert_eq!(
6903 params.text_document.uri,
6904 lsp::Url::from_file_path("/file.rs").unwrap()
6905 );
6906 futures::future::pending::<()>().await;
6907 unreachable!()
6908 });
6909 let save = editor
6910 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6911 .unwrap();
6912 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6913 cx.executor().start_waiting();
6914 save.await;
6915 assert_eq!(
6916 editor.update(cx, |editor, cx| editor.text(cx)),
6917 "one\ntwo\nthree\n"
6918 );
6919 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6920
6921 // For non-dirty buffer, no formatting request should be sent
6922 let save = editor
6923 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6924 .unwrap();
6925 let _pending_format_request = fake_server
6926 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6927 panic!("Should not be invoked on non-dirty buffer");
6928 })
6929 .next();
6930 cx.executor().start_waiting();
6931 save.await;
6932
6933 // Set rust language override and assert overridden tabsize is sent to language server
6934 update_test_language_settings(cx, |settings| {
6935 settings.languages.insert(
6936 "Rust".into(),
6937 LanguageSettingsContent {
6938 tab_size: NonZeroU32::new(8),
6939 ..Default::default()
6940 },
6941 );
6942 });
6943
6944 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6945 assert!(cx.read(|cx| editor.is_dirty(cx)));
6946 let save = editor
6947 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6948 .unwrap();
6949 fake_server
6950 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6951 assert_eq!(
6952 params.text_document.uri,
6953 lsp::Url::from_file_path("/file.rs").unwrap()
6954 );
6955 assert_eq!(params.options.tab_size, 8);
6956 Ok(Some(vec![]))
6957 })
6958 .next()
6959 .await;
6960 cx.executor().start_waiting();
6961 save.await;
6962}
6963
6964#[gpui::test]
6965async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6966 init_test(cx, |_| {});
6967
6968 let cols = 4;
6969 let rows = 10;
6970 let sample_text_1 = sample_text(rows, cols, 'a');
6971 assert_eq!(
6972 sample_text_1,
6973 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6974 );
6975 let sample_text_2 = sample_text(rows, cols, 'l');
6976 assert_eq!(
6977 sample_text_2,
6978 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6979 );
6980 let sample_text_3 = sample_text(rows, cols, 'v');
6981 assert_eq!(
6982 sample_text_3,
6983 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6984 );
6985
6986 let fs = FakeFs::new(cx.executor());
6987 fs.insert_tree(
6988 "/a",
6989 json!({
6990 "main.rs": sample_text_1,
6991 "other.rs": sample_text_2,
6992 "lib.rs": sample_text_3,
6993 }),
6994 )
6995 .await;
6996
6997 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6998 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6999 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7000
7001 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7002 language_registry.add(rust_lang());
7003 let mut fake_servers = language_registry.register_fake_lsp(
7004 "Rust",
7005 FakeLspAdapter {
7006 capabilities: lsp::ServerCapabilities {
7007 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7008 ..Default::default()
7009 },
7010 ..Default::default()
7011 },
7012 );
7013
7014 let worktree = project.update(cx, |project, cx| {
7015 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7016 assert_eq!(worktrees.len(), 1);
7017 worktrees.pop().unwrap()
7018 });
7019 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7020
7021 let buffer_1 = project
7022 .update(cx, |project, cx| {
7023 project.open_buffer((worktree_id, "main.rs"), cx)
7024 })
7025 .await
7026 .unwrap();
7027 let buffer_2 = project
7028 .update(cx, |project, cx| {
7029 project.open_buffer((worktree_id, "other.rs"), cx)
7030 })
7031 .await
7032 .unwrap();
7033 let buffer_3 = project
7034 .update(cx, |project, cx| {
7035 project.open_buffer((worktree_id, "lib.rs"), cx)
7036 })
7037 .await
7038 .unwrap();
7039
7040 let multi_buffer = cx.new_model(|cx| {
7041 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7042 multi_buffer.push_excerpts(
7043 buffer_1.clone(),
7044 [
7045 ExcerptRange {
7046 context: Point::new(0, 0)..Point::new(3, 0),
7047 primary: None,
7048 },
7049 ExcerptRange {
7050 context: Point::new(5, 0)..Point::new(7, 0),
7051 primary: None,
7052 },
7053 ExcerptRange {
7054 context: Point::new(9, 0)..Point::new(10, 4),
7055 primary: None,
7056 },
7057 ],
7058 cx,
7059 );
7060 multi_buffer.push_excerpts(
7061 buffer_2.clone(),
7062 [
7063 ExcerptRange {
7064 context: Point::new(0, 0)..Point::new(3, 0),
7065 primary: None,
7066 },
7067 ExcerptRange {
7068 context: Point::new(5, 0)..Point::new(7, 0),
7069 primary: None,
7070 },
7071 ExcerptRange {
7072 context: Point::new(9, 0)..Point::new(10, 4),
7073 primary: None,
7074 },
7075 ],
7076 cx,
7077 );
7078 multi_buffer.push_excerpts(
7079 buffer_3.clone(),
7080 [
7081 ExcerptRange {
7082 context: Point::new(0, 0)..Point::new(3, 0),
7083 primary: None,
7084 },
7085 ExcerptRange {
7086 context: Point::new(5, 0)..Point::new(7, 0),
7087 primary: None,
7088 },
7089 ExcerptRange {
7090 context: Point::new(9, 0)..Point::new(10, 4),
7091 primary: None,
7092 },
7093 ],
7094 cx,
7095 );
7096 multi_buffer
7097 });
7098 let multi_buffer_editor = cx.new_view(|cx| {
7099 Editor::new(
7100 EditorMode::Full,
7101 multi_buffer,
7102 Some(project.clone()),
7103 true,
7104 cx,
7105 )
7106 });
7107
7108 multi_buffer_editor.update(cx, |editor, cx| {
7109 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7110 editor.insert("|one|two|three|", cx);
7111 });
7112 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7113 multi_buffer_editor.update(cx, |editor, cx| {
7114 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7115 s.select_ranges(Some(60..70))
7116 });
7117 editor.insert("|four|five|six|", cx);
7118 });
7119 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7120
7121 // First two buffers should be edited, but not the third one.
7122 assert_eq!(
7123 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7124 "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}",
7125 );
7126 buffer_1.update(cx, |buffer, _| {
7127 assert!(buffer.is_dirty());
7128 assert_eq!(
7129 buffer.text(),
7130 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7131 )
7132 });
7133 buffer_2.update(cx, |buffer, _| {
7134 assert!(buffer.is_dirty());
7135 assert_eq!(
7136 buffer.text(),
7137 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7138 )
7139 });
7140 buffer_3.update(cx, |buffer, _| {
7141 assert!(!buffer.is_dirty());
7142 assert_eq!(buffer.text(), sample_text_3,)
7143 });
7144 cx.executor().run_until_parked();
7145
7146 cx.executor().start_waiting();
7147 let save = multi_buffer_editor
7148 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7149 .unwrap();
7150
7151 let fake_server = fake_servers.next().await.unwrap();
7152 fake_server
7153 .server
7154 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7155 Ok(Some(vec![lsp::TextEdit::new(
7156 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7157 format!("[{} formatted]", params.text_document.uri),
7158 )]))
7159 })
7160 .detach();
7161 save.await;
7162
7163 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7164 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7165 assert_eq!(
7166 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7167 "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}",
7168 );
7169 buffer_1.update(cx, |buffer, _| {
7170 assert!(!buffer.is_dirty());
7171 assert_eq!(
7172 buffer.text(),
7173 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7174 )
7175 });
7176 buffer_2.update(cx, |buffer, _| {
7177 assert!(!buffer.is_dirty());
7178 assert_eq!(
7179 buffer.text(),
7180 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7181 )
7182 });
7183 buffer_3.update(cx, |buffer, _| {
7184 assert!(!buffer.is_dirty());
7185 assert_eq!(buffer.text(), sample_text_3,)
7186 });
7187}
7188
7189#[gpui::test]
7190async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7191 init_test(cx, |_| {});
7192
7193 let fs = FakeFs::new(cx.executor());
7194 fs.insert_file("/file.rs", Default::default()).await;
7195
7196 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7197
7198 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7199 language_registry.add(rust_lang());
7200 let mut fake_servers = language_registry.register_fake_lsp(
7201 "Rust",
7202 FakeLspAdapter {
7203 capabilities: lsp::ServerCapabilities {
7204 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7205 ..Default::default()
7206 },
7207 ..Default::default()
7208 },
7209 );
7210
7211 let buffer = project
7212 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7213 .await
7214 .unwrap();
7215
7216 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7217 let (editor, cx) =
7218 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7219 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7220 assert!(cx.read(|cx| editor.is_dirty(cx)));
7221
7222 cx.executor().start_waiting();
7223 let fake_server = fake_servers.next().await.unwrap();
7224
7225 let save = editor
7226 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7227 .unwrap();
7228 fake_server
7229 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7230 assert_eq!(
7231 params.text_document.uri,
7232 lsp::Url::from_file_path("/file.rs").unwrap()
7233 );
7234 assert_eq!(params.options.tab_size, 4);
7235 Ok(Some(vec![lsp::TextEdit::new(
7236 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7237 ", ".to_string(),
7238 )]))
7239 })
7240 .next()
7241 .await;
7242 cx.executor().start_waiting();
7243 save.await;
7244 assert_eq!(
7245 editor.update(cx, |editor, cx| editor.text(cx)),
7246 "one, two\nthree\n"
7247 );
7248 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7249
7250 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7251 assert!(cx.read(|cx| editor.is_dirty(cx)));
7252
7253 // Ensure we can still save even if formatting hangs.
7254 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7255 move |params, _| async move {
7256 assert_eq!(
7257 params.text_document.uri,
7258 lsp::Url::from_file_path("/file.rs").unwrap()
7259 );
7260 futures::future::pending::<()>().await;
7261 unreachable!()
7262 },
7263 );
7264 let save = editor
7265 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7266 .unwrap();
7267 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7268 cx.executor().start_waiting();
7269 save.await;
7270 assert_eq!(
7271 editor.update(cx, |editor, cx| editor.text(cx)),
7272 "one\ntwo\nthree\n"
7273 );
7274 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7275
7276 // For non-dirty buffer, no formatting request should be sent
7277 let save = editor
7278 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7279 .unwrap();
7280 let _pending_format_request = fake_server
7281 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7282 panic!("Should not be invoked on non-dirty buffer");
7283 })
7284 .next();
7285 cx.executor().start_waiting();
7286 save.await;
7287
7288 // Set Rust language override and assert overridden tabsize is sent to language server
7289 update_test_language_settings(cx, |settings| {
7290 settings.languages.insert(
7291 "Rust".into(),
7292 LanguageSettingsContent {
7293 tab_size: NonZeroU32::new(8),
7294 ..Default::default()
7295 },
7296 );
7297 });
7298
7299 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7300 assert!(cx.read(|cx| editor.is_dirty(cx)));
7301 let save = editor
7302 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7303 .unwrap();
7304 fake_server
7305 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7306 assert_eq!(
7307 params.text_document.uri,
7308 lsp::Url::from_file_path("/file.rs").unwrap()
7309 );
7310 assert_eq!(params.options.tab_size, 8);
7311 Ok(Some(vec![]))
7312 })
7313 .next()
7314 .await;
7315 cx.executor().start_waiting();
7316 save.await;
7317}
7318
7319#[gpui::test]
7320async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7321 init_test(cx, |settings| {
7322 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7323 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7324 ))
7325 });
7326
7327 let fs = FakeFs::new(cx.executor());
7328 fs.insert_file("/file.rs", Default::default()).await;
7329
7330 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7331
7332 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7333 language_registry.add(Arc::new(Language::new(
7334 LanguageConfig {
7335 name: "Rust".into(),
7336 matcher: LanguageMatcher {
7337 path_suffixes: vec!["rs".to_string()],
7338 ..Default::default()
7339 },
7340 ..LanguageConfig::default()
7341 },
7342 Some(tree_sitter_rust::LANGUAGE.into()),
7343 )));
7344 update_test_language_settings(cx, |settings| {
7345 // Enable Prettier formatting for the same buffer, and ensure
7346 // LSP is called instead of Prettier.
7347 settings.defaults.prettier = Some(PrettierSettings {
7348 allowed: true,
7349 ..PrettierSettings::default()
7350 });
7351 });
7352 let mut fake_servers = language_registry.register_fake_lsp(
7353 "Rust",
7354 FakeLspAdapter {
7355 capabilities: lsp::ServerCapabilities {
7356 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7357 ..Default::default()
7358 },
7359 ..Default::default()
7360 },
7361 );
7362
7363 let buffer = project
7364 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7365 .await
7366 .unwrap();
7367
7368 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7369 let (editor, cx) =
7370 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7371 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7372
7373 cx.executor().start_waiting();
7374 let fake_server = fake_servers.next().await.unwrap();
7375
7376 let format = editor
7377 .update(cx, |editor, cx| {
7378 editor.perform_format(
7379 project.clone(),
7380 FormatTrigger::Manual,
7381 FormatTarget::Buffers,
7382 cx,
7383 )
7384 })
7385 .unwrap();
7386 fake_server
7387 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7388 assert_eq!(
7389 params.text_document.uri,
7390 lsp::Url::from_file_path("/file.rs").unwrap()
7391 );
7392 assert_eq!(params.options.tab_size, 4);
7393 Ok(Some(vec![lsp::TextEdit::new(
7394 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7395 ", ".to_string(),
7396 )]))
7397 })
7398 .next()
7399 .await;
7400 cx.executor().start_waiting();
7401 format.await;
7402 assert_eq!(
7403 editor.update(cx, |editor, cx| editor.text(cx)),
7404 "one, two\nthree\n"
7405 );
7406
7407 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7408 // Ensure we don't lock if formatting hangs.
7409 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7410 assert_eq!(
7411 params.text_document.uri,
7412 lsp::Url::from_file_path("/file.rs").unwrap()
7413 );
7414 futures::future::pending::<()>().await;
7415 unreachable!()
7416 });
7417 let format = editor
7418 .update(cx, |editor, cx| {
7419 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffers, cx)
7420 })
7421 .unwrap();
7422 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7423 cx.executor().start_waiting();
7424 format.await;
7425 assert_eq!(
7426 editor.update(cx, |editor, cx| editor.text(cx)),
7427 "one\ntwo\nthree\n"
7428 );
7429}
7430
7431#[gpui::test]
7432async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7433 init_test(cx, |_| {});
7434
7435 let mut cx = EditorLspTestContext::new_rust(
7436 lsp::ServerCapabilities {
7437 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7438 ..Default::default()
7439 },
7440 cx,
7441 )
7442 .await;
7443
7444 cx.set_state(indoc! {"
7445 one.twoˇ
7446 "});
7447
7448 // The format request takes a long time. When it completes, it inserts
7449 // a newline and an indent before the `.`
7450 cx.lsp
7451 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7452 let executor = cx.background_executor().clone();
7453 async move {
7454 executor.timer(Duration::from_millis(100)).await;
7455 Ok(Some(vec![lsp::TextEdit {
7456 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7457 new_text: "\n ".into(),
7458 }]))
7459 }
7460 });
7461
7462 // Submit a format request.
7463 let format_1 = cx
7464 .update_editor(|editor, cx| editor.format(&Format, cx))
7465 .unwrap();
7466 cx.executor().run_until_parked();
7467
7468 // Submit a second format request.
7469 let format_2 = cx
7470 .update_editor(|editor, cx| editor.format(&Format, cx))
7471 .unwrap();
7472 cx.executor().run_until_parked();
7473
7474 // Wait for both format requests to complete
7475 cx.executor().advance_clock(Duration::from_millis(200));
7476 cx.executor().start_waiting();
7477 format_1.await.unwrap();
7478 cx.executor().start_waiting();
7479 format_2.await.unwrap();
7480
7481 // The formatting edits only happens once.
7482 cx.assert_editor_state(indoc! {"
7483 one
7484 .twoˇ
7485 "});
7486}
7487
7488#[gpui::test]
7489async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7490 init_test(cx, |settings| {
7491 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7492 });
7493
7494 let mut cx = EditorLspTestContext::new_rust(
7495 lsp::ServerCapabilities {
7496 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7497 ..Default::default()
7498 },
7499 cx,
7500 )
7501 .await;
7502
7503 // Set up a buffer white some trailing whitespace and no trailing newline.
7504 cx.set_state(
7505 &[
7506 "one ", //
7507 "twoˇ", //
7508 "three ", //
7509 "four", //
7510 ]
7511 .join("\n"),
7512 );
7513
7514 // Submit a format request.
7515 let format = cx
7516 .update_editor(|editor, cx| editor.format(&Format, cx))
7517 .unwrap();
7518
7519 // Record which buffer changes have been sent to the language server
7520 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7521 cx.lsp
7522 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7523 let buffer_changes = buffer_changes.clone();
7524 move |params, _| {
7525 buffer_changes.lock().extend(
7526 params
7527 .content_changes
7528 .into_iter()
7529 .map(|e| (e.range.unwrap(), e.text)),
7530 );
7531 }
7532 });
7533
7534 // Handle formatting requests to the language server.
7535 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7536 let buffer_changes = buffer_changes.clone();
7537 move |_, _| {
7538 // When formatting is requested, trailing whitespace has already been stripped,
7539 // and the trailing newline has already been added.
7540 assert_eq!(
7541 &buffer_changes.lock()[1..],
7542 &[
7543 (
7544 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7545 "".into()
7546 ),
7547 (
7548 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7549 "".into()
7550 ),
7551 (
7552 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7553 "\n".into()
7554 ),
7555 ]
7556 );
7557
7558 // Insert blank lines between each line of the buffer.
7559 async move {
7560 Ok(Some(vec![
7561 lsp::TextEdit {
7562 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7563 new_text: "\n".into(),
7564 },
7565 lsp::TextEdit {
7566 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7567 new_text: "\n".into(),
7568 },
7569 ]))
7570 }
7571 }
7572 });
7573
7574 // After formatting the buffer, the trailing whitespace is stripped,
7575 // a newline is appended, and the edits provided by the language server
7576 // have been applied.
7577 format.await.unwrap();
7578 cx.assert_editor_state(
7579 &[
7580 "one", //
7581 "", //
7582 "twoˇ", //
7583 "", //
7584 "three", //
7585 "four", //
7586 "", //
7587 ]
7588 .join("\n"),
7589 );
7590
7591 // Undoing the formatting undoes the trailing whitespace removal, the
7592 // trailing newline, and the LSP edits.
7593 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7594 cx.assert_editor_state(
7595 &[
7596 "one ", //
7597 "twoˇ", //
7598 "three ", //
7599 "four", //
7600 ]
7601 .join("\n"),
7602 );
7603}
7604
7605#[gpui::test]
7606async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7607 cx: &mut gpui::TestAppContext,
7608) {
7609 init_test(cx, |_| {});
7610
7611 cx.update(|cx| {
7612 cx.update_global::<SettingsStore, _>(|settings, cx| {
7613 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7614 settings.auto_signature_help = Some(true);
7615 });
7616 });
7617 });
7618
7619 let mut cx = EditorLspTestContext::new_rust(
7620 lsp::ServerCapabilities {
7621 signature_help_provider: Some(lsp::SignatureHelpOptions {
7622 ..Default::default()
7623 }),
7624 ..Default::default()
7625 },
7626 cx,
7627 )
7628 .await;
7629
7630 let language = Language::new(
7631 LanguageConfig {
7632 name: "Rust".into(),
7633 brackets: BracketPairConfig {
7634 pairs: vec![
7635 BracketPair {
7636 start: "{".to_string(),
7637 end: "}".to_string(),
7638 close: true,
7639 surround: true,
7640 newline: true,
7641 },
7642 BracketPair {
7643 start: "(".to_string(),
7644 end: ")".to_string(),
7645 close: true,
7646 surround: true,
7647 newline: true,
7648 },
7649 BracketPair {
7650 start: "/*".to_string(),
7651 end: " */".to_string(),
7652 close: true,
7653 surround: true,
7654 newline: true,
7655 },
7656 BracketPair {
7657 start: "[".to_string(),
7658 end: "]".to_string(),
7659 close: false,
7660 surround: false,
7661 newline: true,
7662 },
7663 BracketPair {
7664 start: "\"".to_string(),
7665 end: "\"".to_string(),
7666 close: true,
7667 surround: true,
7668 newline: false,
7669 },
7670 BracketPair {
7671 start: "<".to_string(),
7672 end: ">".to_string(),
7673 close: false,
7674 surround: true,
7675 newline: true,
7676 },
7677 ],
7678 ..Default::default()
7679 },
7680 autoclose_before: "})]".to_string(),
7681 ..Default::default()
7682 },
7683 Some(tree_sitter_rust::LANGUAGE.into()),
7684 );
7685 let language = Arc::new(language);
7686
7687 cx.language_registry().add(language.clone());
7688 cx.update_buffer(|buffer, cx| {
7689 buffer.set_language(Some(language), cx);
7690 });
7691
7692 cx.set_state(
7693 &r#"
7694 fn main() {
7695 sampleˇ
7696 }
7697 "#
7698 .unindent(),
7699 );
7700
7701 cx.update_editor(|view, cx| {
7702 view.handle_input("(", cx);
7703 });
7704 cx.assert_editor_state(
7705 &"
7706 fn main() {
7707 sample(ˇ)
7708 }
7709 "
7710 .unindent(),
7711 );
7712
7713 let mocked_response = lsp::SignatureHelp {
7714 signatures: vec![lsp::SignatureInformation {
7715 label: "fn sample(param1: u8, param2: u8)".to_string(),
7716 documentation: None,
7717 parameters: Some(vec![
7718 lsp::ParameterInformation {
7719 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7720 documentation: None,
7721 },
7722 lsp::ParameterInformation {
7723 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7724 documentation: None,
7725 },
7726 ]),
7727 active_parameter: None,
7728 }],
7729 active_signature: Some(0),
7730 active_parameter: Some(0),
7731 };
7732 handle_signature_help_request(&mut cx, mocked_response).await;
7733
7734 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7735 .await;
7736
7737 cx.editor(|editor, _| {
7738 let signature_help_state = editor.signature_help_state.popover().cloned();
7739 assert!(signature_help_state.is_some());
7740 let ParsedMarkdown {
7741 text, highlights, ..
7742 } = signature_help_state.unwrap().parsed_content;
7743 assert_eq!(text, "param1: u8, param2: u8");
7744 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7745 });
7746}
7747
7748#[gpui::test]
7749async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7750 init_test(cx, |_| {});
7751
7752 cx.update(|cx| {
7753 cx.update_global::<SettingsStore, _>(|settings, cx| {
7754 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7755 settings.auto_signature_help = Some(false);
7756 settings.show_signature_help_after_edits = Some(false);
7757 });
7758 });
7759 });
7760
7761 let mut cx = EditorLspTestContext::new_rust(
7762 lsp::ServerCapabilities {
7763 signature_help_provider: Some(lsp::SignatureHelpOptions {
7764 ..Default::default()
7765 }),
7766 ..Default::default()
7767 },
7768 cx,
7769 )
7770 .await;
7771
7772 let language = Language::new(
7773 LanguageConfig {
7774 name: "Rust".into(),
7775 brackets: BracketPairConfig {
7776 pairs: vec![
7777 BracketPair {
7778 start: "{".to_string(),
7779 end: "}".to_string(),
7780 close: true,
7781 surround: true,
7782 newline: true,
7783 },
7784 BracketPair {
7785 start: "(".to_string(),
7786 end: ")".to_string(),
7787 close: true,
7788 surround: true,
7789 newline: true,
7790 },
7791 BracketPair {
7792 start: "/*".to_string(),
7793 end: " */".to_string(),
7794 close: true,
7795 surround: true,
7796 newline: true,
7797 },
7798 BracketPair {
7799 start: "[".to_string(),
7800 end: "]".to_string(),
7801 close: false,
7802 surround: false,
7803 newline: true,
7804 },
7805 BracketPair {
7806 start: "\"".to_string(),
7807 end: "\"".to_string(),
7808 close: true,
7809 surround: true,
7810 newline: false,
7811 },
7812 BracketPair {
7813 start: "<".to_string(),
7814 end: ">".to_string(),
7815 close: false,
7816 surround: true,
7817 newline: true,
7818 },
7819 ],
7820 ..Default::default()
7821 },
7822 autoclose_before: "})]".to_string(),
7823 ..Default::default()
7824 },
7825 Some(tree_sitter_rust::LANGUAGE.into()),
7826 );
7827 let language = Arc::new(language);
7828
7829 cx.language_registry().add(language.clone());
7830 cx.update_buffer(|buffer, cx| {
7831 buffer.set_language(Some(language), cx);
7832 });
7833
7834 // Ensure that signature_help is not called when no signature help is enabled.
7835 cx.set_state(
7836 &r#"
7837 fn main() {
7838 sampleˇ
7839 }
7840 "#
7841 .unindent(),
7842 );
7843 cx.update_editor(|view, cx| {
7844 view.handle_input("(", cx);
7845 });
7846 cx.assert_editor_state(
7847 &"
7848 fn main() {
7849 sample(ˇ)
7850 }
7851 "
7852 .unindent(),
7853 );
7854 cx.editor(|editor, _| {
7855 assert!(editor.signature_help_state.task().is_none());
7856 });
7857
7858 let mocked_response = lsp::SignatureHelp {
7859 signatures: vec![lsp::SignatureInformation {
7860 label: "fn sample(param1: u8, param2: u8)".to_string(),
7861 documentation: None,
7862 parameters: Some(vec![
7863 lsp::ParameterInformation {
7864 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7865 documentation: None,
7866 },
7867 lsp::ParameterInformation {
7868 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7869 documentation: None,
7870 },
7871 ]),
7872 active_parameter: None,
7873 }],
7874 active_signature: Some(0),
7875 active_parameter: Some(0),
7876 };
7877
7878 // Ensure that signature_help is called when enabled afte edits
7879 cx.update(|cx| {
7880 cx.update_global::<SettingsStore, _>(|settings, cx| {
7881 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7882 settings.auto_signature_help = Some(false);
7883 settings.show_signature_help_after_edits = Some(true);
7884 });
7885 });
7886 });
7887 cx.set_state(
7888 &r#"
7889 fn main() {
7890 sampleˇ
7891 }
7892 "#
7893 .unindent(),
7894 );
7895 cx.update_editor(|view, cx| {
7896 view.handle_input("(", cx);
7897 });
7898 cx.assert_editor_state(
7899 &"
7900 fn main() {
7901 sample(ˇ)
7902 }
7903 "
7904 .unindent(),
7905 );
7906 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7907 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7908 .await;
7909 cx.update_editor(|editor, _| {
7910 let signature_help_state = editor.signature_help_state.popover().cloned();
7911 assert!(signature_help_state.is_some());
7912 let ParsedMarkdown {
7913 text, highlights, ..
7914 } = signature_help_state.unwrap().parsed_content;
7915 assert_eq!(text, "param1: u8, param2: u8");
7916 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7917 editor.signature_help_state = SignatureHelpState::default();
7918 });
7919
7920 // Ensure that signature_help is called when auto signature help override is enabled
7921 cx.update(|cx| {
7922 cx.update_global::<SettingsStore, _>(|settings, cx| {
7923 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7924 settings.auto_signature_help = Some(true);
7925 settings.show_signature_help_after_edits = Some(false);
7926 });
7927 });
7928 });
7929 cx.set_state(
7930 &r#"
7931 fn main() {
7932 sampleˇ
7933 }
7934 "#
7935 .unindent(),
7936 );
7937 cx.update_editor(|view, cx| {
7938 view.handle_input("(", cx);
7939 });
7940 cx.assert_editor_state(
7941 &"
7942 fn main() {
7943 sample(ˇ)
7944 }
7945 "
7946 .unindent(),
7947 );
7948 handle_signature_help_request(&mut cx, mocked_response).await;
7949 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7950 .await;
7951 cx.editor(|editor, _| {
7952 let signature_help_state = editor.signature_help_state.popover().cloned();
7953 assert!(signature_help_state.is_some());
7954 let ParsedMarkdown {
7955 text, highlights, ..
7956 } = signature_help_state.unwrap().parsed_content;
7957 assert_eq!(text, "param1: u8, param2: u8");
7958 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7959 });
7960}
7961
7962#[gpui::test]
7963async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7964 init_test(cx, |_| {});
7965 cx.update(|cx| {
7966 cx.update_global::<SettingsStore, _>(|settings, cx| {
7967 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7968 settings.auto_signature_help = Some(true);
7969 });
7970 });
7971 });
7972
7973 let mut cx = EditorLspTestContext::new_rust(
7974 lsp::ServerCapabilities {
7975 signature_help_provider: Some(lsp::SignatureHelpOptions {
7976 ..Default::default()
7977 }),
7978 ..Default::default()
7979 },
7980 cx,
7981 )
7982 .await;
7983
7984 // A test that directly calls `show_signature_help`
7985 cx.update_editor(|editor, cx| {
7986 editor.show_signature_help(&ShowSignatureHelp, cx);
7987 });
7988
7989 let mocked_response = lsp::SignatureHelp {
7990 signatures: vec![lsp::SignatureInformation {
7991 label: "fn sample(param1: u8, param2: u8)".to_string(),
7992 documentation: None,
7993 parameters: Some(vec![
7994 lsp::ParameterInformation {
7995 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7996 documentation: None,
7997 },
7998 lsp::ParameterInformation {
7999 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8000 documentation: None,
8001 },
8002 ]),
8003 active_parameter: None,
8004 }],
8005 active_signature: Some(0),
8006 active_parameter: Some(0),
8007 };
8008 handle_signature_help_request(&mut cx, mocked_response).await;
8009
8010 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8011 .await;
8012
8013 cx.editor(|editor, _| {
8014 let signature_help_state = editor.signature_help_state.popover().cloned();
8015 assert!(signature_help_state.is_some());
8016 let ParsedMarkdown {
8017 text, highlights, ..
8018 } = signature_help_state.unwrap().parsed_content;
8019 assert_eq!(text, "param1: u8, param2: u8");
8020 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8021 });
8022
8023 // When exiting outside from inside the brackets, `signature_help` is closed.
8024 cx.set_state(indoc! {"
8025 fn main() {
8026 sample(ˇ);
8027 }
8028
8029 fn sample(param1: u8, param2: u8) {}
8030 "});
8031
8032 cx.update_editor(|editor, cx| {
8033 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8034 });
8035
8036 let mocked_response = lsp::SignatureHelp {
8037 signatures: Vec::new(),
8038 active_signature: None,
8039 active_parameter: None,
8040 };
8041 handle_signature_help_request(&mut cx, mocked_response).await;
8042
8043 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8044 .await;
8045
8046 cx.editor(|editor, _| {
8047 assert!(!editor.signature_help_state.is_shown());
8048 });
8049
8050 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8051 cx.set_state(indoc! {"
8052 fn main() {
8053 sample(ˇ);
8054 }
8055
8056 fn sample(param1: u8, param2: u8) {}
8057 "});
8058
8059 let mocked_response = lsp::SignatureHelp {
8060 signatures: vec![lsp::SignatureInformation {
8061 label: "fn sample(param1: u8, param2: u8)".to_string(),
8062 documentation: None,
8063 parameters: Some(vec![
8064 lsp::ParameterInformation {
8065 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8066 documentation: None,
8067 },
8068 lsp::ParameterInformation {
8069 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8070 documentation: None,
8071 },
8072 ]),
8073 active_parameter: None,
8074 }],
8075 active_signature: Some(0),
8076 active_parameter: Some(0),
8077 };
8078 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8079 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8080 .await;
8081 cx.editor(|editor, _| {
8082 assert!(editor.signature_help_state.is_shown());
8083 });
8084
8085 // Restore the popover with more parameter input
8086 cx.set_state(indoc! {"
8087 fn main() {
8088 sample(param1, param2ˇ);
8089 }
8090
8091 fn sample(param1: u8, param2: u8) {}
8092 "});
8093
8094 let mocked_response = lsp::SignatureHelp {
8095 signatures: vec![lsp::SignatureInformation {
8096 label: "fn sample(param1: u8, param2: u8)".to_string(),
8097 documentation: None,
8098 parameters: Some(vec![
8099 lsp::ParameterInformation {
8100 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8101 documentation: None,
8102 },
8103 lsp::ParameterInformation {
8104 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8105 documentation: None,
8106 },
8107 ]),
8108 active_parameter: None,
8109 }],
8110 active_signature: Some(0),
8111 active_parameter: Some(1),
8112 };
8113 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8114 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8115 .await;
8116
8117 // When selecting a range, the popover is gone.
8118 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8119 cx.update_editor(|editor, cx| {
8120 editor.change_selections(None, cx, |s| {
8121 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8122 })
8123 });
8124 cx.assert_editor_state(indoc! {"
8125 fn main() {
8126 sample(param1, «ˇparam2»);
8127 }
8128
8129 fn sample(param1: u8, param2: u8) {}
8130 "});
8131 cx.editor(|editor, _| {
8132 assert!(!editor.signature_help_state.is_shown());
8133 });
8134
8135 // When unselecting again, the popover is back if within the brackets.
8136 cx.update_editor(|editor, cx| {
8137 editor.change_selections(None, cx, |s| {
8138 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8139 })
8140 });
8141 cx.assert_editor_state(indoc! {"
8142 fn main() {
8143 sample(param1, ˇparam2);
8144 }
8145
8146 fn sample(param1: u8, param2: u8) {}
8147 "});
8148 handle_signature_help_request(&mut cx, mocked_response).await;
8149 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8150 .await;
8151 cx.editor(|editor, _| {
8152 assert!(editor.signature_help_state.is_shown());
8153 });
8154
8155 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8156 cx.update_editor(|editor, cx| {
8157 editor.change_selections(None, cx, |s| {
8158 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8159 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8160 })
8161 });
8162 cx.assert_editor_state(indoc! {"
8163 fn main() {
8164 sample(param1, ˇparam2);
8165 }
8166
8167 fn sample(param1: u8, param2: u8) {}
8168 "});
8169
8170 let mocked_response = lsp::SignatureHelp {
8171 signatures: vec![lsp::SignatureInformation {
8172 label: "fn sample(param1: u8, param2: u8)".to_string(),
8173 documentation: None,
8174 parameters: Some(vec![
8175 lsp::ParameterInformation {
8176 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8177 documentation: None,
8178 },
8179 lsp::ParameterInformation {
8180 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8181 documentation: None,
8182 },
8183 ]),
8184 active_parameter: None,
8185 }],
8186 active_signature: Some(0),
8187 active_parameter: Some(1),
8188 };
8189 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8190 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8191 .await;
8192 cx.update_editor(|editor, cx| {
8193 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8194 });
8195 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8196 .await;
8197 cx.update_editor(|editor, cx| {
8198 editor.change_selections(None, cx, |s| {
8199 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8200 })
8201 });
8202 cx.assert_editor_state(indoc! {"
8203 fn main() {
8204 sample(param1, «ˇparam2»);
8205 }
8206
8207 fn sample(param1: u8, param2: u8) {}
8208 "});
8209 cx.update_editor(|editor, cx| {
8210 editor.change_selections(None, cx, |s| {
8211 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8212 })
8213 });
8214 cx.assert_editor_state(indoc! {"
8215 fn main() {
8216 sample(param1, ˇparam2);
8217 }
8218
8219 fn sample(param1: u8, param2: u8) {}
8220 "});
8221 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8222 .await;
8223}
8224
8225#[gpui::test]
8226async fn test_completion(cx: &mut gpui::TestAppContext) {
8227 init_test(cx, |_| {});
8228
8229 let mut cx = EditorLspTestContext::new_rust(
8230 lsp::ServerCapabilities {
8231 completion_provider: Some(lsp::CompletionOptions {
8232 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8233 resolve_provider: Some(true),
8234 ..Default::default()
8235 }),
8236 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8237 ..Default::default()
8238 },
8239 cx,
8240 )
8241 .await;
8242 let counter = Arc::new(AtomicUsize::new(0));
8243
8244 cx.set_state(indoc! {"
8245 oneˇ
8246 two
8247 three
8248 "});
8249 cx.simulate_keystroke(".");
8250 handle_completion_request(
8251 &mut cx,
8252 indoc! {"
8253 one.|<>
8254 two
8255 three
8256 "},
8257 vec!["first_completion", "second_completion"],
8258 counter.clone(),
8259 )
8260 .await;
8261 cx.condition(|editor, _| editor.context_menu_visible())
8262 .await;
8263 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8264
8265 let _handler = handle_signature_help_request(
8266 &mut cx,
8267 lsp::SignatureHelp {
8268 signatures: vec![lsp::SignatureInformation {
8269 label: "test signature".to_string(),
8270 documentation: None,
8271 parameters: Some(vec![lsp::ParameterInformation {
8272 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8273 documentation: None,
8274 }]),
8275 active_parameter: None,
8276 }],
8277 active_signature: None,
8278 active_parameter: None,
8279 },
8280 );
8281 cx.update_editor(|editor, cx| {
8282 assert!(
8283 !editor.signature_help_state.is_shown(),
8284 "No signature help was called for"
8285 );
8286 editor.show_signature_help(&ShowSignatureHelp, cx);
8287 });
8288 cx.run_until_parked();
8289 cx.update_editor(|editor, _| {
8290 assert!(
8291 !editor.signature_help_state.is_shown(),
8292 "No signature help should be shown when completions menu is open"
8293 );
8294 });
8295
8296 let apply_additional_edits = cx.update_editor(|editor, cx| {
8297 editor.context_menu_next(&Default::default(), cx);
8298 editor
8299 .confirm_completion(&ConfirmCompletion::default(), cx)
8300 .unwrap()
8301 });
8302 cx.assert_editor_state(indoc! {"
8303 one.second_completionˇ
8304 two
8305 three
8306 "});
8307
8308 handle_resolve_completion_request(
8309 &mut cx,
8310 Some(vec![
8311 (
8312 //This overlaps with the primary completion edit which is
8313 //misbehavior from the LSP spec, test that we filter it out
8314 indoc! {"
8315 one.second_ˇcompletion
8316 two
8317 threeˇ
8318 "},
8319 "overlapping additional edit",
8320 ),
8321 (
8322 indoc! {"
8323 one.second_completion
8324 two
8325 threeˇ
8326 "},
8327 "\nadditional edit",
8328 ),
8329 ]),
8330 )
8331 .await;
8332 apply_additional_edits.await.unwrap();
8333 cx.assert_editor_state(indoc! {"
8334 one.second_completionˇ
8335 two
8336 three
8337 additional edit
8338 "});
8339
8340 cx.set_state(indoc! {"
8341 one.second_completion
8342 twoˇ
8343 threeˇ
8344 additional edit
8345 "});
8346 cx.simulate_keystroke(" ");
8347 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8348 cx.simulate_keystroke("s");
8349 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8350
8351 cx.assert_editor_state(indoc! {"
8352 one.second_completion
8353 two sˇ
8354 three sˇ
8355 additional edit
8356 "});
8357 handle_completion_request(
8358 &mut cx,
8359 indoc! {"
8360 one.second_completion
8361 two s
8362 three <s|>
8363 additional edit
8364 "},
8365 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8366 counter.clone(),
8367 )
8368 .await;
8369 cx.condition(|editor, _| editor.context_menu_visible())
8370 .await;
8371 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8372
8373 cx.simulate_keystroke("i");
8374
8375 handle_completion_request(
8376 &mut cx,
8377 indoc! {"
8378 one.second_completion
8379 two si
8380 three <si|>
8381 additional edit
8382 "},
8383 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8384 counter.clone(),
8385 )
8386 .await;
8387 cx.condition(|editor, _| editor.context_menu_visible())
8388 .await;
8389 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8390
8391 let apply_additional_edits = cx.update_editor(|editor, cx| {
8392 editor
8393 .confirm_completion(&ConfirmCompletion::default(), cx)
8394 .unwrap()
8395 });
8396 cx.assert_editor_state(indoc! {"
8397 one.second_completion
8398 two sixth_completionˇ
8399 three sixth_completionˇ
8400 additional edit
8401 "});
8402
8403 apply_additional_edits.await.unwrap();
8404
8405 update_test_language_settings(&mut cx, |settings| {
8406 settings.defaults.show_completions_on_input = Some(false);
8407 });
8408 cx.set_state("editorˇ");
8409 cx.simulate_keystroke(".");
8410 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8411 cx.simulate_keystroke("c");
8412 cx.simulate_keystroke("l");
8413 cx.simulate_keystroke("o");
8414 cx.assert_editor_state("editor.cloˇ");
8415 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8416 cx.update_editor(|editor, cx| {
8417 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8418 });
8419 handle_completion_request(
8420 &mut cx,
8421 "editor.<clo|>",
8422 vec!["close", "clobber"],
8423 counter.clone(),
8424 )
8425 .await;
8426 cx.condition(|editor, _| editor.context_menu_visible())
8427 .await;
8428 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8429
8430 let apply_additional_edits = cx.update_editor(|editor, cx| {
8431 editor
8432 .confirm_completion(&ConfirmCompletion::default(), cx)
8433 .unwrap()
8434 });
8435 cx.assert_editor_state("editor.closeˇ");
8436 handle_resolve_completion_request(&mut cx, None).await;
8437 apply_additional_edits.await.unwrap();
8438}
8439
8440#[gpui::test]
8441async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8442 init_test(cx, |_| {});
8443 let mut cx = EditorLspTestContext::new_rust(
8444 lsp::ServerCapabilities {
8445 completion_provider: Some(lsp::CompletionOptions {
8446 trigger_characters: Some(vec![".".to_string()]),
8447 ..Default::default()
8448 }),
8449 ..Default::default()
8450 },
8451 cx,
8452 )
8453 .await;
8454 cx.lsp
8455 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8456 Ok(Some(lsp::CompletionResponse::Array(vec![
8457 lsp::CompletionItem {
8458 label: "first".into(),
8459 ..Default::default()
8460 },
8461 lsp::CompletionItem {
8462 label: "last".into(),
8463 ..Default::default()
8464 },
8465 ])))
8466 });
8467 cx.set_state("variableˇ");
8468 cx.simulate_keystroke(".");
8469 cx.executor().run_until_parked();
8470
8471 cx.update_editor(|editor, _| {
8472 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8473 {
8474 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
8475 } else {
8476 panic!("expected completion menu to be open");
8477 }
8478 });
8479
8480 cx.update_editor(|editor, cx| {
8481 editor.move_page_down(&MovePageDown::default(), cx);
8482 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8483 {
8484 assert!(
8485 menu.selected_item == 1,
8486 "expected PageDown to select the last item from the context menu"
8487 );
8488 } else {
8489 panic!("expected completion menu to stay open after PageDown");
8490 }
8491 });
8492
8493 cx.update_editor(|editor, cx| {
8494 editor.move_page_up(&MovePageUp::default(), cx);
8495 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8496 {
8497 assert!(
8498 menu.selected_item == 0,
8499 "expected PageUp to select the first item from the context menu"
8500 );
8501 } else {
8502 panic!("expected completion menu to stay open after PageUp");
8503 }
8504 });
8505}
8506
8507#[gpui::test]
8508async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8509 init_test(cx, |_| {});
8510 let mut cx = EditorLspTestContext::new_rust(
8511 lsp::ServerCapabilities {
8512 completion_provider: Some(lsp::CompletionOptions {
8513 trigger_characters: Some(vec![".".to_string()]),
8514 ..Default::default()
8515 }),
8516 ..Default::default()
8517 },
8518 cx,
8519 )
8520 .await;
8521 cx.lsp
8522 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8523 Ok(Some(lsp::CompletionResponse::Array(vec![
8524 lsp::CompletionItem {
8525 label: "Range".into(),
8526 sort_text: Some("a".into()),
8527 ..Default::default()
8528 },
8529 lsp::CompletionItem {
8530 label: "r".into(),
8531 sort_text: Some("b".into()),
8532 ..Default::default()
8533 },
8534 lsp::CompletionItem {
8535 label: "ret".into(),
8536 sort_text: Some("c".into()),
8537 ..Default::default()
8538 },
8539 lsp::CompletionItem {
8540 label: "return".into(),
8541 sort_text: Some("d".into()),
8542 ..Default::default()
8543 },
8544 lsp::CompletionItem {
8545 label: "slice".into(),
8546 sort_text: Some("d".into()),
8547 ..Default::default()
8548 },
8549 ])))
8550 });
8551 cx.set_state("rˇ");
8552 cx.executor().run_until_parked();
8553 cx.update_editor(|editor, cx| {
8554 editor.show_completions(
8555 &ShowCompletions {
8556 trigger: Some("r".into()),
8557 },
8558 cx,
8559 );
8560 });
8561 cx.executor().run_until_parked();
8562
8563 cx.update_editor(|editor, _| {
8564 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8565 {
8566 assert_eq!(
8567 completion_menu_entries(&menu),
8568 &["r", "ret", "Range", "return"]
8569 );
8570 } else {
8571 panic!("expected completion menu to be open");
8572 }
8573 });
8574}
8575
8576#[gpui::test]
8577async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8578 init_test(cx, |_| {});
8579
8580 let mut cx = EditorLspTestContext::new_rust(
8581 lsp::ServerCapabilities {
8582 completion_provider: Some(lsp::CompletionOptions {
8583 trigger_characters: Some(vec![".".to_string()]),
8584 resolve_provider: Some(true),
8585 ..Default::default()
8586 }),
8587 ..Default::default()
8588 },
8589 cx,
8590 )
8591 .await;
8592
8593 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8594 cx.simulate_keystroke(".");
8595 let completion_item = lsp::CompletionItem {
8596 label: "Some".into(),
8597 kind: Some(lsp::CompletionItemKind::SNIPPET),
8598 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8599 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8600 kind: lsp::MarkupKind::Markdown,
8601 value: "```rust\nSome(2)\n```".to_string(),
8602 })),
8603 deprecated: Some(false),
8604 sort_text: Some("Some".to_string()),
8605 filter_text: Some("Some".to_string()),
8606 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8607 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8608 range: lsp::Range {
8609 start: lsp::Position {
8610 line: 0,
8611 character: 22,
8612 },
8613 end: lsp::Position {
8614 line: 0,
8615 character: 22,
8616 },
8617 },
8618 new_text: "Some(2)".to_string(),
8619 })),
8620 additional_text_edits: Some(vec![lsp::TextEdit {
8621 range: lsp::Range {
8622 start: lsp::Position {
8623 line: 0,
8624 character: 20,
8625 },
8626 end: lsp::Position {
8627 line: 0,
8628 character: 22,
8629 },
8630 },
8631 new_text: "".to_string(),
8632 }]),
8633 ..Default::default()
8634 };
8635
8636 let closure_completion_item = completion_item.clone();
8637 let counter = Arc::new(AtomicUsize::new(0));
8638 let counter_clone = counter.clone();
8639 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8640 let task_completion_item = closure_completion_item.clone();
8641 counter_clone.fetch_add(1, atomic::Ordering::Release);
8642 async move {
8643 Ok(Some(lsp::CompletionResponse::Array(vec![
8644 task_completion_item,
8645 ])))
8646 }
8647 });
8648
8649 cx.condition(|editor, _| editor.context_menu_visible())
8650 .await;
8651 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8652 assert!(request.next().await.is_some());
8653 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8654
8655 cx.simulate_keystroke("S");
8656 cx.simulate_keystroke("o");
8657 cx.simulate_keystroke("m");
8658 cx.condition(|editor, _| editor.context_menu_visible())
8659 .await;
8660 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8661 assert!(request.next().await.is_some());
8662 assert!(request.next().await.is_some());
8663 assert!(request.next().await.is_some());
8664 request.close();
8665 assert!(request.next().await.is_none());
8666 assert_eq!(
8667 counter.load(atomic::Ordering::Acquire),
8668 4,
8669 "With the completions menu open, only one LSP request should happen per input"
8670 );
8671}
8672
8673#[gpui::test]
8674async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8675 init_test(cx, |_| {});
8676 let mut cx = EditorTestContext::new(cx).await;
8677 let language = Arc::new(Language::new(
8678 LanguageConfig {
8679 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8680 ..Default::default()
8681 },
8682 Some(tree_sitter_rust::LANGUAGE.into()),
8683 ));
8684 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8685
8686 // If multiple selections intersect a line, the line is only toggled once.
8687 cx.set_state(indoc! {"
8688 fn a() {
8689 «//b();
8690 ˇ»// «c();
8691 //ˇ» d();
8692 }
8693 "});
8694
8695 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8696
8697 cx.assert_editor_state(indoc! {"
8698 fn a() {
8699 «b();
8700 c();
8701 ˇ» d();
8702 }
8703 "});
8704
8705 // The comment prefix is inserted at the same column for every line in a
8706 // selection.
8707 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8708
8709 cx.assert_editor_state(indoc! {"
8710 fn a() {
8711 // «b();
8712 // c();
8713 ˇ»// d();
8714 }
8715 "});
8716
8717 // If a selection ends at the beginning of a line, that line is not toggled.
8718 cx.set_selections_state(indoc! {"
8719 fn a() {
8720 // b();
8721 «// c();
8722 ˇ» // d();
8723 }
8724 "});
8725
8726 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8727
8728 cx.assert_editor_state(indoc! {"
8729 fn a() {
8730 // b();
8731 «c();
8732 ˇ» // d();
8733 }
8734 "});
8735
8736 // If a selection span a single line and is empty, the line is toggled.
8737 cx.set_state(indoc! {"
8738 fn a() {
8739 a();
8740 b();
8741 ˇ
8742 }
8743 "});
8744
8745 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8746
8747 cx.assert_editor_state(indoc! {"
8748 fn a() {
8749 a();
8750 b();
8751 //•ˇ
8752 }
8753 "});
8754
8755 // If a selection span multiple lines, empty lines are not toggled.
8756 cx.set_state(indoc! {"
8757 fn a() {
8758 «a();
8759
8760 c();ˇ»
8761 }
8762 "});
8763
8764 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8765
8766 cx.assert_editor_state(indoc! {"
8767 fn a() {
8768 // «a();
8769
8770 // c();ˇ»
8771 }
8772 "});
8773
8774 // If a selection includes multiple comment prefixes, all lines are uncommented.
8775 cx.set_state(indoc! {"
8776 fn a() {
8777 «// a();
8778 /// b();
8779 //! c();ˇ»
8780 }
8781 "});
8782
8783 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8784
8785 cx.assert_editor_state(indoc! {"
8786 fn a() {
8787 «a();
8788 b();
8789 c();ˇ»
8790 }
8791 "});
8792}
8793
8794#[gpui::test]
8795async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8796 init_test(cx, |_| {});
8797 let mut cx = EditorTestContext::new(cx).await;
8798 let language = Arc::new(Language::new(
8799 LanguageConfig {
8800 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8801 ..Default::default()
8802 },
8803 Some(tree_sitter_rust::LANGUAGE.into()),
8804 ));
8805 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8806
8807 let toggle_comments = &ToggleComments {
8808 advance_downwards: false,
8809 ignore_indent: true,
8810 };
8811
8812 // If multiple selections intersect a line, the line is only toggled once.
8813 cx.set_state(indoc! {"
8814 fn a() {
8815 // «b();
8816 // c();
8817 // ˇ» d();
8818 }
8819 "});
8820
8821 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8822
8823 cx.assert_editor_state(indoc! {"
8824 fn a() {
8825 «b();
8826 c();
8827 ˇ» d();
8828 }
8829 "});
8830
8831 // The comment prefix is inserted at the beginning of each line
8832 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8833
8834 cx.assert_editor_state(indoc! {"
8835 fn a() {
8836 // «b();
8837 // c();
8838 // ˇ» d();
8839 }
8840 "});
8841
8842 // If a selection ends at the beginning of a line, that line is not toggled.
8843 cx.set_selections_state(indoc! {"
8844 fn a() {
8845 // b();
8846 // «c();
8847 ˇ»// d();
8848 }
8849 "});
8850
8851 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8852
8853 cx.assert_editor_state(indoc! {"
8854 fn a() {
8855 // b();
8856 «c();
8857 ˇ»// d();
8858 }
8859 "});
8860
8861 // If a selection span a single line and is empty, the line is toggled.
8862 cx.set_state(indoc! {"
8863 fn a() {
8864 a();
8865 b();
8866 ˇ
8867 }
8868 "});
8869
8870 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8871
8872 cx.assert_editor_state(indoc! {"
8873 fn a() {
8874 a();
8875 b();
8876 //ˇ
8877 }
8878 "});
8879
8880 // If a selection span multiple lines, empty lines are not toggled.
8881 cx.set_state(indoc! {"
8882 fn a() {
8883 «a();
8884
8885 c();ˇ»
8886 }
8887 "});
8888
8889 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8890
8891 cx.assert_editor_state(indoc! {"
8892 fn a() {
8893 // «a();
8894
8895 // c();ˇ»
8896 }
8897 "});
8898
8899 // If a selection includes multiple comment prefixes, all lines are uncommented.
8900 cx.set_state(indoc! {"
8901 fn a() {
8902 // «a();
8903 /// b();
8904 //! c();ˇ»
8905 }
8906 "});
8907
8908 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8909
8910 cx.assert_editor_state(indoc! {"
8911 fn a() {
8912 «a();
8913 b();
8914 c();ˇ»
8915 }
8916 "});
8917}
8918
8919#[gpui::test]
8920async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8921 init_test(cx, |_| {});
8922
8923 let language = Arc::new(Language::new(
8924 LanguageConfig {
8925 line_comments: vec!["// ".into()],
8926 ..Default::default()
8927 },
8928 Some(tree_sitter_rust::LANGUAGE.into()),
8929 ));
8930
8931 let mut cx = EditorTestContext::new(cx).await;
8932
8933 cx.language_registry().add(language.clone());
8934 cx.update_buffer(|buffer, cx| {
8935 buffer.set_language(Some(language), cx);
8936 });
8937
8938 let toggle_comments = &ToggleComments {
8939 advance_downwards: true,
8940 ignore_indent: false,
8941 };
8942
8943 // Single cursor on one line -> advance
8944 // Cursor moves horizontally 3 characters as well on non-blank line
8945 cx.set_state(indoc!(
8946 "fn a() {
8947 ˇdog();
8948 cat();
8949 }"
8950 ));
8951 cx.update_editor(|editor, cx| {
8952 editor.toggle_comments(toggle_comments, cx);
8953 });
8954 cx.assert_editor_state(indoc!(
8955 "fn a() {
8956 // dog();
8957 catˇ();
8958 }"
8959 ));
8960
8961 // Single selection on one line -> don't advance
8962 cx.set_state(indoc!(
8963 "fn a() {
8964 «dog()ˇ»;
8965 cat();
8966 }"
8967 ));
8968 cx.update_editor(|editor, cx| {
8969 editor.toggle_comments(toggle_comments, cx);
8970 });
8971 cx.assert_editor_state(indoc!(
8972 "fn a() {
8973 // «dog()ˇ»;
8974 cat();
8975 }"
8976 ));
8977
8978 // Multiple cursors on one line -> advance
8979 cx.set_state(indoc!(
8980 "fn a() {
8981 ˇdˇog();
8982 cat();
8983 }"
8984 ));
8985 cx.update_editor(|editor, cx| {
8986 editor.toggle_comments(toggle_comments, cx);
8987 });
8988 cx.assert_editor_state(indoc!(
8989 "fn a() {
8990 // dog();
8991 catˇ(ˇ);
8992 }"
8993 ));
8994
8995 // Multiple cursors on one line, with selection -> don't advance
8996 cx.set_state(indoc!(
8997 "fn a() {
8998 ˇdˇog«()ˇ»;
8999 cat();
9000 }"
9001 ));
9002 cx.update_editor(|editor, cx| {
9003 editor.toggle_comments(toggle_comments, cx);
9004 });
9005 cx.assert_editor_state(indoc!(
9006 "fn a() {
9007 // ˇdˇog«()ˇ»;
9008 cat();
9009 }"
9010 ));
9011
9012 // Single cursor on one line -> advance
9013 // Cursor moves to column 0 on blank line
9014 cx.set_state(indoc!(
9015 "fn a() {
9016 ˇdog();
9017
9018 cat();
9019 }"
9020 ));
9021 cx.update_editor(|editor, cx| {
9022 editor.toggle_comments(toggle_comments, cx);
9023 });
9024 cx.assert_editor_state(indoc!(
9025 "fn a() {
9026 // dog();
9027 ˇ
9028 cat();
9029 }"
9030 ));
9031
9032 // Single cursor on one line -> advance
9033 // Cursor starts and ends at column 0
9034 cx.set_state(indoc!(
9035 "fn a() {
9036 ˇ dog();
9037 cat();
9038 }"
9039 ));
9040 cx.update_editor(|editor, cx| {
9041 editor.toggle_comments(toggle_comments, cx);
9042 });
9043 cx.assert_editor_state(indoc!(
9044 "fn a() {
9045 // dog();
9046 ˇ cat();
9047 }"
9048 ));
9049}
9050
9051#[gpui::test]
9052async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9053 init_test(cx, |_| {});
9054
9055 let mut cx = EditorTestContext::new(cx).await;
9056
9057 let html_language = Arc::new(
9058 Language::new(
9059 LanguageConfig {
9060 name: "HTML".into(),
9061 block_comment: Some(("<!-- ".into(), " -->".into())),
9062 ..Default::default()
9063 },
9064 Some(tree_sitter_html::language()),
9065 )
9066 .with_injection_query(
9067 r#"
9068 (script_element
9069 (raw_text) @injection.content
9070 (#set! injection.language "javascript"))
9071 "#,
9072 )
9073 .unwrap(),
9074 );
9075
9076 let javascript_language = Arc::new(Language::new(
9077 LanguageConfig {
9078 name: "JavaScript".into(),
9079 line_comments: vec!["// ".into()],
9080 ..Default::default()
9081 },
9082 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9083 ));
9084
9085 cx.language_registry().add(html_language.clone());
9086 cx.language_registry().add(javascript_language.clone());
9087 cx.update_buffer(|buffer, cx| {
9088 buffer.set_language(Some(html_language), cx);
9089 });
9090
9091 // Toggle comments for empty selections
9092 cx.set_state(
9093 &r#"
9094 <p>A</p>ˇ
9095 <p>B</p>ˇ
9096 <p>C</p>ˇ
9097 "#
9098 .unindent(),
9099 );
9100 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9101 cx.assert_editor_state(
9102 &r#"
9103 <!-- <p>A</p>ˇ -->
9104 <!-- <p>B</p>ˇ -->
9105 <!-- <p>C</p>ˇ -->
9106 "#
9107 .unindent(),
9108 );
9109 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9110 cx.assert_editor_state(
9111 &r#"
9112 <p>A</p>ˇ
9113 <p>B</p>ˇ
9114 <p>C</p>ˇ
9115 "#
9116 .unindent(),
9117 );
9118
9119 // Toggle comments for mixture of empty and non-empty selections, where
9120 // multiple selections occupy a given line.
9121 cx.set_state(
9122 &r#"
9123 <p>A«</p>
9124 <p>ˇ»B</p>ˇ
9125 <p>C«</p>
9126 <p>ˇ»D</p>ˇ
9127 "#
9128 .unindent(),
9129 );
9130
9131 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9132 cx.assert_editor_state(
9133 &r#"
9134 <!-- <p>A«</p>
9135 <p>ˇ»B</p>ˇ -->
9136 <!-- <p>C«</p>
9137 <p>ˇ»D</p>ˇ -->
9138 "#
9139 .unindent(),
9140 );
9141 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9142 cx.assert_editor_state(
9143 &r#"
9144 <p>A«</p>
9145 <p>ˇ»B</p>ˇ
9146 <p>C«</p>
9147 <p>ˇ»D</p>ˇ
9148 "#
9149 .unindent(),
9150 );
9151
9152 // Toggle comments when different languages are active for different
9153 // selections.
9154 cx.set_state(
9155 &r#"
9156 ˇ<script>
9157 ˇvar x = new Y();
9158 ˇ</script>
9159 "#
9160 .unindent(),
9161 );
9162 cx.executor().run_until_parked();
9163 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9164 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9165 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9166 cx.assert_editor_state(
9167 &r#"
9168 <!-- ˇ<script> -->
9169 // ˇvar x = new Y();
9170 // ˇ</script>
9171 "#
9172 .unindent(),
9173 );
9174}
9175
9176#[gpui::test]
9177fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9178 init_test(cx, |_| {});
9179
9180 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9181 let multibuffer = cx.new_model(|cx| {
9182 let mut multibuffer = MultiBuffer::new(ReadWrite);
9183 multibuffer.push_excerpts(
9184 buffer.clone(),
9185 [
9186 ExcerptRange {
9187 context: Point::new(0, 0)..Point::new(0, 4),
9188 primary: None,
9189 },
9190 ExcerptRange {
9191 context: Point::new(1, 0)..Point::new(1, 4),
9192 primary: None,
9193 },
9194 ],
9195 cx,
9196 );
9197 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9198 multibuffer
9199 });
9200
9201 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9202 view.update(cx, |view, cx| {
9203 assert_eq!(view.text(cx), "aaaa\nbbbb");
9204 view.change_selections(None, cx, |s| {
9205 s.select_ranges([
9206 Point::new(0, 0)..Point::new(0, 0),
9207 Point::new(1, 0)..Point::new(1, 0),
9208 ])
9209 });
9210
9211 view.handle_input("X", cx);
9212 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9213 assert_eq!(
9214 view.selections.ranges(cx),
9215 [
9216 Point::new(0, 1)..Point::new(0, 1),
9217 Point::new(1, 1)..Point::new(1, 1),
9218 ]
9219 );
9220
9221 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9222 view.change_selections(None, cx, |s| {
9223 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9224 });
9225 view.backspace(&Default::default(), cx);
9226 assert_eq!(view.text(cx), "Xa\nbbb");
9227 assert_eq!(
9228 view.selections.ranges(cx),
9229 [Point::new(1, 0)..Point::new(1, 0)]
9230 );
9231
9232 view.change_selections(None, cx, |s| {
9233 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9234 });
9235 view.backspace(&Default::default(), cx);
9236 assert_eq!(view.text(cx), "X\nbb");
9237 assert_eq!(
9238 view.selections.ranges(cx),
9239 [Point::new(0, 1)..Point::new(0, 1)]
9240 );
9241 });
9242}
9243
9244#[gpui::test]
9245fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9246 init_test(cx, |_| {});
9247
9248 let markers = vec![('[', ']').into(), ('(', ')').into()];
9249 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9250 indoc! {"
9251 [aaaa
9252 (bbbb]
9253 cccc)",
9254 },
9255 markers.clone(),
9256 );
9257 let excerpt_ranges = markers.into_iter().map(|marker| {
9258 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9259 ExcerptRange {
9260 context,
9261 primary: None,
9262 }
9263 });
9264 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9265 let multibuffer = cx.new_model(|cx| {
9266 let mut multibuffer = MultiBuffer::new(ReadWrite);
9267 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9268 multibuffer
9269 });
9270
9271 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9272 view.update(cx, |view, cx| {
9273 let (expected_text, selection_ranges) = marked_text_ranges(
9274 indoc! {"
9275 aaaa
9276 bˇbbb
9277 bˇbbˇb
9278 cccc"
9279 },
9280 true,
9281 );
9282 assert_eq!(view.text(cx), expected_text);
9283 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9284
9285 view.handle_input("X", cx);
9286
9287 let (expected_text, expected_selections) = marked_text_ranges(
9288 indoc! {"
9289 aaaa
9290 bXˇbbXb
9291 bXˇbbXˇb
9292 cccc"
9293 },
9294 false,
9295 );
9296 assert_eq!(view.text(cx), expected_text);
9297 assert_eq!(view.selections.ranges(cx), expected_selections);
9298
9299 view.newline(&Newline, cx);
9300 let (expected_text, expected_selections) = marked_text_ranges(
9301 indoc! {"
9302 aaaa
9303 bX
9304 ˇbbX
9305 b
9306 bX
9307 ˇbbX
9308 ˇb
9309 cccc"
9310 },
9311 false,
9312 );
9313 assert_eq!(view.text(cx), expected_text);
9314 assert_eq!(view.selections.ranges(cx), expected_selections);
9315 });
9316}
9317
9318#[gpui::test]
9319fn test_refresh_selections(cx: &mut TestAppContext) {
9320 init_test(cx, |_| {});
9321
9322 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9323 let mut excerpt1_id = None;
9324 let multibuffer = cx.new_model(|cx| {
9325 let mut multibuffer = MultiBuffer::new(ReadWrite);
9326 excerpt1_id = multibuffer
9327 .push_excerpts(
9328 buffer.clone(),
9329 [
9330 ExcerptRange {
9331 context: Point::new(0, 0)..Point::new(1, 4),
9332 primary: None,
9333 },
9334 ExcerptRange {
9335 context: Point::new(1, 0)..Point::new(2, 4),
9336 primary: None,
9337 },
9338 ],
9339 cx,
9340 )
9341 .into_iter()
9342 .next();
9343 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9344 multibuffer
9345 });
9346
9347 let editor = cx.add_window(|cx| {
9348 let mut editor = build_editor(multibuffer.clone(), cx);
9349 let snapshot = editor.snapshot(cx);
9350 editor.change_selections(None, cx, |s| {
9351 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9352 });
9353 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9354 assert_eq!(
9355 editor.selections.ranges(cx),
9356 [
9357 Point::new(1, 3)..Point::new(1, 3),
9358 Point::new(2, 1)..Point::new(2, 1),
9359 ]
9360 );
9361 editor
9362 });
9363
9364 // Refreshing selections is a no-op when excerpts haven't changed.
9365 _ = editor.update(cx, |editor, cx| {
9366 editor.change_selections(None, cx, |s| s.refresh());
9367 assert_eq!(
9368 editor.selections.ranges(cx),
9369 [
9370 Point::new(1, 3)..Point::new(1, 3),
9371 Point::new(2, 1)..Point::new(2, 1),
9372 ]
9373 );
9374 });
9375
9376 multibuffer.update(cx, |multibuffer, cx| {
9377 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9378 });
9379 _ = editor.update(cx, |editor, cx| {
9380 // Removing an excerpt causes the first selection to become degenerate.
9381 assert_eq!(
9382 editor.selections.ranges(cx),
9383 [
9384 Point::new(0, 0)..Point::new(0, 0),
9385 Point::new(0, 1)..Point::new(0, 1)
9386 ]
9387 );
9388
9389 // Refreshing selections will relocate the first selection to the original buffer
9390 // location.
9391 editor.change_selections(None, cx, |s| s.refresh());
9392 assert_eq!(
9393 editor.selections.ranges(cx),
9394 [
9395 Point::new(0, 1)..Point::new(0, 1),
9396 Point::new(0, 3)..Point::new(0, 3)
9397 ]
9398 );
9399 assert!(editor.selections.pending_anchor().is_some());
9400 });
9401}
9402
9403#[gpui::test]
9404fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9405 init_test(cx, |_| {});
9406
9407 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9408 let mut excerpt1_id = None;
9409 let multibuffer = cx.new_model(|cx| {
9410 let mut multibuffer = MultiBuffer::new(ReadWrite);
9411 excerpt1_id = multibuffer
9412 .push_excerpts(
9413 buffer.clone(),
9414 [
9415 ExcerptRange {
9416 context: Point::new(0, 0)..Point::new(1, 4),
9417 primary: None,
9418 },
9419 ExcerptRange {
9420 context: Point::new(1, 0)..Point::new(2, 4),
9421 primary: None,
9422 },
9423 ],
9424 cx,
9425 )
9426 .into_iter()
9427 .next();
9428 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9429 multibuffer
9430 });
9431
9432 let editor = cx.add_window(|cx| {
9433 let mut editor = build_editor(multibuffer.clone(), cx);
9434 let snapshot = editor.snapshot(cx);
9435 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9436 assert_eq!(
9437 editor.selections.ranges(cx),
9438 [Point::new(1, 3)..Point::new(1, 3)]
9439 );
9440 editor
9441 });
9442
9443 multibuffer.update(cx, |multibuffer, cx| {
9444 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9445 });
9446 _ = editor.update(cx, |editor, cx| {
9447 assert_eq!(
9448 editor.selections.ranges(cx),
9449 [Point::new(0, 0)..Point::new(0, 0)]
9450 );
9451
9452 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9453 editor.change_selections(None, cx, |s| s.refresh());
9454 assert_eq!(
9455 editor.selections.ranges(cx),
9456 [Point::new(0, 3)..Point::new(0, 3)]
9457 );
9458 assert!(editor.selections.pending_anchor().is_some());
9459 });
9460}
9461
9462#[gpui::test]
9463async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9464 init_test(cx, |_| {});
9465
9466 let language = Arc::new(
9467 Language::new(
9468 LanguageConfig {
9469 brackets: BracketPairConfig {
9470 pairs: vec![
9471 BracketPair {
9472 start: "{".to_string(),
9473 end: "}".to_string(),
9474 close: true,
9475 surround: true,
9476 newline: true,
9477 },
9478 BracketPair {
9479 start: "/* ".to_string(),
9480 end: " */".to_string(),
9481 close: true,
9482 surround: true,
9483 newline: true,
9484 },
9485 ],
9486 ..Default::default()
9487 },
9488 ..Default::default()
9489 },
9490 Some(tree_sitter_rust::LANGUAGE.into()),
9491 )
9492 .with_indents_query("")
9493 .unwrap(),
9494 );
9495
9496 let text = concat!(
9497 "{ }\n", //
9498 " x\n", //
9499 " /* */\n", //
9500 "x\n", //
9501 "{{} }\n", //
9502 );
9503
9504 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9505 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9506 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9507 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9508 .await;
9509
9510 view.update(cx, |view, cx| {
9511 view.change_selections(None, cx, |s| {
9512 s.select_display_ranges([
9513 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9514 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9515 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9516 ])
9517 });
9518 view.newline(&Newline, cx);
9519
9520 assert_eq!(
9521 view.buffer().read(cx).read(cx).text(),
9522 concat!(
9523 "{ \n", // Suppress rustfmt
9524 "\n", //
9525 "}\n", //
9526 " x\n", //
9527 " /* \n", //
9528 " \n", //
9529 " */\n", //
9530 "x\n", //
9531 "{{} \n", //
9532 "}\n", //
9533 )
9534 );
9535 });
9536}
9537
9538#[gpui::test]
9539fn test_highlighted_ranges(cx: &mut TestAppContext) {
9540 init_test(cx, |_| {});
9541
9542 let editor = cx.add_window(|cx| {
9543 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9544 build_editor(buffer.clone(), cx)
9545 });
9546
9547 _ = editor.update(cx, |editor, cx| {
9548 struct Type1;
9549 struct Type2;
9550
9551 let buffer = editor.buffer.read(cx).snapshot(cx);
9552
9553 let anchor_range =
9554 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9555
9556 editor.highlight_background::<Type1>(
9557 &[
9558 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9559 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9560 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9561 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9562 ],
9563 |_| Hsla::red(),
9564 cx,
9565 );
9566 editor.highlight_background::<Type2>(
9567 &[
9568 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9569 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9570 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9571 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9572 ],
9573 |_| Hsla::green(),
9574 cx,
9575 );
9576
9577 let snapshot = editor.snapshot(cx);
9578 let mut highlighted_ranges = editor.background_highlights_in_range(
9579 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9580 &snapshot,
9581 cx.theme().colors(),
9582 );
9583 // Enforce a consistent ordering based on color without relying on the ordering of the
9584 // highlight's `TypeId` which is non-executor.
9585 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9586 assert_eq!(
9587 highlighted_ranges,
9588 &[
9589 (
9590 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9591 Hsla::red(),
9592 ),
9593 (
9594 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9595 Hsla::red(),
9596 ),
9597 (
9598 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9599 Hsla::green(),
9600 ),
9601 (
9602 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9603 Hsla::green(),
9604 ),
9605 ]
9606 );
9607 assert_eq!(
9608 editor.background_highlights_in_range(
9609 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9610 &snapshot,
9611 cx.theme().colors(),
9612 ),
9613 &[(
9614 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9615 Hsla::red(),
9616 )]
9617 );
9618 });
9619}
9620
9621#[gpui::test]
9622async fn test_following(cx: &mut gpui::TestAppContext) {
9623 init_test(cx, |_| {});
9624
9625 let fs = FakeFs::new(cx.executor());
9626 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9627
9628 let buffer = project.update(cx, |project, cx| {
9629 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9630 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9631 });
9632 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9633 let follower = cx.update(|cx| {
9634 cx.open_window(
9635 WindowOptions {
9636 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9637 gpui::Point::new(px(0.), px(0.)),
9638 gpui::Point::new(px(10.), px(80.)),
9639 ))),
9640 ..Default::default()
9641 },
9642 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9643 )
9644 .unwrap()
9645 });
9646
9647 let is_still_following = Rc::new(RefCell::new(true));
9648 let follower_edit_event_count = Rc::new(RefCell::new(0));
9649 let pending_update = Rc::new(RefCell::new(None));
9650 _ = follower.update(cx, {
9651 let update = pending_update.clone();
9652 let is_still_following = is_still_following.clone();
9653 let follower_edit_event_count = follower_edit_event_count.clone();
9654 |_, cx| {
9655 cx.subscribe(
9656 &leader.root_view(cx).unwrap(),
9657 move |_, leader, event, cx| {
9658 leader
9659 .read(cx)
9660 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9661 },
9662 )
9663 .detach();
9664
9665 cx.subscribe(
9666 &follower.root_view(cx).unwrap(),
9667 move |_, _, event: &EditorEvent, _cx| {
9668 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9669 *is_still_following.borrow_mut() = false;
9670 }
9671
9672 if let EditorEvent::BufferEdited = event {
9673 *follower_edit_event_count.borrow_mut() += 1;
9674 }
9675 },
9676 )
9677 .detach();
9678 }
9679 });
9680
9681 // Update the selections only
9682 _ = leader.update(cx, |leader, cx| {
9683 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9684 });
9685 follower
9686 .update(cx, |follower, cx| {
9687 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9688 })
9689 .unwrap()
9690 .await
9691 .unwrap();
9692 _ = follower.update(cx, |follower, cx| {
9693 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9694 });
9695 assert!(*is_still_following.borrow());
9696 assert_eq!(*follower_edit_event_count.borrow(), 0);
9697
9698 // Update the scroll position only
9699 _ = leader.update(cx, |leader, cx| {
9700 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9701 });
9702 follower
9703 .update(cx, |follower, cx| {
9704 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9705 })
9706 .unwrap()
9707 .await
9708 .unwrap();
9709 assert_eq!(
9710 follower
9711 .update(cx, |follower, cx| follower.scroll_position(cx))
9712 .unwrap(),
9713 gpui::Point::new(1.5, 3.5)
9714 );
9715 assert!(*is_still_following.borrow());
9716 assert_eq!(*follower_edit_event_count.borrow(), 0);
9717
9718 // Update the selections and scroll position. The follower's scroll position is updated
9719 // via autoscroll, not via the leader's exact scroll position.
9720 _ = leader.update(cx, |leader, cx| {
9721 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9722 leader.request_autoscroll(Autoscroll::newest(), cx);
9723 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9724 });
9725 follower
9726 .update(cx, |follower, cx| {
9727 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9728 })
9729 .unwrap()
9730 .await
9731 .unwrap();
9732 _ = follower.update(cx, |follower, cx| {
9733 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9734 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9735 });
9736 assert!(*is_still_following.borrow());
9737
9738 // Creating a pending selection that precedes another selection
9739 _ = leader.update(cx, |leader, cx| {
9740 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9741 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9742 });
9743 follower
9744 .update(cx, |follower, cx| {
9745 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9746 })
9747 .unwrap()
9748 .await
9749 .unwrap();
9750 _ = follower.update(cx, |follower, cx| {
9751 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9752 });
9753 assert!(*is_still_following.borrow());
9754
9755 // Extend the pending selection so that it surrounds another selection
9756 _ = leader.update(cx, |leader, cx| {
9757 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9758 });
9759 follower
9760 .update(cx, |follower, cx| {
9761 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9762 })
9763 .unwrap()
9764 .await
9765 .unwrap();
9766 _ = follower.update(cx, |follower, cx| {
9767 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9768 });
9769
9770 // Scrolling locally breaks the follow
9771 _ = follower.update(cx, |follower, cx| {
9772 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9773 follower.set_scroll_anchor(
9774 ScrollAnchor {
9775 anchor: top_anchor,
9776 offset: gpui::Point::new(0.0, 0.5),
9777 },
9778 cx,
9779 );
9780 });
9781 assert!(!(*is_still_following.borrow()));
9782}
9783
9784#[gpui::test]
9785async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9786 init_test(cx, |_| {});
9787
9788 let fs = FakeFs::new(cx.executor());
9789 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9790 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9791 let pane = workspace
9792 .update(cx, |workspace, _| workspace.active_pane().clone())
9793 .unwrap();
9794
9795 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9796
9797 let leader = pane.update(cx, |_, cx| {
9798 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9799 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9800 });
9801
9802 // Start following the editor when it has no excerpts.
9803 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9804 let follower_1 = cx
9805 .update_window(*workspace.deref(), |_, cx| {
9806 Editor::from_state_proto(
9807 workspace.root_view(cx).unwrap(),
9808 ViewId {
9809 creator: Default::default(),
9810 id: 0,
9811 },
9812 &mut state_message,
9813 cx,
9814 )
9815 })
9816 .unwrap()
9817 .unwrap()
9818 .await
9819 .unwrap();
9820
9821 let update_message = Rc::new(RefCell::new(None));
9822 follower_1.update(cx, {
9823 let update = update_message.clone();
9824 |_, cx| {
9825 cx.subscribe(&leader, move |_, leader, event, cx| {
9826 leader
9827 .read(cx)
9828 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9829 })
9830 .detach();
9831 }
9832 });
9833
9834 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9835 (
9836 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9837 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9838 )
9839 });
9840
9841 // Insert some excerpts.
9842 leader.update(cx, |leader, cx| {
9843 leader.buffer.update(cx, |multibuffer, cx| {
9844 let excerpt_ids = multibuffer.push_excerpts(
9845 buffer_1.clone(),
9846 [
9847 ExcerptRange {
9848 context: 1..6,
9849 primary: None,
9850 },
9851 ExcerptRange {
9852 context: 12..15,
9853 primary: None,
9854 },
9855 ExcerptRange {
9856 context: 0..3,
9857 primary: None,
9858 },
9859 ],
9860 cx,
9861 );
9862 multibuffer.insert_excerpts_after(
9863 excerpt_ids[0],
9864 buffer_2.clone(),
9865 [
9866 ExcerptRange {
9867 context: 8..12,
9868 primary: None,
9869 },
9870 ExcerptRange {
9871 context: 0..6,
9872 primary: None,
9873 },
9874 ],
9875 cx,
9876 );
9877 });
9878 });
9879
9880 // Apply the update of adding the excerpts.
9881 follower_1
9882 .update(cx, |follower, cx| {
9883 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9884 })
9885 .await
9886 .unwrap();
9887 assert_eq!(
9888 follower_1.update(cx, |editor, cx| editor.text(cx)),
9889 leader.update(cx, |editor, cx| editor.text(cx))
9890 );
9891 update_message.borrow_mut().take();
9892
9893 // Start following separately after it already has excerpts.
9894 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9895 let follower_2 = cx
9896 .update_window(*workspace.deref(), |_, cx| {
9897 Editor::from_state_proto(
9898 workspace.root_view(cx).unwrap().clone(),
9899 ViewId {
9900 creator: Default::default(),
9901 id: 0,
9902 },
9903 &mut state_message,
9904 cx,
9905 )
9906 })
9907 .unwrap()
9908 .unwrap()
9909 .await
9910 .unwrap();
9911 assert_eq!(
9912 follower_2.update(cx, |editor, cx| editor.text(cx)),
9913 leader.update(cx, |editor, cx| editor.text(cx))
9914 );
9915
9916 // Remove some excerpts.
9917 leader.update(cx, |leader, cx| {
9918 leader.buffer.update(cx, |multibuffer, cx| {
9919 let excerpt_ids = multibuffer.excerpt_ids();
9920 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9921 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9922 });
9923 });
9924
9925 // Apply the update of removing the excerpts.
9926 follower_1
9927 .update(cx, |follower, cx| {
9928 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9929 })
9930 .await
9931 .unwrap();
9932 follower_2
9933 .update(cx, |follower, cx| {
9934 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9935 })
9936 .await
9937 .unwrap();
9938 update_message.borrow_mut().take();
9939 assert_eq!(
9940 follower_1.update(cx, |editor, cx| editor.text(cx)),
9941 leader.update(cx, |editor, cx| editor.text(cx))
9942 );
9943}
9944
9945#[gpui::test]
9946async fn go_to_prev_overlapping_diagnostic(
9947 executor: BackgroundExecutor,
9948 cx: &mut gpui::TestAppContext,
9949) {
9950 init_test(cx, |_| {});
9951
9952 let mut cx = EditorTestContext::new(cx).await;
9953 let lsp_store =
9954 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9955
9956 cx.set_state(indoc! {"
9957 ˇfn func(abc def: i32) -> u32 {
9958 }
9959 "});
9960
9961 cx.update(|cx| {
9962 lsp_store.update(cx, |lsp_store, cx| {
9963 lsp_store
9964 .update_diagnostics(
9965 LanguageServerId(0),
9966 lsp::PublishDiagnosticsParams {
9967 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9968 version: None,
9969 diagnostics: vec![
9970 lsp::Diagnostic {
9971 range: lsp::Range::new(
9972 lsp::Position::new(0, 11),
9973 lsp::Position::new(0, 12),
9974 ),
9975 severity: Some(lsp::DiagnosticSeverity::ERROR),
9976 ..Default::default()
9977 },
9978 lsp::Diagnostic {
9979 range: lsp::Range::new(
9980 lsp::Position::new(0, 12),
9981 lsp::Position::new(0, 15),
9982 ),
9983 severity: Some(lsp::DiagnosticSeverity::ERROR),
9984 ..Default::default()
9985 },
9986 lsp::Diagnostic {
9987 range: lsp::Range::new(
9988 lsp::Position::new(0, 25),
9989 lsp::Position::new(0, 28),
9990 ),
9991 severity: Some(lsp::DiagnosticSeverity::ERROR),
9992 ..Default::default()
9993 },
9994 ],
9995 },
9996 &[],
9997 cx,
9998 )
9999 .unwrap()
10000 });
10001 });
10002
10003 executor.run_until_parked();
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 cx.update_editor(|editor, cx| {
10015 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10016 });
10017
10018 cx.assert_editor_state(indoc! {"
10019 fn func(abc ˇdef: i32) -> u32 {
10020 }
10021 "});
10022
10023 cx.update_editor(|editor, cx| {
10024 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10025 });
10026
10027 cx.assert_editor_state(indoc! {"
10028 fn func(abcˇ def: i32) -> u32 {
10029 }
10030 "});
10031
10032 cx.update_editor(|editor, cx| {
10033 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10034 });
10035
10036 cx.assert_editor_state(indoc! {"
10037 fn func(abc def: i32) -> ˇu32 {
10038 }
10039 "});
10040}
10041
10042#[gpui::test]
10043async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10044 init_test(cx, |_| {});
10045
10046 let mut cx = EditorTestContext::new(cx).await;
10047
10048 cx.set_state(indoc! {"
10049 fn func(abˇc def: i32) -> u32 {
10050 }
10051 "});
10052 let lsp_store =
10053 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10054
10055 cx.update(|cx| {
10056 lsp_store.update(cx, |lsp_store, cx| {
10057 lsp_store.update_diagnostics(
10058 LanguageServerId(0),
10059 lsp::PublishDiagnosticsParams {
10060 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10061 version: None,
10062 diagnostics: vec![lsp::Diagnostic {
10063 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10064 severity: Some(lsp::DiagnosticSeverity::ERROR),
10065 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10066 ..Default::default()
10067 }],
10068 },
10069 &[],
10070 cx,
10071 )
10072 })
10073 }).unwrap();
10074 cx.run_until_parked();
10075 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10076 cx.run_until_parked();
10077 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10078}
10079
10080#[gpui::test]
10081async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10082 init_test(cx, |_| {});
10083
10084 let mut cx = EditorTestContext::new(cx).await;
10085
10086 let diff_base = r#"
10087 use some::mod;
10088
10089 const A: u32 = 42;
10090
10091 fn main() {
10092 println!("hello");
10093
10094 println!("world");
10095 }
10096 "#
10097 .unindent();
10098
10099 // Edits are modified, removed, modified, added
10100 cx.set_state(
10101 &r#"
10102 use some::modified;
10103
10104 ˇ
10105 fn main() {
10106 println!("hello there");
10107
10108 println!("around the");
10109 println!("world");
10110 }
10111 "#
10112 .unindent(),
10113 );
10114
10115 cx.set_diff_base(&diff_base);
10116 executor.run_until_parked();
10117
10118 cx.update_editor(|editor, cx| {
10119 //Wrap around the bottom of the buffer
10120 for _ in 0..3 {
10121 editor.go_to_next_hunk(&GoToHunk, cx);
10122 }
10123 });
10124
10125 cx.assert_editor_state(
10126 &r#"
10127 ˇuse some::modified;
10128
10129
10130 fn main() {
10131 println!("hello there");
10132
10133 println!("around the");
10134 println!("world");
10135 }
10136 "#
10137 .unindent(),
10138 );
10139
10140 cx.update_editor(|editor, cx| {
10141 //Wrap around the top of the buffer
10142 for _ in 0..2 {
10143 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10144 }
10145 });
10146
10147 cx.assert_editor_state(
10148 &r#"
10149 use some::modified;
10150
10151
10152 fn main() {
10153 ˇ println!("hello there");
10154
10155 println!("around the");
10156 println!("world");
10157 }
10158 "#
10159 .unindent(),
10160 );
10161
10162 cx.update_editor(|editor, cx| {
10163 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10164 });
10165
10166 cx.assert_editor_state(
10167 &r#"
10168 use some::modified;
10169
10170 ˇ
10171 fn main() {
10172 println!("hello there");
10173
10174 println!("around the");
10175 println!("world");
10176 }
10177 "#
10178 .unindent(),
10179 );
10180
10181 cx.update_editor(|editor, cx| {
10182 for _ in 0..3 {
10183 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10184 }
10185 });
10186
10187 cx.assert_editor_state(
10188 &r#"
10189 use some::modified;
10190
10191
10192 fn main() {
10193 ˇ println!("hello there");
10194
10195 println!("around the");
10196 println!("world");
10197 }
10198 "#
10199 .unindent(),
10200 );
10201
10202 cx.update_editor(|editor, cx| {
10203 editor.fold(&Fold, cx);
10204
10205 //Make sure that the fold only gets one hunk
10206 for _ in 0..4 {
10207 editor.go_to_next_hunk(&GoToHunk, cx);
10208 }
10209 });
10210
10211 cx.assert_editor_state(
10212 &r#"
10213 ˇuse some::modified;
10214
10215
10216 fn main() {
10217 println!("hello there");
10218
10219 println!("around the");
10220 println!("world");
10221 }
10222 "#
10223 .unindent(),
10224 );
10225}
10226
10227#[test]
10228fn test_split_words() {
10229 fn split(text: &str) -> Vec<&str> {
10230 split_words(text).collect()
10231 }
10232
10233 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10234 assert_eq!(split("hello_world"), &["hello_", "world"]);
10235 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10236 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10237 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10238 assert_eq!(split("helloworld"), &["helloworld"]);
10239
10240 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10241}
10242
10243#[gpui::test]
10244async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10245 init_test(cx, |_| {});
10246
10247 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10248 let mut assert = |before, after| {
10249 let _state_context = cx.set_state(before);
10250 cx.update_editor(|editor, cx| {
10251 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10252 });
10253 cx.assert_editor_state(after);
10254 };
10255
10256 // Outside bracket jumps to outside of matching bracket
10257 assert("console.logˇ(var);", "console.log(var)ˇ;");
10258 assert("console.log(var)ˇ;", "console.logˇ(var);");
10259
10260 // Inside bracket jumps to inside of matching bracket
10261 assert("console.log(ˇvar);", "console.log(varˇ);");
10262 assert("console.log(varˇ);", "console.log(ˇvar);");
10263
10264 // When outside a bracket and inside, favor jumping to the inside bracket
10265 assert(
10266 "console.log('foo', [1, 2, 3]ˇ);",
10267 "console.log(ˇ'foo', [1, 2, 3]);",
10268 );
10269 assert(
10270 "console.log(ˇ'foo', [1, 2, 3]);",
10271 "console.log('foo', [1, 2, 3]ˇ);",
10272 );
10273
10274 // Bias forward if two options are equally likely
10275 assert(
10276 "let result = curried_fun()ˇ();",
10277 "let result = curried_fun()()ˇ;",
10278 );
10279
10280 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10281 assert(
10282 indoc! {"
10283 function test() {
10284 console.log('test')ˇ
10285 }"},
10286 indoc! {"
10287 function test() {
10288 console.logˇ('test')
10289 }"},
10290 );
10291}
10292
10293#[gpui::test]
10294async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10295 init_test(cx, |_| {});
10296
10297 let fs = FakeFs::new(cx.executor());
10298 fs.insert_tree(
10299 "/a",
10300 json!({
10301 "main.rs": "fn main() { let a = 5; }",
10302 "other.rs": "// Test file",
10303 }),
10304 )
10305 .await;
10306 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10307
10308 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10309 language_registry.add(Arc::new(Language::new(
10310 LanguageConfig {
10311 name: "Rust".into(),
10312 matcher: LanguageMatcher {
10313 path_suffixes: vec!["rs".to_string()],
10314 ..Default::default()
10315 },
10316 brackets: BracketPairConfig {
10317 pairs: vec![BracketPair {
10318 start: "{".to_string(),
10319 end: "}".to_string(),
10320 close: true,
10321 surround: true,
10322 newline: true,
10323 }],
10324 disabled_scopes_by_bracket_ix: Vec::new(),
10325 },
10326 ..Default::default()
10327 },
10328 Some(tree_sitter_rust::LANGUAGE.into()),
10329 )));
10330 let mut fake_servers = language_registry.register_fake_lsp(
10331 "Rust",
10332 FakeLspAdapter {
10333 capabilities: lsp::ServerCapabilities {
10334 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10335 first_trigger_character: "{".to_string(),
10336 more_trigger_character: None,
10337 }),
10338 ..Default::default()
10339 },
10340 ..Default::default()
10341 },
10342 );
10343
10344 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10345
10346 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10347
10348 let worktree_id = workspace
10349 .update(cx, |workspace, cx| {
10350 workspace.project().update(cx, |project, cx| {
10351 project.worktrees(cx).next().unwrap().read(cx).id()
10352 })
10353 })
10354 .unwrap();
10355
10356 let buffer = project
10357 .update(cx, |project, cx| {
10358 project.open_local_buffer("/a/main.rs", cx)
10359 })
10360 .await
10361 .unwrap();
10362 let editor_handle = workspace
10363 .update(cx, |workspace, cx| {
10364 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10365 })
10366 .unwrap()
10367 .await
10368 .unwrap()
10369 .downcast::<Editor>()
10370 .unwrap();
10371
10372 cx.executor().start_waiting();
10373 let fake_server = fake_servers.next().await.unwrap();
10374
10375 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10376 assert_eq!(
10377 params.text_document_position.text_document.uri,
10378 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10379 );
10380 assert_eq!(
10381 params.text_document_position.position,
10382 lsp::Position::new(0, 21),
10383 );
10384
10385 Ok(Some(vec![lsp::TextEdit {
10386 new_text: "]".to_string(),
10387 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10388 }]))
10389 });
10390
10391 editor_handle.update(cx, |editor, cx| {
10392 editor.focus(cx);
10393 editor.change_selections(None, cx, |s| {
10394 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10395 });
10396 editor.handle_input("{", cx);
10397 });
10398
10399 cx.executor().run_until_parked();
10400
10401 buffer.update(cx, |buffer, _| {
10402 assert_eq!(
10403 buffer.text(),
10404 "fn main() { let a = {5}; }",
10405 "No extra braces from on type formatting should appear in the buffer"
10406 )
10407 });
10408}
10409
10410#[gpui::test]
10411async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10412 init_test(cx, |_| {});
10413
10414 let fs = FakeFs::new(cx.executor());
10415 fs.insert_tree(
10416 "/a",
10417 json!({
10418 "main.rs": "fn main() { let a = 5; }",
10419 "other.rs": "// Test file",
10420 }),
10421 )
10422 .await;
10423
10424 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10425
10426 let server_restarts = Arc::new(AtomicUsize::new(0));
10427 let closure_restarts = Arc::clone(&server_restarts);
10428 let language_server_name = "test language server";
10429 let language_name: LanguageName = "Rust".into();
10430
10431 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10432 language_registry.add(Arc::new(Language::new(
10433 LanguageConfig {
10434 name: language_name.clone(),
10435 matcher: LanguageMatcher {
10436 path_suffixes: vec!["rs".to_string()],
10437 ..Default::default()
10438 },
10439 ..Default::default()
10440 },
10441 Some(tree_sitter_rust::LANGUAGE.into()),
10442 )));
10443 let mut fake_servers = language_registry.register_fake_lsp(
10444 "Rust",
10445 FakeLspAdapter {
10446 name: language_server_name,
10447 initialization_options: Some(json!({
10448 "testOptionValue": true
10449 })),
10450 initializer: Some(Box::new(move |fake_server| {
10451 let task_restarts = Arc::clone(&closure_restarts);
10452 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10453 task_restarts.fetch_add(1, atomic::Ordering::Release);
10454 futures::future::ready(Ok(()))
10455 });
10456 })),
10457 ..Default::default()
10458 },
10459 );
10460
10461 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10462 let _buffer = project
10463 .update(cx, |project, cx| {
10464 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10465 })
10466 .await
10467 .unwrap();
10468 let _fake_server = fake_servers.next().await.unwrap();
10469 update_test_language_settings(cx, |language_settings| {
10470 language_settings.languages.insert(
10471 language_name.clone(),
10472 LanguageSettingsContent {
10473 tab_size: NonZeroU32::new(8),
10474 ..Default::default()
10475 },
10476 );
10477 });
10478 cx.executor().run_until_parked();
10479 assert_eq!(
10480 server_restarts.load(atomic::Ordering::Acquire),
10481 0,
10482 "Should not restart LSP server on an unrelated change"
10483 );
10484
10485 update_test_project_settings(cx, |project_settings| {
10486 project_settings.lsp.insert(
10487 "Some other server name".into(),
10488 LspSettings {
10489 binary: None,
10490 settings: None,
10491 initialization_options: Some(json!({
10492 "some other init value": false
10493 })),
10494 },
10495 );
10496 });
10497 cx.executor().run_until_parked();
10498 assert_eq!(
10499 server_restarts.load(atomic::Ordering::Acquire),
10500 0,
10501 "Should not restart LSP server on an unrelated LSP settings change"
10502 );
10503
10504 update_test_project_settings(cx, |project_settings| {
10505 project_settings.lsp.insert(
10506 language_server_name.into(),
10507 LspSettings {
10508 binary: None,
10509 settings: None,
10510 initialization_options: Some(json!({
10511 "anotherInitValue": false
10512 })),
10513 },
10514 );
10515 });
10516 cx.executor().run_until_parked();
10517 assert_eq!(
10518 server_restarts.load(atomic::Ordering::Acquire),
10519 1,
10520 "Should restart LSP server on a related LSP settings change"
10521 );
10522
10523 update_test_project_settings(cx, |project_settings| {
10524 project_settings.lsp.insert(
10525 language_server_name.into(),
10526 LspSettings {
10527 binary: None,
10528 settings: None,
10529 initialization_options: Some(json!({
10530 "anotherInitValue": false
10531 })),
10532 },
10533 );
10534 });
10535 cx.executor().run_until_parked();
10536 assert_eq!(
10537 server_restarts.load(atomic::Ordering::Acquire),
10538 1,
10539 "Should not restart LSP server on a related LSP settings change that is the same"
10540 );
10541
10542 update_test_project_settings(cx, |project_settings| {
10543 project_settings.lsp.insert(
10544 language_server_name.into(),
10545 LspSettings {
10546 binary: None,
10547 settings: None,
10548 initialization_options: None,
10549 },
10550 );
10551 });
10552 cx.executor().run_until_parked();
10553 assert_eq!(
10554 server_restarts.load(atomic::Ordering::Acquire),
10555 2,
10556 "Should restart LSP server on another related LSP settings change"
10557 );
10558}
10559
10560#[gpui::test]
10561async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10562 init_test(cx, |_| {});
10563
10564 let mut cx = EditorLspTestContext::new_rust(
10565 lsp::ServerCapabilities {
10566 completion_provider: Some(lsp::CompletionOptions {
10567 trigger_characters: Some(vec![".".to_string()]),
10568 resolve_provider: Some(true),
10569 ..Default::default()
10570 }),
10571 ..Default::default()
10572 },
10573 cx,
10574 )
10575 .await;
10576
10577 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10578 cx.simulate_keystroke(".");
10579 let completion_item = lsp::CompletionItem {
10580 label: "some".into(),
10581 kind: Some(lsp::CompletionItemKind::SNIPPET),
10582 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10583 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10584 kind: lsp::MarkupKind::Markdown,
10585 value: "```rust\nSome(2)\n```".to_string(),
10586 })),
10587 deprecated: Some(false),
10588 sort_text: Some("fffffff2".to_string()),
10589 filter_text: Some("some".to_string()),
10590 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10591 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10592 range: lsp::Range {
10593 start: lsp::Position {
10594 line: 0,
10595 character: 22,
10596 },
10597 end: lsp::Position {
10598 line: 0,
10599 character: 22,
10600 },
10601 },
10602 new_text: "Some(2)".to_string(),
10603 })),
10604 additional_text_edits: Some(vec![lsp::TextEdit {
10605 range: lsp::Range {
10606 start: lsp::Position {
10607 line: 0,
10608 character: 20,
10609 },
10610 end: lsp::Position {
10611 line: 0,
10612 character: 22,
10613 },
10614 },
10615 new_text: "".to_string(),
10616 }]),
10617 ..Default::default()
10618 };
10619
10620 let closure_completion_item = completion_item.clone();
10621 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10622 let task_completion_item = closure_completion_item.clone();
10623 async move {
10624 Ok(Some(lsp::CompletionResponse::Array(vec![
10625 task_completion_item,
10626 ])))
10627 }
10628 });
10629
10630 request.next().await;
10631
10632 cx.condition(|editor, _| editor.context_menu_visible())
10633 .await;
10634 let apply_additional_edits = cx.update_editor(|editor, cx| {
10635 editor
10636 .confirm_completion(&ConfirmCompletion::default(), cx)
10637 .unwrap()
10638 });
10639 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10640
10641 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10642 let task_completion_item = completion_item.clone();
10643 async move { Ok(task_completion_item) }
10644 })
10645 .next()
10646 .await
10647 .unwrap();
10648 apply_additional_edits.await.unwrap();
10649 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10650}
10651
10652#[gpui::test]
10653async fn test_completions_resolve_updates_labels_if_filter_text_matches(
10654 cx: &mut gpui::TestAppContext,
10655) {
10656 init_test(cx, |_| {});
10657
10658 let mut cx = EditorLspTestContext::new_rust(
10659 lsp::ServerCapabilities {
10660 completion_provider: Some(lsp::CompletionOptions {
10661 trigger_characters: Some(vec![".".to_string()]),
10662 resolve_provider: Some(true),
10663 ..Default::default()
10664 }),
10665 ..Default::default()
10666 },
10667 cx,
10668 )
10669 .await;
10670
10671 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10672 cx.simulate_keystroke(".");
10673
10674 let item1 = lsp::CompletionItem {
10675 label: "id".to_string(),
10676 filter_text: Some("id".to_string()),
10677 detail: None,
10678 documentation: None,
10679 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10680 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10681 new_text: ".id".to_string(),
10682 })),
10683 ..lsp::CompletionItem::default()
10684 };
10685
10686 let item2 = lsp::CompletionItem {
10687 label: "other".to_string(),
10688 filter_text: Some("other".to_string()),
10689 detail: None,
10690 documentation: None,
10691 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10692 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10693 new_text: ".other".to_string(),
10694 })),
10695 ..lsp::CompletionItem::default()
10696 };
10697
10698 let item1 = item1.clone();
10699 cx.handle_request::<lsp::request::Completion, _, _>({
10700 let item1 = item1.clone();
10701 move |_, _, _| {
10702 let item1 = item1.clone();
10703 let item2 = item2.clone();
10704 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
10705 }
10706 })
10707 .next()
10708 .await;
10709
10710 cx.condition(|editor, _| editor.context_menu_visible())
10711 .await;
10712 cx.update_editor(|editor, _| {
10713 let context_menu = editor.context_menu.borrow_mut();
10714 let context_menu = context_menu
10715 .as_ref()
10716 .expect("Should have the context menu deployed");
10717 match context_menu {
10718 CodeContextMenu::Completions(completions_menu) => {
10719 let completions = completions_menu.completions.borrow_mut();
10720 assert_eq!(
10721 completions
10722 .iter()
10723 .map(|completion| &completion.label.text)
10724 .collect::<Vec<_>>(),
10725 vec!["id", "other"]
10726 )
10727 }
10728 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10729 }
10730 });
10731
10732 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
10733 let item1 = item1.clone();
10734 move |_, item_to_resolve, _| {
10735 let item1 = item1.clone();
10736 async move {
10737 if item1 == item_to_resolve {
10738 Ok(lsp::CompletionItem {
10739 label: "method id()".to_string(),
10740 filter_text: Some("id".to_string()),
10741 detail: Some("Now resolved!".to_string()),
10742 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10743 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10744 range: lsp::Range::new(
10745 lsp::Position::new(0, 22),
10746 lsp::Position::new(0, 22),
10747 ),
10748 new_text: ".id".to_string(),
10749 })),
10750 ..lsp::CompletionItem::default()
10751 })
10752 } else {
10753 Ok(item_to_resolve)
10754 }
10755 }
10756 }
10757 })
10758 .next()
10759 .await
10760 .unwrap();
10761 cx.run_until_parked();
10762
10763 cx.update_editor(|editor, cx| {
10764 editor.context_menu_next(&Default::default(), cx);
10765 });
10766
10767 cx.update_editor(|editor, _| {
10768 let context_menu = editor.context_menu.borrow_mut();
10769 let context_menu = context_menu
10770 .as_ref()
10771 .expect("Should have the context menu deployed");
10772 match context_menu {
10773 CodeContextMenu::Completions(completions_menu) => {
10774 let completions = completions_menu.completions.borrow_mut();
10775 assert_eq!(
10776 completions
10777 .iter()
10778 .map(|completion| &completion.label.text)
10779 .collect::<Vec<_>>(),
10780 vec!["method id()", "other"],
10781 "Should update first completion label, but not second as the filter text did not match."
10782 );
10783 }
10784 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10785 }
10786 });
10787}
10788
10789#[gpui::test]
10790async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
10791 init_test(cx, |_| {});
10792
10793 let mut cx = EditorLspTestContext::new_rust(
10794 lsp::ServerCapabilities {
10795 completion_provider: Some(lsp::CompletionOptions {
10796 trigger_characters: Some(vec![".".to_string()]),
10797 resolve_provider: Some(true),
10798 ..Default::default()
10799 }),
10800 ..Default::default()
10801 },
10802 cx,
10803 )
10804 .await;
10805
10806 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10807 cx.simulate_keystroke(".");
10808
10809 let unresolved_item_1 = lsp::CompletionItem {
10810 label: "id".to_string(),
10811 filter_text: Some("id".to_string()),
10812 detail: None,
10813 documentation: None,
10814 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10815 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10816 new_text: ".id".to_string(),
10817 })),
10818 ..lsp::CompletionItem::default()
10819 };
10820 let resolved_item_1 = lsp::CompletionItem {
10821 additional_text_edits: Some(vec![lsp::TextEdit {
10822 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
10823 new_text: "!!".to_string(),
10824 }]),
10825 ..unresolved_item_1.clone()
10826 };
10827 let unresolved_item_2 = lsp::CompletionItem {
10828 label: "other".to_string(),
10829 filter_text: Some("other".to_string()),
10830 detail: None,
10831 documentation: None,
10832 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10833 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10834 new_text: ".other".to_string(),
10835 })),
10836 ..lsp::CompletionItem::default()
10837 };
10838 let resolved_item_2 = lsp::CompletionItem {
10839 additional_text_edits: Some(vec![lsp::TextEdit {
10840 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
10841 new_text: "??".to_string(),
10842 }]),
10843 ..unresolved_item_2.clone()
10844 };
10845
10846 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
10847 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
10848 cx.lsp
10849 .server
10850 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10851 let unresolved_item_1 = unresolved_item_1.clone();
10852 let resolved_item_1 = resolved_item_1.clone();
10853 let unresolved_item_2 = unresolved_item_2.clone();
10854 let resolved_item_2 = resolved_item_2.clone();
10855 let resolve_requests_1 = resolve_requests_1.clone();
10856 let resolve_requests_2 = resolve_requests_2.clone();
10857 move |unresolved_request, _| {
10858 let unresolved_item_1 = unresolved_item_1.clone();
10859 let resolved_item_1 = resolved_item_1.clone();
10860 let unresolved_item_2 = unresolved_item_2.clone();
10861 let resolved_item_2 = resolved_item_2.clone();
10862 let resolve_requests_1 = resolve_requests_1.clone();
10863 let resolve_requests_2 = resolve_requests_2.clone();
10864 async move {
10865 if unresolved_request == unresolved_item_1 {
10866 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
10867 Ok(resolved_item_1.clone())
10868 } else if unresolved_request == unresolved_item_2 {
10869 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
10870 Ok(resolved_item_2.clone())
10871 } else {
10872 panic!("Unexpected completion item {unresolved_request:?}")
10873 }
10874 }
10875 }
10876 })
10877 .detach();
10878
10879 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10880 let unresolved_item_1 = unresolved_item_1.clone();
10881 let unresolved_item_2 = unresolved_item_2.clone();
10882 async move {
10883 Ok(Some(lsp::CompletionResponse::Array(vec![
10884 unresolved_item_1,
10885 unresolved_item_2,
10886 ])))
10887 }
10888 })
10889 .next()
10890 .await;
10891
10892 cx.condition(|editor, _| editor.context_menu_visible())
10893 .await;
10894 cx.update_editor(|editor, _| {
10895 let context_menu = editor.context_menu.borrow_mut();
10896 let context_menu = context_menu
10897 .as_ref()
10898 .expect("Should have the context menu deployed");
10899 match context_menu {
10900 CodeContextMenu::Completions(completions_menu) => {
10901 let completions = completions_menu.completions.borrow_mut();
10902 assert_eq!(
10903 completions
10904 .iter()
10905 .map(|completion| &completion.label.text)
10906 .collect::<Vec<_>>(),
10907 vec!["id", "other"]
10908 )
10909 }
10910 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10911 }
10912 });
10913 cx.run_until_parked();
10914
10915 cx.update_editor(|editor, cx| {
10916 editor.context_menu_next(&ContextMenuNext, cx);
10917 });
10918 cx.run_until_parked();
10919 cx.update_editor(|editor, cx| {
10920 editor.context_menu_prev(&ContextMenuPrev, cx);
10921 });
10922 cx.run_until_parked();
10923 cx.update_editor(|editor, cx| {
10924 editor.context_menu_next(&ContextMenuNext, cx);
10925 });
10926 cx.run_until_parked();
10927 cx.update_editor(|editor, cx| {
10928 editor
10929 .compose_completion(&ComposeCompletion::default(), cx)
10930 .expect("No task returned")
10931 })
10932 .await
10933 .expect("Completion failed");
10934 cx.run_until_parked();
10935
10936 cx.update_editor(|editor, cx| {
10937 assert_eq!(
10938 resolve_requests_1.load(atomic::Ordering::Acquire),
10939 1,
10940 "Should always resolve once despite multiple selections"
10941 );
10942 assert_eq!(
10943 resolve_requests_2.load(atomic::Ordering::Acquire),
10944 1,
10945 "Should always resolve once after multiple selections and applying the completion"
10946 );
10947 assert_eq!(
10948 editor.text(cx),
10949 "fn main() { let a = ??.other; }",
10950 "Should use resolved data when applying the completion"
10951 );
10952 });
10953}
10954
10955#[gpui::test]
10956async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10957 init_test(cx, |_| {});
10958
10959 let item_0 = lsp::CompletionItem {
10960 label: "abs".into(),
10961 insert_text: Some("abs".into()),
10962 data: Some(json!({ "very": "special"})),
10963 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10964 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10965 lsp::InsertReplaceEdit {
10966 new_text: "abs".to_string(),
10967 insert: lsp::Range::default(),
10968 replace: lsp::Range::default(),
10969 },
10970 )),
10971 ..lsp::CompletionItem::default()
10972 };
10973 let items = iter::once(item_0.clone())
10974 .chain((11..51).map(|i| lsp::CompletionItem {
10975 label: format!("item_{}", i),
10976 insert_text: Some(format!("item_{}", i)),
10977 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10978 ..lsp::CompletionItem::default()
10979 }))
10980 .collect::<Vec<_>>();
10981
10982 let default_commit_characters = vec!["?".to_string()];
10983 let default_data = json!({ "default": "data"});
10984 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10985 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10986 let default_edit_range = lsp::Range {
10987 start: lsp::Position {
10988 line: 0,
10989 character: 5,
10990 },
10991 end: lsp::Position {
10992 line: 0,
10993 character: 5,
10994 },
10995 };
10996
10997 let item_0_out = lsp::CompletionItem {
10998 commit_characters: Some(default_commit_characters.clone()),
10999 insert_text_format: Some(default_insert_text_format),
11000 ..item_0
11001 };
11002 let items_out = iter::once(item_0_out)
11003 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11004 commit_characters: Some(default_commit_characters.clone()),
11005 data: Some(default_data.clone()),
11006 insert_text_mode: Some(default_insert_text_mode),
11007 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11008 range: default_edit_range,
11009 new_text: item.label.clone(),
11010 })),
11011 ..item.clone()
11012 }))
11013 .collect::<Vec<lsp::CompletionItem>>();
11014
11015 let mut cx = EditorLspTestContext::new_rust(
11016 lsp::ServerCapabilities {
11017 completion_provider: Some(lsp::CompletionOptions {
11018 trigger_characters: Some(vec![".".to_string()]),
11019 resolve_provider: Some(true),
11020 ..Default::default()
11021 }),
11022 ..Default::default()
11023 },
11024 cx,
11025 )
11026 .await;
11027
11028 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11029 cx.simulate_keystroke(".");
11030
11031 let completion_data = default_data.clone();
11032 let completion_characters = default_commit_characters.clone();
11033 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11034 let default_data = completion_data.clone();
11035 let default_commit_characters = completion_characters.clone();
11036 let items = items.clone();
11037 async move {
11038 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11039 items,
11040 item_defaults: Some(lsp::CompletionListItemDefaults {
11041 data: Some(default_data.clone()),
11042 commit_characters: Some(default_commit_characters.clone()),
11043 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11044 default_edit_range,
11045 )),
11046 insert_text_format: Some(default_insert_text_format),
11047 insert_text_mode: Some(default_insert_text_mode),
11048 }),
11049 ..lsp::CompletionList::default()
11050 })))
11051 }
11052 })
11053 .next()
11054 .await;
11055
11056 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11057 cx.lsp
11058 .server
11059 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11060 let closure_resolved_items = resolved_items.clone();
11061 move |item_to_resolve, _| {
11062 let closure_resolved_items = closure_resolved_items.clone();
11063 async move {
11064 closure_resolved_items.lock().push(item_to_resolve.clone());
11065 Ok(item_to_resolve)
11066 }
11067 }
11068 })
11069 .detach();
11070
11071 cx.condition(|editor, _| editor.context_menu_visible())
11072 .await;
11073 cx.run_until_parked();
11074 cx.update_editor(|editor, _| {
11075 let menu = editor.context_menu.borrow_mut();
11076 match menu.as_ref().expect("should have the completions menu") {
11077 CodeContextMenu::Completions(completions_menu) => {
11078 assert_eq!(
11079 completions_menu
11080 .entries
11081 .borrow()
11082 .iter()
11083 .flat_map(|c| match c {
11084 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11085 _ => None,
11086 })
11087 .collect::<Vec<String>>(),
11088 items_out
11089 .iter()
11090 .map(|completion| completion.label.clone())
11091 .collect::<Vec<String>>()
11092 );
11093 }
11094 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11095 }
11096 });
11097 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11098 // with 4 from the end.
11099 assert_eq!(
11100 *resolved_items.lock(),
11101 [
11102 &items_out[0..16],
11103 &items_out[items_out.len() - 4..items_out.len()]
11104 ]
11105 .concat()
11106 .iter()
11107 .cloned()
11108 .collect::<Vec<lsp::CompletionItem>>()
11109 );
11110 resolved_items.lock().clear();
11111
11112 cx.update_editor(|editor, cx| {
11113 editor.context_menu_prev(&ContextMenuPrev, cx);
11114 });
11115 cx.run_until_parked();
11116 // Completions that have already been resolved are skipped.
11117 assert_eq!(
11118 *resolved_items.lock(),
11119 items_out[items_out.len() - 16..items_out.len() - 4]
11120 .iter()
11121 .cloned()
11122 .collect::<Vec<lsp::CompletionItem>>()
11123 );
11124 resolved_items.lock().clear();
11125}
11126
11127#[gpui::test]
11128async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11129 init_test(cx, |_| {});
11130
11131 let mut cx = EditorLspTestContext::new(
11132 Language::new(
11133 LanguageConfig {
11134 matcher: LanguageMatcher {
11135 path_suffixes: vec!["jsx".into()],
11136 ..Default::default()
11137 },
11138 overrides: [(
11139 "element".into(),
11140 LanguageConfigOverride {
11141 word_characters: Override::Set(['-'].into_iter().collect()),
11142 ..Default::default()
11143 },
11144 )]
11145 .into_iter()
11146 .collect(),
11147 ..Default::default()
11148 },
11149 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11150 )
11151 .with_override_query("(jsx_self_closing_element) @element")
11152 .unwrap(),
11153 lsp::ServerCapabilities {
11154 completion_provider: Some(lsp::CompletionOptions {
11155 trigger_characters: Some(vec![":".to_string()]),
11156 ..Default::default()
11157 }),
11158 ..Default::default()
11159 },
11160 cx,
11161 )
11162 .await;
11163
11164 cx.lsp
11165 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11166 Ok(Some(lsp::CompletionResponse::Array(vec![
11167 lsp::CompletionItem {
11168 label: "bg-blue".into(),
11169 ..Default::default()
11170 },
11171 lsp::CompletionItem {
11172 label: "bg-red".into(),
11173 ..Default::default()
11174 },
11175 lsp::CompletionItem {
11176 label: "bg-yellow".into(),
11177 ..Default::default()
11178 },
11179 ])))
11180 });
11181
11182 cx.set_state(r#"<p class="bgˇ" />"#);
11183
11184 // Trigger completion when typing a dash, because the dash is an extra
11185 // word character in the 'element' scope, which contains the cursor.
11186 cx.simulate_keystroke("-");
11187 cx.executor().run_until_parked();
11188 cx.update_editor(|editor, _| {
11189 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11190 {
11191 assert_eq!(
11192 completion_menu_entries(&menu),
11193 &["bg-red", "bg-blue", "bg-yellow"]
11194 );
11195 } else {
11196 panic!("expected completion menu to be open");
11197 }
11198 });
11199
11200 cx.simulate_keystroke("l");
11201 cx.executor().run_until_parked();
11202 cx.update_editor(|editor, _| {
11203 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11204 {
11205 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
11206 } else {
11207 panic!("expected completion menu to be open");
11208 }
11209 });
11210
11211 // When filtering completions, consider the character after the '-' to
11212 // be the start of a subword.
11213 cx.set_state(r#"<p class="yelˇ" />"#);
11214 cx.simulate_keystroke("l");
11215 cx.executor().run_until_parked();
11216 cx.update_editor(|editor, _| {
11217 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11218 {
11219 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
11220 } else {
11221 panic!("expected completion menu to be open");
11222 }
11223 });
11224}
11225
11226fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
11227 let entries = menu.entries.borrow();
11228 entries
11229 .iter()
11230 .flat_map(|e| match e {
11231 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11232 _ => None,
11233 })
11234 .collect()
11235}
11236
11237#[gpui::test]
11238async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11239 init_test(cx, |settings| {
11240 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11241 FormatterList(vec![Formatter::Prettier].into()),
11242 ))
11243 });
11244
11245 let fs = FakeFs::new(cx.executor());
11246 fs.insert_file("/file.ts", Default::default()).await;
11247
11248 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11249 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11250
11251 language_registry.add(Arc::new(Language::new(
11252 LanguageConfig {
11253 name: "TypeScript".into(),
11254 matcher: LanguageMatcher {
11255 path_suffixes: vec!["ts".to_string()],
11256 ..Default::default()
11257 },
11258 ..Default::default()
11259 },
11260 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11261 )));
11262 update_test_language_settings(cx, |settings| {
11263 settings.defaults.prettier = Some(PrettierSettings {
11264 allowed: true,
11265 ..PrettierSettings::default()
11266 });
11267 });
11268
11269 let test_plugin = "test_plugin";
11270 let _ = language_registry.register_fake_lsp(
11271 "TypeScript",
11272 FakeLspAdapter {
11273 prettier_plugins: vec![test_plugin],
11274 ..Default::default()
11275 },
11276 );
11277
11278 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11279 let buffer = project
11280 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11281 .await
11282 .unwrap();
11283
11284 let buffer_text = "one\ntwo\nthree\n";
11285 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11286 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11287 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11288
11289 editor
11290 .update(cx, |editor, cx| {
11291 editor.perform_format(
11292 project.clone(),
11293 FormatTrigger::Manual,
11294 FormatTarget::Buffers,
11295 cx,
11296 )
11297 })
11298 .unwrap()
11299 .await;
11300 assert_eq!(
11301 editor.update(cx, |editor, cx| editor.text(cx)),
11302 buffer_text.to_string() + prettier_format_suffix,
11303 "Test prettier formatting was not applied to the original buffer text",
11304 );
11305
11306 update_test_language_settings(cx, |settings| {
11307 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11308 });
11309 let format = editor.update(cx, |editor, cx| {
11310 editor.perform_format(
11311 project.clone(),
11312 FormatTrigger::Manual,
11313 FormatTarget::Buffers,
11314 cx,
11315 )
11316 });
11317 format.await.unwrap();
11318 assert_eq!(
11319 editor.update(cx, |editor, cx| editor.text(cx)),
11320 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11321 "Autoformatting (via test prettier) was not applied to the original buffer text",
11322 );
11323}
11324
11325#[gpui::test]
11326async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11327 init_test(cx, |_| {});
11328 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11329 let base_text = indoc! {r#"
11330 struct Row;
11331 struct Row1;
11332 struct Row2;
11333
11334 struct Row4;
11335 struct Row5;
11336 struct Row6;
11337
11338 struct Row8;
11339 struct Row9;
11340 struct Row10;"#};
11341
11342 // When addition hunks are not adjacent to carets, no hunk revert is performed
11343 assert_hunk_revert(
11344 indoc! {r#"struct Row;
11345 struct Row1;
11346 struct Row1.1;
11347 struct Row1.2;
11348 struct Row2;ˇ
11349
11350 struct Row4;
11351 struct Row5;
11352 struct Row6;
11353
11354 struct Row8;
11355 ˇstruct Row9;
11356 struct Row9.1;
11357 struct Row9.2;
11358 struct Row9.3;
11359 struct Row10;"#},
11360 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11361 indoc! {r#"struct Row;
11362 struct Row1;
11363 struct Row1.1;
11364 struct Row1.2;
11365 struct Row2;ˇ
11366
11367 struct Row4;
11368 struct Row5;
11369 struct Row6;
11370
11371 struct Row8;
11372 ˇstruct Row9;
11373 struct Row9.1;
11374 struct Row9.2;
11375 struct Row9.3;
11376 struct Row10;"#},
11377 base_text,
11378 &mut cx,
11379 );
11380 // Same for selections
11381 assert_hunk_revert(
11382 indoc! {r#"struct Row;
11383 struct Row1;
11384 struct Row2;
11385 struct Row2.1;
11386 struct Row2.2;
11387 «ˇ
11388 struct Row4;
11389 struct» Row5;
11390 «struct Row6;
11391 ˇ»
11392 struct Row9.1;
11393 struct Row9.2;
11394 struct Row9.3;
11395 struct Row8;
11396 struct Row9;
11397 struct Row10;"#},
11398 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11399 indoc! {r#"struct Row;
11400 struct Row1;
11401 struct Row2;
11402 struct Row2.1;
11403 struct Row2.2;
11404 «ˇ
11405 struct Row4;
11406 struct» Row5;
11407 «struct Row6;
11408 ˇ»
11409 struct Row9.1;
11410 struct Row9.2;
11411 struct Row9.3;
11412 struct Row8;
11413 struct Row9;
11414 struct Row10;"#},
11415 base_text,
11416 &mut cx,
11417 );
11418
11419 // When carets and selections intersect the addition hunks, those are reverted.
11420 // Adjacent carets got merged.
11421 assert_hunk_revert(
11422 indoc! {r#"struct Row;
11423 ˇ// something on the top
11424 struct Row1;
11425 struct Row2;
11426 struct Roˇw3.1;
11427 struct Row2.2;
11428 struct Row2.3;ˇ
11429
11430 struct Row4;
11431 struct ˇRow5.1;
11432 struct Row5.2;
11433 struct «Rowˇ»5.3;
11434 struct Row5;
11435 struct Row6;
11436 ˇ
11437 struct Row9.1;
11438 struct «Rowˇ»9.2;
11439 struct «ˇRow»9.3;
11440 struct Row8;
11441 struct Row9;
11442 «ˇ// something on bottom»
11443 struct Row10;"#},
11444 vec![
11445 DiffHunkStatus::Added,
11446 DiffHunkStatus::Added,
11447 DiffHunkStatus::Added,
11448 DiffHunkStatus::Added,
11449 DiffHunkStatus::Added,
11450 ],
11451 indoc! {r#"struct Row;
11452 ˇstruct Row1;
11453 struct Row2;
11454 ˇ
11455 struct Row4;
11456 ˇstruct Row5;
11457 struct Row6;
11458 ˇ
11459 ˇstruct Row8;
11460 struct Row9;
11461 ˇstruct Row10;"#},
11462 base_text,
11463 &mut cx,
11464 );
11465}
11466
11467#[gpui::test]
11468async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11469 init_test(cx, |_| {});
11470 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11471 let base_text = indoc! {r#"
11472 struct Row;
11473 struct Row1;
11474 struct Row2;
11475
11476 struct Row4;
11477 struct Row5;
11478 struct Row6;
11479
11480 struct Row8;
11481 struct Row9;
11482 struct Row10;"#};
11483
11484 // Modification hunks behave the same as the addition ones.
11485 assert_hunk_revert(
11486 indoc! {r#"struct Row;
11487 struct Row1;
11488 struct Row33;
11489 ˇ
11490 struct Row4;
11491 struct Row5;
11492 struct Row6;
11493 ˇ
11494 struct Row99;
11495 struct Row9;
11496 struct Row10;"#},
11497 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11498 indoc! {r#"struct Row;
11499 struct Row1;
11500 struct Row33;
11501 ˇ
11502 struct Row4;
11503 struct Row5;
11504 struct Row6;
11505 ˇ
11506 struct Row99;
11507 struct Row9;
11508 struct Row10;"#},
11509 base_text,
11510 &mut cx,
11511 );
11512 assert_hunk_revert(
11513 indoc! {r#"struct Row;
11514 struct Row1;
11515 struct Row33;
11516 «ˇ
11517 struct Row4;
11518 struct» Row5;
11519 «struct Row6;
11520 ˇ»
11521 struct Row99;
11522 struct Row9;
11523 struct Row10;"#},
11524 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11525 indoc! {r#"struct Row;
11526 struct Row1;
11527 struct Row33;
11528 «ˇ
11529 struct Row4;
11530 struct» Row5;
11531 «struct Row6;
11532 ˇ»
11533 struct Row99;
11534 struct Row9;
11535 struct Row10;"#},
11536 base_text,
11537 &mut cx,
11538 );
11539
11540 assert_hunk_revert(
11541 indoc! {r#"ˇstruct Row1.1;
11542 struct Row1;
11543 «ˇstr»uct Row22;
11544
11545 struct ˇRow44;
11546 struct Row5;
11547 struct «Rˇ»ow66;ˇ
11548
11549 «struˇ»ct Row88;
11550 struct Row9;
11551 struct Row1011;ˇ"#},
11552 vec![
11553 DiffHunkStatus::Modified,
11554 DiffHunkStatus::Modified,
11555 DiffHunkStatus::Modified,
11556 DiffHunkStatus::Modified,
11557 DiffHunkStatus::Modified,
11558 DiffHunkStatus::Modified,
11559 ],
11560 indoc! {r#"struct Row;
11561 ˇstruct Row1;
11562 struct Row2;
11563 ˇ
11564 struct Row4;
11565 ˇstruct Row5;
11566 struct Row6;
11567 ˇ
11568 struct Row8;
11569 ˇstruct Row9;
11570 struct Row10;ˇ"#},
11571 base_text,
11572 &mut cx,
11573 );
11574}
11575
11576#[gpui::test]
11577async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11578 init_test(cx, |_| {});
11579 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11580 let base_text = indoc! {r#"struct Row;
11581struct Row1;
11582struct Row2;
11583
11584struct Row4;
11585struct Row5;
11586struct Row6;
11587
11588struct Row8;
11589struct Row9;
11590struct Row10;"#};
11591
11592 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11593 assert_hunk_revert(
11594 indoc! {r#"struct Row;
11595 struct Row2;
11596
11597 ˇstruct Row4;
11598 struct Row5;
11599 struct Row6;
11600 ˇ
11601 struct Row8;
11602 struct Row10;"#},
11603 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11604 indoc! {r#"struct Row;
11605 struct Row2;
11606
11607 ˇstruct Row4;
11608 struct Row5;
11609 struct Row6;
11610 ˇ
11611 struct Row8;
11612 struct Row10;"#},
11613 base_text,
11614 &mut cx,
11615 );
11616 assert_hunk_revert(
11617 indoc! {r#"struct Row;
11618 struct Row2;
11619
11620 «ˇstruct Row4;
11621 struct» Row5;
11622 «struct Row6;
11623 ˇ»
11624 struct Row8;
11625 struct Row10;"#},
11626 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11627 indoc! {r#"struct Row;
11628 struct Row2;
11629
11630 «ˇstruct Row4;
11631 struct» Row5;
11632 «struct Row6;
11633 ˇ»
11634 struct Row8;
11635 struct Row10;"#},
11636 base_text,
11637 &mut cx,
11638 );
11639
11640 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11641 assert_hunk_revert(
11642 indoc! {r#"struct Row;
11643 ˇstruct Row2;
11644
11645 struct Row4;
11646 struct Row5;
11647 struct Row6;
11648
11649 struct Row8;ˇ
11650 struct Row10;"#},
11651 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11652 indoc! {r#"struct Row;
11653 struct Row1;
11654 ˇstruct Row2;
11655
11656 struct Row4;
11657 struct Row5;
11658 struct Row6;
11659
11660 struct Row8;ˇ
11661 struct Row9;
11662 struct Row10;"#},
11663 base_text,
11664 &mut cx,
11665 );
11666 assert_hunk_revert(
11667 indoc! {r#"struct Row;
11668 struct Row2«ˇ;
11669 struct Row4;
11670 struct» Row5;
11671 «struct Row6;
11672
11673 struct Row8;ˇ»
11674 struct Row10;"#},
11675 vec![
11676 DiffHunkStatus::Removed,
11677 DiffHunkStatus::Removed,
11678 DiffHunkStatus::Removed,
11679 ],
11680 indoc! {r#"struct Row;
11681 struct Row1;
11682 struct Row2«ˇ;
11683
11684 struct Row4;
11685 struct» Row5;
11686 «struct Row6;
11687
11688 struct Row8;ˇ»
11689 struct Row9;
11690 struct Row10;"#},
11691 base_text,
11692 &mut cx,
11693 );
11694}
11695
11696#[gpui::test]
11697async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11698 init_test(cx, |_| {});
11699
11700 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11701 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11702 let base_text_3 =
11703 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11704
11705 let text_1 = edit_first_char_of_every_line(base_text_1);
11706 let text_2 = edit_first_char_of_every_line(base_text_2);
11707 let text_3 = edit_first_char_of_every_line(base_text_3);
11708
11709 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11710 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11711 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11712
11713 let multibuffer = cx.new_model(|cx| {
11714 let mut multibuffer = MultiBuffer::new(ReadWrite);
11715 multibuffer.push_excerpts(
11716 buffer_1.clone(),
11717 [
11718 ExcerptRange {
11719 context: Point::new(0, 0)..Point::new(3, 0),
11720 primary: None,
11721 },
11722 ExcerptRange {
11723 context: Point::new(5, 0)..Point::new(7, 0),
11724 primary: None,
11725 },
11726 ExcerptRange {
11727 context: Point::new(9, 0)..Point::new(10, 4),
11728 primary: None,
11729 },
11730 ],
11731 cx,
11732 );
11733 multibuffer.push_excerpts(
11734 buffer_2.clone(),
11735 [
11736 ExcerptRange {
11737 context: Point::new(0, 0)..Point::new(3, 0),
11738 primary: None,
11739 },
11740 ExcerptRange {
11741 context: Point::new(5, 0)..Point::new(7, 0),
11742 primary: None,
11743 },
11744 ExcerptRange {
11745 context: Point::new(9, 0)..Point::new(10, 4),
11746 primary: None,
11747 },
11748 ],
11749 cx,
11750 );
11751 multibuffer.push_excerpts(
11752 buffer_3.clone(),
11753 [
11754 ExcerptRange {
11755 context: Point::new(0, 0)..Point::new(3, 0),
11756 primary: None,
11757 },
11758 ExcerptRange {
11759 context: Point::new(5, 0)..Point::new(7, 0),
11760 primary: None,
11761 },
11762 ExcerptRange {
11763 context: Point::new(9, 0)..Point::new(10, 4),
11764 primary: None,
11765 },
11766 ],
11767 cx,
11768 );
11769 multibuffer
11770 });
11771
11772 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11773 editor.update(cx, |editor, cx| {
11774 for (buffer, diff_base) in [
11775 (buffer_1.clone(), base_text_1),
11776 (buffer_2.clone(), base_text_2),
11777 (buffer_3.clone(), base_text_3),
11778 ] {
11779 let change_set = cx.new_model(|cx| {
11780 BufferChangeSet::new_with_base_text(
11781 diff_base.to_string(),
11782 buffer.read(cx).text_snapshot(),
11783 cx,
11784 )
11785 });
11786 editor.diff_map.add_change_set(change_set, cx)
11787 }
11788 });
11789 cx.executor().run_until_parked();
11790
11791 editor.update(cx, |editor, cx| {
11792 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}");
11793 editor.select_all(&SelectAll, cx);
11794 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11795 });
11796 cx.executor().run_until_parked();
11797
11798 // When all ranges are selected, all buffer hunks are reverted.
11799 editor.update(cx, |editor, cx| {
11800 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");
11801 });
11802 buffer_1.update(cx, |buffer, _| {
11803 assert_eq!(buffer.text(), base_text_1);
11804 });
11805 buffer_2.update(cx, |buffer, _| {
11806 assert_eq!(buffer.text(), base_text_2);
11807 });
11808 buffer_3.update(cx, |buffer, _| {
11809 assert_eq!(buffer.text(), base_text_3);
11810 });
11811
11812 editor.update(cx, |editor, cx| {
11813 editor.undo(&Default::default(), cx);
11814 });
11815
11816 editor.update(cx, |editor, cx| {
11817 editor.change_selections(None, cx, |s| {
11818 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11819 });
11820 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11821 });
11822
11823 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11824 // but not affect buffer_2 and its related excerpts.
11825 editor.update(cx, |editor, cx| {
11826 assert_eq!(
11827 editor.text(cx),
11828 "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}"
11829 );
11830 });
11831 buffer_1.update(cx, |buffer, _| {
11832 assert_eq!(buffer.text(), base_text_1);
11833 });
11834 buffer_2.update(cx, |buffer, _| {
11835 assert_eq!(
11836 buffer.text(),
11837 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11838 );
11839 });
11840 buffer_3.update(cx, |buffer, _| {
11841 assert_eq!(
11842 buffer.text(),
11843 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11844 );
11845 });
11846
11847 fn edit_first_char_of_every_line(text: &str) -> String {
11848 text.split('\n')
11849 .map(|line| format!("X{}", &line[1..]))
11850 .collect::<Vec<_>>()
11851 .join("\n")
11852 }
11853}
11854
11855#[gpui::test]
11856async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11857 init_test(cx, |_| {});
11858
11859 let cols = 4;
11860 let rows = 10;
11861 let sample_text_1 = sample_text(rows, cols, 'a');
11862 assert_eq!(
11863 sample_text_1,
11864 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11865 );
11866 let sample_text_2 = sample_text(rows, cols, 'l');
11867 assert_eq!(
11868 sample_text_2,
11869 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11870 );
11871 let sample_text_3 = sample_text(rows, cols, 'v');
11872 assert_eq!(
11873 sample_text_3,
11874 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11875 );
11876
11877 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11878 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11879 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11880
11881 let multi_buffer = cx.new_model(|cx| {
11882 let mut multibuffer = MultiBuffer::new(ReadWrite);
11883 multibuffer.push_excerpts(
11884 buffer_1.clone(),
11885 [
11886 ExcerptRange {
11887 context: Point::new(0, 0)..Point::new(3, 0),
11888 primary: None,
11889 },
11890 ExcerptRange {
11891 context: Point::new(5, 0)..Point::new(7, 0),
11892 primary: None,
11893 },
11894 ExcerptRange {
11895 context: Point::new(9, 0)..Point::new(10, 4),
11896 primary: None,
11897 },
11898 ],
11899 cx,
11900 );
11901 multibuffer.push_excerpts(
11902 buffer_2.clone(),
11903 [
11904 ExcerptRange {
11905 context: Point::new(0, 0)..Point::new(3, 0),
11906 primary: None,
11907 },
11908 ExcerptRange {
11909 context: Point::new(5, 0)..Point::new(7, 0),
11910 primary: None,
11911 },
11912 ExcerptRange {
11913 context: Point::new(9, 0)..Point::new(10, 4),
11914 primary: None,
11915 },
11916 ],
11917 cx,
11918 );
11919 multibuffer.push_excerpts(
11920 buffer_3.clone(),
11921 [
11922 ExcerptRange {
11923 context: Point::new(0, 0)..Point::new(3, 0),
11924 primary: None,
11925 },
11926 ExcerptRange {
11927 context: Point::new(5, 0)..Point::new(7, 0),
11928 primary: None,
11929 },
11930 ExcerptRange {
11931 context: Point::new(9, 0)..Point::new(10, 4),
11932 primary: None,
11933 },
11934 ],
11935 cx,
11936 );
11937 multibuffer
11938 });
11939
11940 let fs = FakeFs::new(cx.executor());
11941 fs.insert_tree(
11942 "/a",
11943 json!({
11944 "main.rs": sample_text_1,
11945 "other.rs": sample_text_2,
11946 "lib.rs": sample_text_3,
11947 }),
11948 )
11949 .await;
11950 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11951 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11952 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11953 let multi_buffer_editor = cx.new_view(|cx| {
11954 Editor::new(
11955 EditorMode::Full,
11956 multi_buffer,
11957 Some(project.clone()),
11958 true,
11959 cx,
11960 )
11961 });
11962 let multibuffer_item_id = workspace
11963 .update(cx, |workspace, cx| {
11964 assert!(
11965 workspace.active_item(cx).is_none(),
11966 "active item should be None before the first item is added"
11967 );
11968 workspace.add_item_to_active_pane(
11969 Box::new(multi_buffer_editor.clone()),
11970 None,
11971 true,
11972 cx,
11973 );
11974 let active_item = workspace
11975 .active_item(cx)
11976 .expect("should have an active item after adding the multi buffer");
11977 assert!(
11978 !active_item.is_singleton(cx),
11979 "A multi buffer was expected to active after adding"
11980 );
11981 active_item.item_id()
11982 })
11983 .unwrap();
11984 cx.executor().run_until_parked();
11985
11986 multi_buffer_editor.update(cx, |editor, cx| {
11987 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11988 editor.open_excerpts(&OpenExcerpts, cx);
11989 });
11990 cx.executor().run_until_parked();
11991 let first_item_id = workspace
11992 .update(cx, |workspace, cx| {
11993 let active_item = workspace
11994 .active_item(cx)
11995 .expect("should have an active item after navigating into the 1st buffer");
11996 let first_item_id = active_item.item_id();
11997 assert_ne!(
11998 first_item_id, multibuffer_item_id,
11999 "Should navigate into the 1st buffer and activate it"
12000 );
12001 assert!(
12002 active_item.is_singleton(cx),
12003 "New active item should be a singleton buffer"
12004 );
12005 assert_eq!(
12006 active_item
12007 .act_as::<Editor>(cx)
12008 .expect("should have navigated into an editor for the 1st buffer")
12009 .read(cx)
12010 .text(cx),
12011 sample_text_1
12012 );
12013
12014 workspace
12015 .go_back(workspace.active_pane().downgrade(), cx)
12016 .detach_and_log_err(cx);
12017
12018 first_item_id
12019 })
12020 .unwrap();
12021 cx.executor().run_until_parked();
12022 workspace
12023 .update(cx, |workspace, cx| {
12024 let active_item = workspace
12025 .active_item(cx)
12026 .expect("should have an active item after navigating back");
12027 assert_eq!(
12028 active_item.item_id(),
12029 multibuffer_item_id,
12030 "Should navigate back to the multi buffer"
12031 );
12032 assert!(!active_item.is_singleton(cx));
12033 })
12034 .unwrap();
12035
12036 multi_buffer_editor.update(cx, |editor, cx| {
12037 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12038 s.select_ranges(Some(39..40))
12039 });
12040 editor.open_excerpts(&OpenExcerpts, cx);
12041 });
12042 cx.executor().run_until_parked();
12043 let second_item_id = workspace
12044 .update(cx, |workspace, cx| {
12045 let active_item = workspace
12046 .active_item(cx)
12047 .expect("should have an active item after navigating into the 2nd buffer");
12048 let second_item_id = active_item.item_id();
12049 assert_ne!(
12050 second_item_id, multibuffer_item_id,
12051 "Should navigate away from the multibuffer"
12052 );
12053 assert_ne!(
12054 second_item_id, first_item_id,
12055 "Should navigate into the 2nd buffer and activate it"
12056 );
12057 assert!(
12058 active_item.is_singleton(cx),
12059 "New active item should be a singleton buffer"
12060 );
12061 assert_eq!(
12062 active_item
12063 .act_as::<Editor>(cx)
12064 .expect("should have navigated into an editor")
12065 .read(cx)
12066 .text(cx),
12067 sample_text_2
12068 );
12069
12070 workspace
12071 .go_back(workspace.active_pane().downgrade(), cx)
12072 .detach_and_log_err(cx);
12073
12074 second_item_id
12075 })
12076 .unwrap();
12077 cx.executor().run_until_parked();
12078 workspace
12079 .update(cx, |workspace, cx| {
12080 let active_item = workspace
12081 .active_item(cx)
12082 .expect("should have an active item after navigating back from the 2nd buffer");
12083 assert_eq!(
12084 active_item.item_id(),
12085 multibuffer_item_id,
12086 "Should navigate back from the 2nd buffer to the multi buffer"
12087 );
12088 assert!(!active_item.is_singleton(cx));
12089 })
12090 .unwrap();
12091
12092 multi_buffer_editor.update(cx, |editor, cx| {
12093 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12094 s.select_ranges(Some(70..70))
12095 });
12096 editor.open_excerpts(&OpenExcerpts, cx);
12097 });
12098 cx.executor().run_until_parked();
12099 workspace
12100 .update(cx, |workspace, cx| {
12101 let active_item = workspace
12102 .active_item(cx)
12103 .expect("should have an active item after navigating into the 3rd buffer");
12104 let third_item_id = active_item.item_id();
12105 assert_ne!(
12106 third_item_id, multibuffer_item_id,
12107 "Should navigate into the 3rd buffer and activate it"
12108 );
12109 assert_ne!(third_item_id, first_item_id);
12110 assert_ne!(third_item_id, second_item_id);
12111 assert!(
12112 active_item.is_singleton(cx),
12113 "New active item should be a singleton buffer"
12114 );
12115 assert_eq!(
12116 active_item
12117 .act_as::<Editor>(cx)
12118 .expect("should have navigated into an editor")
12119 .read(cx)
12120 .text(cx),
12121 sample_text_3
12122 );
12123
12124 workspace
12125 .go_back(workspace.active_pane().downgrade(), cx)
12126 .detach_and_log_err(cx);
12127 })
12128 .unwrap();
12129 cx.executor().run_until_parked();
12130 workspace
12131 .update(cx, |workspace, cx| {
12132 let active_item = workspace
12133 .active_item(cx)
12134 .expect("should have an active item after navigating back from the 3rd buffer");
12135 assert_eq!(
12136 active_item.item_id(),
12137 multibuffer_item_id,
12138 "Should navigate back from the 3rd buffer to the multi buffer"
12139 );
12140 assert!(!active_item.is_singleton(cx));
12141 })
12142 .unwrap();
12143}
12144
12145#[gpui::test]
12146async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12147 init_test(cx, |_| {});
12148
12149 let mut cx = EditorTestContext::new(cx).await;
12150
12151 let diff_base = r#"
12152 use some::mod;
12153
12154 const A: u32 = 42;
12155
12156 fn main() {
12157 println!("hello");
12158
12159 println!("world");
12160 }
12161 "#
12162 .unindent();
12163
12164 cx.set_state(
12165 &r#"
12166 use some::modified;
12167
12168 ˇ
12169 fn main() {
12170 println!("hello there");
12171
12172 println!("around the");
12173 println!("world");
12174 }
12175 "#
12176 .unindent(),
12177 );
12178
12179 cx.set_diff_base(&diff_base);
12180 executor.run_until_parked();
12181
12182 cx.update_editor(|editor, cx| {
12183 editor.go_to_next_hunk(&GoToHunk, cx);
12184 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12185 });
12186 executor.run_until_parked();
12187 cx.assert_state_with_diff(
12188 r#"
12189 use some::modified;
12190
12191
12192 fn main() {
12193 - println!("hello");
12194 + ˇ println!("hello there");
12195
12196 println!("around the");
12197 println!("world");
12198 }
12199 "#
12200 .unindent(),
12201 );
12202
12203 cx.update_editor(|editor, cx| {
12204 for _ in 0..3 {
12205 editor.go_to_next_hunk(&GoToHunk, cx);
12206 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12207 }
12208 });
12209 executor.run_until_parked();
12210 cx.assert_state_with_diff(
12211 r#"
12212 - use some::mod;
12213 + use some::modified;
12214
12215 - const A: u32 = 42;
12216 ˇ
12217 fn main() {
12218 - println!("hello");
12219 + println!("hello there");
12220
12221 + println!("around the");
12222 println!("world");
12223 }
12224 "#
12225 .unindent(),
12226 );
12227
12228 cx.update_editor(|editor, cx| {
12229 editor.cancel(&Cancel, cx);
12230 });
12231
12232 cx.assert_state_with_diff(
12233 r#"
12234 use some::modified;
12235
12236 ˇ
12237 fn main() {
12238 println!("hello there");
12239
12240 println!("around the");
12241 println!("world");
12242 }
12243 "#
12244 .unindent(),
12245 );
12246}
12247
12248#[gpui::test]
12249async fn test_diff_base_change_with_expanded_diff_hunks(
12250 executor: BackgroundExecutor,
12251 cx: &mut gpui::TestAppContext,
12252) {
12253 init_test(cx, |_| {});
12254
12255 let mut cx = EditorTestContext::new(cx).await;
12256
12257 let diff_base = r#"
12258 use some::mod1;
12259 use some::mod2;
12260
12261 const A: u32 = 42;
12262 const B: u32 = 42;
12263 const C: u32 = 42;
12264
12265 fn main() {
12266 println!("hello");
12267
12268 println!("world");
12269 }
12270 "#
12271 .unindent();
12272
12273 cx.set_state(
12274 &r#"
12275 use some::mod2;
12276
12277 const A: u32 = 42;
12278 const C: u32 = 42;
12279
12280 fn main(ˇ) {
12281 //println!("hello");
12282
12283 println!("world");
12284 //
12285 //
12286 }
12287 "#
12288 .unindent(),
12289 );
12290
12291 cx.set_diff_base(&diff_base);
12292 executor.run_until_parked();
12293
12294 cx.update_editor(|editor, cx| {
12295 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12296 });
12297 executor.run_until_parked();
12298 cx.assert_state_with_diff(
12299 r#"
12300 - use some::mod1;
12301 use some::mod2;
12302
12303 const A: u32 = 42;
12304 - const B: u32 = 42;
12305 const C: u32 = 42;
12306
12307 fn main(ˇ) {
12308 - println!("hello");
12309 + //println!("hello");
12310
12311 println!("world");
12312 + //
12313 + //
12314 }
12315 "#
12316 .unindent(),
12317 );
12318
12319 cx.set_diff_base("new diff base!");
12320 executor.run_until_parked();
12321 cx.assert_state_with_diff(
12322 r#"
12323 use some::mod2;
12324
12325 const A: u32 = 42;
12326 const C: u32 = 42;
12327
12328 fn main(ˇ) {
12329 //println!("hello");
12330
12331 println!("world");
12332 //
12333 //
12334 }
12335 "#
12336 .unindent(),
12337 );
12338
12339 cx.update_editor(|editor, cx| {
12340 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12341 });
12342 executor.run_until_parked();
12343 cx.assert_state_with_diff(
12344 r#"
12345 - new diff base!
12346 + use some::mod2;
12347 +
12348 + const A: u32 = 42;
12349 + const C: u32 = 42;
12350 +
12351 + fn main(ˇ) {
12352 + //println!("hello");
12353 +
12354 + println!("world");
12355 + //
12356 + //
12357 + }
12358 "#
12359 .unindent(),
12360 );
12361}
12362
12363#[gpui::test]
12364async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12365 init_test(cx, |_| {});
12366
12367 let mut cx = EditorTestContext::new(cx).await;
12368
12369 let diff_base = r#"
12370 use some::mod1;
12371 use some::mod2;
12372
12373 const A: u32 = 42;
12374 const B: u32 = 42;
12375 const C: u32 = 42;
12376
12377 fn main() {
12378 println!("hello");
12379
12380 println!("world");
12381 }
12382
12383 fn another() {
12384 println!("another");
12385 }
12386
12387 fn another2() {
12388 println!("another2");
12389 }
12390 "#
12391 .unindent();
12392
12393 cx.set_state(
12394 &r#"
12395 «use some::mod2;
12396
12397 const A: u32 = 42;
12398 const C: u32 = 42;
12399
12400 fn main() {
12401 //println!("hello");
12402
12403 println!("world");
12404 //
12405 //ˇ»
12406 }
12407
12408 fn another() {
12409 println!("another");
12410 println!("another");
12411 }
12412
12413 println!("another2");
12414 }
12415 "#
12416 .unindent(),
12417 );
12418
12419 cx.set_diff_base(&diff_base);
12420 executor.run_until_parked();
12421
12422 cx.update_editor(|editor, cx| {
12423 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12424 });
12425 executor.run_until_parked();
12426
12427 cx.assert_state_with_diff(
12428 r#"
12429 - use some::mod1;
12430 «use some::mod2;
12431
12432 const A: u32 = 42;
12433 - const B: u32 = 42;
12434 const C: u32 = 42;
12435
12436 fn main() {
12437 - println!("hello");
12438 + //println!("hello");
12439
12440 println!("world");
12441 + //
12442 + //ˇ»
12443 }
12444
12445 fn another() {
12446 println!("another");
12447 + println!("another");
12448 }
12449
12450 - fn another2() {
12451 println!("another2");
12452 }
12453 "#
12454 .unindent(),
12455 );
12456
12457 // Fold across some of the diff hunks. They should no longer appear expanded.
12458 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12459 cx.executor().run_until_parked();
12460
12461 // Hunks are not shown if their position is within a fold
12462 cx.assert_state_with_diff(
12463 r#"
12464 «use some::mod2;
12465
12466 const A: u32 = 42;
12467 const C: u32 = 42;
12468
12469 fn main() {
12470 //println!("hello");
12471
12472 println!("world");
12473 //
12474 //ˇ»
12475 }
12476
12477 fn another() {
12478 println!("another");
12479 + println!("another");
12480 }
12481
12482 - fn another2() {
12483 println!("another2");
12484 }
12485 "#
12486 .unindent(),
12487 );
12488
12489 cx.update_editor(|editor, cx| {
12490 editor.select_all(&SelectAll, cx);
12491 editor.unfold_lines(&UnfoldLines, cx);
12492 });
12493 cx.executor().run_until_parked();
12494
12495 // The deletions reappear when unfolding.
12496 cx.assert_state_with_diff(
12497 r#"
12498 - use some::mod1;
12499 «use some::mod2;
12500
12501 const A: u32 = 42;
12502 - const B: u32 = 42;
12503 const C: u32 = 42;
12504
12505 fn main() {
12506 - println!("hello");
12507 + //println!("hello");
12508
12509 println!("world");
12510 + //
12511 + //
12512 }
12513
12514 fn another() {
12515 println!("another");
12516 + println!("another");
12517 }
12518
12519 - fn another2() {
12520 println!("another2");
12521 }
12522 ˇ»"#
12523 .unindent(),
12524 );
12525}
12526
12527#[gpui::test]
12528async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12529 init_test(cx, |_| {});
12530
12531 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12532 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12533 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12534 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12535 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12536 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12537
12538 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12539 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12540 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12541
12542 let multi_buffer = cx.new_model(|cx| {
12543 let mut multibuffer = MultiBuffer::new(ReadWrite);
12544 multibuffer.push_excerpts(
12545 buffer_1.clone(),
12546 [
12547 ExcerptRange {
12548 context: Point::new(0, 0)..Point::new(3, 0),
12549 primary: None,
12550 },
12551 ExcerptRange {
12552 context: Point::new(5, 0)..Point::new(7, 0),
12553 primary: None,
12554 },
12555 ExcerptRange {
12556 context: Point::new(9, 0)..Point::new(10, 3),
12557 primary: None,
12558 },
12559 ],
12560 cx,
12561 );
12562 multibuffer.push_excerpts(
12563 buffer_2.clone(),
12564 [
12565 ExcerptRange {
12566 context: Point::new(0, 0)..Point::new(3, 0),
12567 primary: None,
12568 },
12569 ExcerptRange {
12570 context: Point::new(5, 0)..Point::new(7, 0),
12571 primary: None,
12572 },
12573 ExcerptRange {
12574 context: Point::new(9, 0)..Point::new(10, 3),
12575 primary: None,
12576 },
12577 ],
12578 cx,
12579 );
12580 multibuffer.push_excerpts(
12581 buffer_3.clone(),
12582 [
12583 ExcerptRange {
12584 context: Point::new(0, 0)..Point::new(3, 0),
12585 primary: None,
12586 },
12587 ExcerptRange {
12588 context: Point::new(5, 0)..Point::new(7, 0),
12589 primary: None,
12590 },
12591 ExcerptRange {
12592 context: Point::new(9, 0)..Point::new(10, 3),
12593 primary: None,
12594 },
12595 ],
12596 cx,
12597 );
12598 multibuffer
12599 });
12600
12601 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12602 editor
12603 .update(cx, |editor, cx| {
12604 for (buffer, diff_base) in [
12605 (buffer_1.clone(), file_1_old),
12606 (buffer_2.clone(), file_2_old),
12607 (buffer_3.clone(), file_3_old),
12608 ] {
12609 let change_set = cx.new_model(|cx| {
12610 BufferChangeSet::new_with_base_text(
12611 diff_base.to_string(),
12612 buffer.read(cx).text_snapshot(),
12613 cx,
12614 )
12615 });
12616 editor.diff_map.add_change_set(change_set, cx)
12617 }
12618 })
12619 .unwrap();
12620
12621 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12622 cx.run_until_parked();
12623
12624 cx.assert_editor_state(
12625 &"
12626 ˇaaa
12627 ccc
12628 ddd
12629
12630 ggg
12631 hhh
12632
12633
12634 lll
12635 mmm
12636 NNN
12637
12638 qqq
12639 rrr
12640
12641 uuu
12642 111
12643 222
12644 333
12645
12646 666
12647 777
12648
12649 000
12650 !!!"
12651 .unindent(),
12652 );
12653
12654 cx.update_editor(|editor, cx| {
12655 editor.select_all(&SelectAll, cx);
12656 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12657 });
12658 cx.executor().run_until_parked();
12659
12660 cx.assert_state_with_diff(
12661 "
12662 «aaa
12663 - bbb
12664 ccc
12665 ddd
12666
12667 ggg
12668 hhh
12669
12670
12671 lll
12672 mmm
12673 - nnn
12674 + NNN
12675
12676 qqq
12677 rrr
12678
12679 uuu
12680 111
12681 222
12682 333
12683
12684 + 666
12685 777
12686
12687 000
12688 !!!ˇ»"
12689 .unindent(),
12690 );
12691}
12692
12693#[gpui::test]
12694async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12695 init_test(cx, |_| {});
12696
12697 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12698 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12699
12700 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12701 let multi_buffer = cx.new_model(|cx| {
12702 let mut multibuffer = MultiBuffer::new(ReadWrite);
12703 multibuffer.push_excerpts(
12704 buffer.clone(),
12705 [
12706 ExcerptRange {
12707 context: Point::new(0, 0)..Point::new(2, 0),
12708 primary: None,
12709 },
12710 ExcerptRange {
12711 context: Point::new(5, 0)..Point::new(7, 0),
12712 primary: None,
12713 },
12714 ],
12715 cx,
12716 );
12717 multibuffer
12718 });
12719
12720 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12721 editor
12722 .update(cx, |editor, cx| {
12723 let buffer = buffer.read(cx).text_snapshot();
12724 let change_set = cx
12725 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12726 editor.diff_map.add_change_set(change_set, cx)
12727 })
12728 .unwrap();
12729
12730 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12731 cx.run_until_parked();
12732
12733 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12734 cx.executor().run_until_parked();
12735
12736 cx.assert_state_with_diff(
12737 "
12738 ˇaaa
12739 - bbb
12740 + BBB
12741
12742 - ddd
12743 - eee
12744 + EEE
12745 fff
12746 "
12747 .unindent(),
12748 );
12749}
12750
12751#[gpui::test]
12752async fn test_edits_around_expanded_insertion_hunks(
12753 executor: BackgroundExecutor,
12754 cx: &mut gpui::TestAppContext,
12755) {
12756 init_test(cx, |_| {});
12757
12758 let mut cx = EditorTestContext::new(cx).await;
12759
12760 let diff_base = r#"
12761 use some::mod1;
12762 use some::mod2;
12763
12764 const A: u32 = 42;
12765
12766 fn main() {
12767 println!("hello");
12768
12769 println!("world");
12770 }
12771 "#
12772 .unindent();
12773 executor.run_until_parked();
12774 cx.set_state(
12775 &r#"
12776 use some::mod1;
12777 use some::mod2;
12778
12779 const A: u32 = 42;
12780 const B: u32 = 42;
12781 const C: u32 = 42;
12782 ˇ
12783
12784 fn main() {
12785 println!("hello");
12786
12787 println!("world");
12788 }
12789 "#
12790 .unindent(),
12791 );
12792
12793 cx.set_diff_base(&diff_base);
12794 executor.run_until_parked();
12795
12796 cx.update_editor(|editor, cx| {
12797 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12798 });
12799 executor.run_until_parked();
12800
12801 cx.assert_state_with_diff(
12802 r#"
12803 use some::mod1;
12804 use some::mod2;
12805
12806 const A: u32 = 42;
12807 + const B: u32 = 42;
12808 + const C: u32 = 42;
12809 + ˇ
12810
12811 fn main() {
12812 println!("hello");
12813
12814 println!("world");
12815 }
12816 "#
12817 .unindent(),
12818 );
12819
12820 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12821 executor.run_until_parked();
12822
12823 cx.assert_state_with_diff(
12824 r#"
12825 use some::mod1;
12826 use some::mod2;
12827
12828 const A: u32 = 42;
12829 + const B: u32 = 42;
12830 + const C: u32 = 42;
12831 + const D: u32 = 42;
12832 + ˇ
12833
12834 fn main() {
12835 println!("hello");
12836
12837 println!("world");
12838 }
12839 "#
12840 .unindent(),
12841 );
12842
12843 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12844 executor.run_until_parked();
12845
12846 cx.assert_state_with_diff(
12847 r#"
12848 use some::mod1;
12849 use some::mod2;
12850
12851 const A: u32 = 42;
12852 + const B: u32 = 42;
12853 + const C: u32 = 42;
12854 + const D: u32 = 42;
12855 + const E: u32 = 42;
12856 + ˇ
12857
12858 fn main() {
12859 println!("hello");
12860
12861 println!("world");
12862 }
12863 "#
12864 .unindent(),
12865 );
12866
12867 cx.update_editor(|editor, cx| {
12868 editor.delete_line(&DeleteLine, cx);
12869 });
12870 executor.run_until_parked();
12871
12872 cx.assert_state_with_diff(
12873 r#"
12874 use some::mod1;
12875 use some::mod2;
12876
12877 const A: u32 = 42;
12878 + const B: u32 = 42;
12879 + const C: u32 = 42;
12880 + const D: u32 = 42;
12881 + const E: u32 = 42;
12882 ˇ
12883 fn main() {
12884 println!("hello");
12885
12886 println!("world");
12887 }
12888 "#
12889 .unindent(),
12890 );
12891
12892 cx.update_editor(|editor, cx| {
12893 editor.move_up(&MoveUp, cx);
12894 editor.delete_line(&DeleteLine, cx);
12895 editor.move_up(&MoveUp, cx);
12896 editor.delete_line(&DeleteLine, cx);
12897 editor.move_up(&MoveUp, cx);
12898 editor.delete_line(&DeleteLine, cx);
12899 });
12900 executor.run_until_parked();
12901 cx.assert_state_with_diff(
12902 r#"
12903 use some::mod1;
12904 use some::mod2;
12905
12906 const A: u32 = 42;
12907 + const B: u32 = 42;
12908 ˇ
12909 fn main() {
12910 println!("hello");
12911
12912 println!("world");
12913 }
12914 "#
12915 .unindent(),
12916 );
12917
12918 cx.update_editor(|editor, cx| {
12919 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12920 editor.delete_line(&DeleteLine, cx);
12921 });
12922 executor.run_until_parked();
12923 cx.assert_state_with_diff(
12924 r#"
12925 use some::mod1;
12926 - use some::mod2;
12927 -
12928 - const A: u32 = 42;
12929 ˇ
12930 fn main() {
12931 println!("hello");
12932
12933 println!("world");
12934 }
12935 "#
12936 .unindent(),
12937 );
12938}
12939
12940#[gpui::test]
12941async fn test_edits_around_expanded_deletion_hunks(
12942 executor: BackgroundExecutor,
12943 cx: &mut gpui::TestAppContext,
12944) {
12945 init_test(cx, |_| {});
12946
12947 let mut cx = EditorTestContext::new(cx).await;
12948
12949 let diff_base = r#"
12950 use some::mod1;
12951 use some::mod2;
12952
12953 const A: u32 = 42;
12954 const B: u32 = 42;
12955 const C: u32 = 42;
12956
12957
12958 fn main() {
12959 println!("hello");
12960
12961 println!("world");
12962 }
12963 "#
12964 .unindent();
12965 executor.run_until_parked();
12966 cx.set_state(
12967 &r#"
12968 use some::mod1;
12969 use some::mod2;
12970
12971 ˇconst B: u32 = 42;
12972 const C: u32 = 42;
12973
12974
12975 fn main() {
12976 println!("hello");
12977
12978 println!("world");
12979 }
12980 "#
12981 .unindent(),
12982 );
12983
12984 cx.set_diff_base(&diff_base);
12985 executor.run_until_parked();
12986
12987 cx.update_editor(|editor, cx| {
12988 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12989 });
12990 executor.run_until_parked();
12991
12992 cx.assert_state_with_diff(
12993 r#"
12994 use some::mod1;
12995 use some::mod2;
12996
12997 - const A: u32 = 42;
12998 ˇconst B: u32 = 42;
12999 const C: u32 = 42;
13000
13001
13002 fn main() {
13003 println!("hello");
13004
13005 println!("world");
13006 }
13007 "#
13008 .unindent(),
13009 );
13010
13011 cx.update_editor(|editor, cx| {
13012 editor.delete_line(&DeleteLine, cx);
13013 });
13014 executor.run_until_parked();
13015 cx.assert_state_with_diff(
13016 r#"
13017 use some::mod1;
13018 use some::mod2;
13019
13020 - const A: u32 = 42;
13021 - const B: u32 = 42;
13022 ˇconst C: u32 = 42;
13023
13024
13025 fn main() {
13026 println!("hello");
13027
13028 println!("world");
13029 }
13030 "#
13031 .unindent(),
13032 );
13033
13034 cx.update_editor(|editor, cx| {
13035 editor.delete_line(&DeleteLine, cx);
13036 });
13037 executor.run_until_parked();
13038 cx.assert_state_with_diff(
13039 r#"
13040 use some::mod1;
13041 use some::mod2;
13042
13043 - const A: u32 = 42;
13044 - const B: u32 = 42;
13045 - const C: u32 = 42;
13046 ˇ
13047
13048 fn main() {
13049 println!("hello");
13050
13051 println!("world");
13052 }
13053 "#
13054 .unindent(),
13055 );
13056
13057 cx.update_editor(|editor, cx| {
13058 editor.handle_input("replacement", cx);
13059 });
13060 executor.run_until_parked();
13061 cx.assert_state_with_diff(
13062 r#"
13063 use some::mod1;
13064 use some::mod2;
13065
13066 - const A: u32 = 42;
13067 - const B: u32 = 42;
13068 - const C: u32 = 42;
13069 -
13070 + replacementˇ
13071
13072 fn main() {
13073 println!("hello");
13074
13075 println!("world");
13076 }
13077 "#
13078 .unindent(),
13079 );
13080}
13081
13082#[gpui::test]
13083async fn test_edit_after_expanded_modification_hunk(
13084 executor: BackgroundExecutor,
13085 cx: &mut gpui::TestAppContext,
13086) {
13087 init_test(cx, |_| {});
13088
13089 let mut cx = EditorTestContext::new(cx).await;
13090
13091 let diff_base = r#"
13092 use some::mod1;
13093 use some::mod2;
13094
13095 const A: u32 = 42;
13096 const B: u32 = 42;
13097 const C: u32 = 42;
13098 const D: u32 = 42;
13099
13100
13101 fn main() {
13102 println!("hello");
13103
13104 println!("world");
13105 }"#
13106 .unindent();
13107
13108 cx.set_state(
13109 &r#"
13110 use some::mod1;
13111 use some::mod2;
13112
13113 const A: u32 = 42;
13114 const B: u32 = 42;
13115 const C: u32 = 43ˇ
13116 const D: u32 = 42;
13117
13118
13119 fn main() {
13120 println!("hello");
13121
13122 println!("world");
13123 }"#
13124 .unindent(),
13125 );
13126
13127 cx.set_diff_base(&diff_base);
13128 executor.run_until_parked();
13129 cx.update_editor(|editor, cx| {
13130 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
13131 });
13132 executor.run_until_parked();
13133
13134 cx.assert_state_with_diff(
13135 r#"
13136 use some::mod1;
13137 use some::mod2;
13138
13139 const A: u32 = 42;
13140 const B: u32 = 42;
13141 - const C: u32 = 42;
13142 + const C: u32 = 43ˇ
13143 const D: u32 = 42;
13144
13145
13146 fn main() {
13147 println!("hello");
13148
13149 println!("world");
13150 }"#
13151 .unindent(),
13152 );
13153
13154 cx.update_editor(|editor, cx| {
13155 editor.handle_input("\nnew_line\n", cx);
13156 });
13157 executor.run_until_parked();
13158
13159 cx.assert_state_with_diff(
13160 r#"
13161 use some::mod1;
13162 use some::mod2;
13163
13164 const A: u32 = 42;
13165 const B: u32 = 42;
13166 - const C: u32 = 42;
13167 + const C: u32 = 43
13168 + new_line
13169 + ˇ
13170 const D: u32 = 42;
13171
13172
13173 fn main() {
13174 println!("hello");
13175
13176 println!("world");
13177 }"#
13178 .unindent(),
13179 );
13180}
13181
13182async fn setup_indent_guides_editor(
13183 text: &str,
13184 cx: &mut gpui::TestAppContext,
13185) -> (BufferId, EditorTestContext) {
13186 init_test(cx, |_| {});
13187
13188 let mut cx = EditorTestContext::new(cx).await;
13189
13190 let buffer_id = cx.update_editor(|editor, cx| {
13191 editor.set_text(text, cx);
13192 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13193
13194 buffer_ids[0]
13195 });
13196
13197 (buffer_id, cx)
13198}
13199
13200fn assert_indent_guides(
13201 range: Range<u32>,
13202 expected: Vec<IndentGuide>,
13203 active_indices: Option<Vec<usize>>,
13204 cx: &mut EditorTestContext,
13205) {
13206 let indent_guides = cx.update_editor(|editor, cx| {
13207 let snapshot = editor.snapshot(cx).display_snapshot;
13208 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13209 editor,
13210 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13211 true,
13212 &snapshot,
13213 cx,
13214 );
13215
13216 indent_guides.sort_by(|a, b| {
13217 a.depth.cmp(&b.depth).then(
13218 a.start_row
13219 .cmp(&b.start_row)
13220 .then(a.end_row.cmp(&b.end_row)),
13221 )
13222 });
13223 indent_guides
13224 });
13225
13226 if let Some(expected) = active_indices {
13227 let active_indices = cx.update_editor(|editor, cx| {
13228 let snapshot = editor.snapshot(cx).display_snapshot;
13229 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13230 });
13231
13232 assert_eq!(
13233 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13234 expected,
13235 "Active indent guide indices do not match"
13236 );
13237 }
13238
13239 let expected: Vec<_> = expected
13240 .into_iter()
13241 .map(|guide| MultiBufferIndentGuide {
13242 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13243 buffer: guide,
13244 })
13245 .collect();
13246
13247 assert_eq!(indent_guides, expected, "Indent guides do not match");
13248}
13249
13250fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13251 IndentGuide {
13252 buffer_id,
13253 start_row,
13254 end_row,
13255 depth,
13256 tab_size: 4,
13257 settings: IndentGuideSettings {
13258 enabled: true,
13259 line_width: 1,
13260 active_line_width: 1,
13261 ..Default::default()
13262 },
13263 }
13264}
13265
13266#[gpui::test]
13267async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13268 let (buffer_id, mut cx) = setup_indent_guides_editor(
13269 &"
13270 fn main() {
13271 let a = 1;
13272 }"
13273 .unindent(),
13274 cx,
13275 )
13276 .await;
13277
13278 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13279}
13280
13281#[gpui::test]
13282async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13283 let (buffer_id, mut cx) = setup_indent_guides_editor(
13284 &"
13285 fn main() {
13286 let a = 1;
13287 let b = 2;
13288 }"
13289 .unindent(),
13290 cx,
13291 )
13292 .await;
13293
13294 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13295}
13296
13297#[gpui::test]
13298async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13299 let (buffer_id, mut cx) = setup_indent_guides_editor(
13300 &"
13301 fn main() {
13302 let a = 1;
13303 if a == 3 {
13304 let b = 2;
13305 } else {
13306 let c = 3;
13307 }
13308 }"
13309 .unindent(),
13310 cx,
13311 )
13312 .await;
13313
13314 assert_indent_guides(
13315 0..8,
13316 vec![
13317 indent_guide(buffer_id, 1, 6, 0),
13318 indent_guide(buffer_id, 3, 3, 1),
13319 indent_guide(buffer_id, 5, 5, 1),
13320 ],
13321 None,
13322 &mut cx,
13323 );
13324}
13325
13326#[gpui::test]
13327async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13328 let (buffer_id, mut cx) = setup_indent_guides_editor(
13329 &"
13330 fn main() {
13331 let a = 1;
13332 let b = 2;
13333 let c = 3;
13334 }"
13335 .unindent(),
13336 cx,
13337 )
13338 .await;
13339
13340 assert_indent_guides(
13341 0..5,
13342 vec![
13343 indent_guide(buffer_id, 1, 3, 0),
13344 indent_guide(buffer_id, 2, 2, 1),
13345 ],
13346 None,
13347 &mut cx,
13348 );
13349}
13350
13351#[gpui::test]
13352async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13353 let (buffer_id, mut cx) = setup_indent_guides_editor(
13354 &"
13355 fn main() {
13356 let a = 1;
13357
13358 let c = 3;
13359 }"
13360 .unindent(),
13361 cx,
13362 )
13363 .await;
13364
13365 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13366}
13367
13368#[gpui::test]
13369async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13370 let (buffer_id, mut cx) = setup_indent_guides_editor(
13371 &"
13372 fn main() {
13373 let a = 1;
13374
13375 let c = 3;
13376
13377 if a == 3 {
13378 let b = 2;
13379 } else {
13380 let c = 3;
13381 }
13382 }"
13383 .unindent(),
13384 cx,
13385 )
13386 .await;
13387
13388 assert_indent_guides(
13389 0..11,
13390 vec![
13391 indent_guide(buffer_id, 1, 9, 0),
13392 indent_guide(buffer_id, 6, 6, 1),
13393 indent_guide(buffer_id, 8, 8, 1),
13394 ],
13395 None,
13396 &mut cx,
13397 );
13398}
13399
13400#[gpui::test]
13401async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13402 let (buffer_id, mut cx) = setup_indent_guides_editor(
13403 &"
13404 fn main() {
13405 let a = 1;
13406
13407 let c = 3;
13408
13409 if a == 3 {
13410 let b = 2;
13411 } else {
13412 let c = 3;
13413 }
13414 }"
13415 .unindent(),
13416 cx,
13417 )
13418 .await;
13419
13420 assert_indent_guides(
13421 1..11,
13422 vec![
13423 indent_guide(buffer_id, 1, 9, 0),
13424 indent_guide(buffer_id, 6, 6, 1),
13425 indent_guide(buffer_id, 8, 8, 1),
13426 ],
13427 None,
13428 &mut cx,
13429 );
13430}
13431
13432#[gpui::test]
13433async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13434 let (buffer_id, mut cx) = setup_indent_guides_editor(
13435 &"
13436 fn main() {
13437 let a = 1;
13438
13439 let c = 3;
13440
13441 if a == 3 {
13442 let b = 2;
13443 } else {
13444 let c = 3;
13445 }
13446 }"
13447 .unindent(),
13448 cx,
13449 )
13450 .await;
13451
13452 assert_indent_guides(
13453 1..10,
13454 vec![
13455 indent_guide(buffer_id, 1, 9, 0),
13456 indent_guide(buffer_id, 6, 6, 1),
13457 indent_guide(buffer_id, 8, 8, 1),
13458 ],
13459 None,
13460 &mut cx,
13461 );
13462}
13463
13464#[gpui::test]
13465async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13466 let (buffer_id, mut cx) = setup_indent_guides_editor(
13467 &"
13468 block1
13469 block2
13470 block3
13471 block4
13472 block2
13473 block1
13474 block1"
13475 .unindent(),
13476 cx,
13477 )
13478 .await;
13479
13480 assert_indent_guides(
13481 1..10,
13482 vec![
13483 indent_guide(buffer_id, 1, 4, 0),
13484 indent_guide(buffer_id, 2, 3, 1),
13485 indent_guide(buffer_id, 3, 3, 2),
13486 ],
13487 None,
13488 &mut cx,
13489 );
13490}
13491
13492#[gpui::test]
13493async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13494 let (buffer_id, mut cx) = setup_indent_guides_editor(
13495 &"
13496 block1
13497 block2
13498 block3
13499
13500 block1
13501 block1"
13502 .unindent(),
13503 cx,
13504 )
13505 .await;
13506
13507 assert_indent_guides(
13508 0..6,
13509 vec![
13510 indent_guide(buffer_id, 1, 2, 0),
13511 indent_guide(buffer_id, 2, 2, 1),
13512 ],
13513 None,
13514 &mut cx,
13515 );
13516}
13517
13518#[gpui::test]
13519async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13520 let (buffer_id, mut cx) = setup_indent_guides_editor(
13521 &"
13522 block1
13523
13524
13525
13526 block2
13527 "
13528 .unindent(),
13529 cx,
13530 )
13531 .await;
13532
13533 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13534}
13535
13536#[gpui::test]
13537async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13538 let (buffer_id, mut cx) = setup_indent_guides_editor(
13539 &"
13540 def a:
13541 \tb = 3
13542 \tif True:
13543 \t\tc = 4
13544 \t\td = 5
13545 \tprint(b)
13546 "
13547 .unindent(),
13548 cx,
13549 )
13550 .await;
13551
13552 assert_indent_guides(
13553 0..6,
13554 vec![
13555 indent_guide(buffer_id, 1, 6, 0),
13556 indent_guide(buffer_id, 3, 4, 1),
13557 ],
13558 None,
13559 &mut cx,
13560 );
13561}
13562
13563#[gpui::test]
13564async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13565 let (buffer_id, mut cx) = setup_indent_guides_editor(
13566 &"
13567 fn main() {
13568 let a = 1;
13569 }"
13570 .unindent(),
13571 cx,
13572 )
13573 .await;
13574
13575 cx.update_editor(|editor, cx| {
13576 editor.change_selections(None, cx, |s| {
13577 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13578 });
13579 });
13580
13581 assert_indent_guides(
13582 0..3,
13583 vec![indent_guide(buffer_id, 1, 1, 0)],
13584 Some(vec![0]),
13585 &mut cx,
13586 );
13587}
13588
13589#[gpui::test]
13590async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13591 let (buffer_id, mut cx) = setup_indent_guides_editor(
13592 &"
13593 fn main() {
13594 if 1 == 2 {
13595 let a = 1;
13596 }
13597 }"
13598 .unindent(),
13599 cx,
13600 )
13601 .await;
13602
13603 cx.update_editor(|editor, cx| {
13604 editor.change_selections(None, cx, |s| {
13605 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13606 });
13607 });
13608
13609 assert_indent_guides(
13610 0..4,
13611 vec![
13612 indent_guide(buffer_id, 1, 3, 0),
13613 indent_guide(buffer_id, 2, 2, 1),
13614 ],
13615 Some(vec![1]),
13616 &mut cx,
13617 );
13618
13619 cx.update_editor(|editor, cx| {
13620 editor.change_selections(None, cx, |s| {
13621 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13622 });
13623 });
13624
13625 assert_indent_guides(
13626 0..4,
13627 vec![
13628 indent_guide(buffer_id, 1, 3, 0),
13629 indent_guide(buffer_id, 2, 2, 1),
13630 ],
13631 Some(vec![1]),
13632 &mut cx,
13633 );
13634
13635 cx.update_editor(|editor, cx| {
13636 editor.change_selections(None, cx, |s| {
13637 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13638 });
13639 });
13640
13641 assert_indent_guides(
13642 0..4,
13643 vec![
13644 indent_guide(buffer_id, 1, 3, 0),
13645 indent_guide(buffer_id, 2, 2, 1),
13646 ],
13647 Some(vec![0]),
13648 &mut cx,
13649 );
13650}
13651
13652#[gpui::test]
13653async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13654 let (buffer_id, mut cx) = setup_indent_guides_editor(
13655 &"
13656 fn main() {
13657 let a = 1;
13658
13659 let b = 2;
13660 }"
13661 .unindent(),
13662 cx,
13663 )
13664 .await;
13665
13666 cx.update_editor(|editor, cx| {
13667 editor.change_selections(None, cx, |s| {
13668 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13669 });
13670 });
13671
13672 assert_indent_guides(
13673 0..5,
13674 vec![indent_guide(buffer_id, 1, 3, 0)],
13675 Some(vec![0]),
13676 &mut cx,
13677 );
13678}
13679
13680#[gpui::test]
13681async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13682 let (buffer_id, mut cx) = setup_indent_guides_editor(
13683 &"
13684 def m:
13685 a = 1
13686 pass"
13687 .unindent(),
13688 cx,
13689 )
13690 .await;
13691
13692 cx.update_editor(|editor, cx| {
13693 editor.change_selections(None, cx, |s| {
13694 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13695 });
13696 });
13697
13698 assert_indent_guides(
13699 0..3,
13700 vec![indent_guide(buffer_id, 1, 2, 0)],
13701 Some(vec![0]),
13702 &mut cx,
13703 );
13704}
13705
13706#[gpui::test]
13707fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13708 init_test(cx, |_| {});
13709
13710 let editor = cx.add_window(|cx| {
13711 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13712 build_editor(buffer, cx)
13713 });
13714
13715 let render_args = Arc::new(Mutex::new(None));
13716 let snapshot = editor
13717 .update(cx, |editor, cx| {
13718 let snapshot = editor.buffer().read(cx).snapshot(cx);
13719 let range =
13720 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13721
13722 struct RenderArgs {
13723 row: MultiBufferRow,
13724 folded: bool,
13725 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13726 }
13727
13728 let crease = Crease::inline(
13729 range,
13730 FoldPlaceholder::test(),
13731 {
13732 let toggle_callback = render_args.clone();
13733 move |row, folded, callback, _cx| {
13734 *toggle_callback.lock() = Some(RenderArgs {
13735 row,
13736 folded,
13737 callback,
13738 });
13739 div()
13740 }
13741 },
13742 |_row, _folded, _cx| div(),
13743 );
13744
13745 editor.insert_creases(Some(crease), cx);
13746 let snapshot = editor.snapshot(cx);
13747 let _div =
13748 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13749 snapshot
13750 })
13751 .unwrap();
13752
13753 let render_args = render_args.lock().take().unwrap();
13754 assert_eq!(render_args.row, MultiBufferRow(1));
13755 assert!(!render_args.folded);
13756 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13757
13758 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13759 .unwrap();
13760 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13761 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13762
13763 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13764 .unwrap();
13765 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13766 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13767}
13768
13769#[gpui::test]
13770async fn test_input_text(cx: &mut gpui::TestAppContext) {
13771 init_test(cx, |_| {});
13772 let mut cx = EditorTestContext::new(cx).await;
13773
13774 cx.set_state(
13775 &r#"ˇone
13776 two
13777
13778 three
13779 fourˇ
13780 five
13781
13782 siˇx"#
13783 .unindent(),
13784 );
13785
13786 cx.dispatch_action(HandleInput(String::new()));
13787 cx.assert_editor_state(
13788 &r#"ˇone
13789 two
13790
13791 three
13792 fourˇ
13793 five
13794
13795 siˇx"#
13796 .unindent(),
13797 );
13798
13799 cx.dispatch_action(HandleInput("AAAA".to_string()));
13800 cx.assert_editor_state(
13801 &r#"AAAAˇone
13802 two
13803
13804 three
13805 fourAAAAˇ
13806 five
13807
13808 siAAAAˇx"#
13809 .unindent(),
13810 );
13811}
13812
13813#[gpui::test]
13814async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13815 init_test(cx, |_| {});
13816
13817 let mut cx = EditorTestContext::new(cx).await;
13818 cx.set_state(
13819 r#"let foo = 1;
13820let foo = 2;
13821let foo = 3;
13822let fooˇ = 4;
13823let foo = 5;
13824let foo = 6;
13825let foo = 7;
13826let foo = 8;
13827let foo = 9;
13828let foo = 10;
13829let foo = 11;
13830let foo = 12;
13831let foo = 13;
13832let foo = 14;
13833let foo = 15;"#,
13834 );
13835
13836 cx.update_editor(|e, cx| {
13837 assert_eq!(
13838 e.next_scroll_position,
13839 NextScrollCursorCenterTopBottom::Center,
13840 "Default next scroll direction is center",
13841 );
13842
13843 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13844 assert_eq!(
13845 e.next_scroll_position,
13846 NextScrollCursorCenterTopBottom::Top,
13847 "After center, next scroll direction should be top",
13848 );
13849
13850 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13851 assert_eq!(
13852 e.next_scroll_position,
13853 NextScrollCursorCenterTopBottom::Bottom,
13854 "After top, next scroll direction should be bottom",
13855 );
13856
13857 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13858 assert_eq!(
13859 e.next_scroll_position,
13860 NextScrollCursorCenterTopBottom::Center,
13861 "After bottom, scrolling should start over",
13862 );
13863
13864 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13865 assert_eq!(
13866 e.next_scroll_position,
13867 NextScrollCursorCenterTopBottom::Top,
13868 "Scrolling continues if retriggered fast enough"
13869 );
13870 });
13871
13872 cx.executor()
13873 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13874 cx.executor().run_until_parked();
13875 cx.update_editor(|e, _| {
13876 assert_eq!(
13877 e.next_scroll_position,
13878 NextScrollCursorCenterTopBottom::Center,
13879 "If scrolling is not triggered fast enough, it should reset"
13880 );
13881 });
13882}
13883
13884#[gpui::test]
13885async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13886 init_test(cx, |_| {});
13887 let mut cx = EditorLspTestContext::new_rust(
13888 lsp::ServerCapabilities {
13889 definition_provider: Some(lsp::OneOf::Left(true)),
13890 references_provider: Some(lsp::OneOf::Left(true)),
13891 ..lsp::ServerCapabilities::default()
13892 },
13893 cx,
13894 )
13895 .await;
13896
13897 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13898 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13899 move |params, _| async move {
13900 if empty_go_to_definition {
13901 Ok(None)
13902 } else {
13903 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13904 uri: params.text_document_position_params.text_document.uri,
13905 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13906 })))
13907 }
13908 },
13909 );
13910 let references =
13911 cx.lsp
13912 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13913 Ok(Some(vec![lsp::Location {
13914 uri: params.text_document_position.text_document.uri,
13915 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13916 }]))
13917 });
13918 (go_to_definition, references)
13919 };
13920
13921 cx.set_state(
13922 &r#"fn one() {
13923 let mut a = ˇtwo();
13924 }
13925
13926 fn two() {}"#
13927 .unindent(),
13928 );
13929 set_up_lsp_handlers(false, &mut cx);
13930 let navigated = cx
13931 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13932 .await
13933 .expect("Failed to navigate to definition");
13934 assert_eq!(
13935 navigated,
13936 Navigated::Yes,
13937 "Should have navigated to definition from the GetDefinition response"
13938 );
13939 cx.assert_editor_state(
13940 &r#"fn one() {
13941 let mut a = two();
13942 }
13943
13944 fn «twoˇ»() {}"#
13945 .unindent(),
13946 );
13947
13948 let editors = cx.update_workspace(|workspace, cx| {
13949 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13950 });
13951 cx.update_editor(|_, test_editor_cx| {
13952 assert_eq!(
13953 editors.len(),
13954 1,
13955 "Initially, only one, test, editor should be open in the workspace"
13956 );
13957 assert_eq!(
13958 test_editor_cx.view(),
13959 editors.last().expect("Asserted len is 1")
13960 );
13961 });
13962
13963 set_up_lsp_handlers(true, &mut cx);
13964 let navigated = cx
13965 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13966 .await
13967 .expect("Failed to navigate to lookup references");
13968 assert_eq!(
13969 navigated,
13970 Navigated::Yes,
13971 "Should have navigated to references as a fallback after empty GoToDefinition response"
13972 );
13973 // We should not change the selections in the existing file,
13974 // if opening another milti buffer with the references
13975 cx.assert_editor_state(
13976 &r#"fn one() {
13977 let mut a = two();
13978 }
13979
13980 fn «twoˇ»() {}"#
13981 .unindent(),
13982 );
13983 let editors = cx.update_workspace(|workspace, cx| {
13984 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13985 });
13986 cx.update_editor(|_, test_editor_cx| {
13987 assert_eq!(
13988 editors.len(),
13989 2,
13990 "After falling back to references search, we open a new editor with the results"
13991 );
13992 let references_fallback_text = editors
13993 .into_iter()
13994 .find(|new_editor| new_editor != test_editor_cx.view())
13995 .expect("Should have one non-test editor now")
13996 .read(test_editor_cx)
13997 .text(test_editor_cx);
13998 assert_eq!(
13999 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14000 "Should use the range from the references response and not the GoToDefinition one"
14001 );
14002 });
14003}
14004
14005#[gpui::test]
14006async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14007 init_test(cx, |_| {});
14008
14009 let language = Arc::new(Language::new(
14010 LanguageConfig::default(),
14011 Some(tree_sitter_rust::LANGUAGE.into()),
14012 ));
14013
14014 let text = r#"
14015 #[cfg(test)]
14016 mod tests() {
14017 #[test]
14018 fn runnable_1() {
14019 let a = 1;
14020 }
14021
14022 #[test]
14023 fn runnable_2() {
14024 let a = 1;
14025 let b = 2;
14026 }
14027 }
14028 "#
14029 .unindent();
14030
14031 let fs = FakeFs::new(cx.executor());
14032 fs.insert_file("/file.rs", Default::default()).await;
14033
14034 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14035 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14036 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14037 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
14038 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14039
14040 let editor = cx.new_view(|cx| {
14041 Editor::new(
14042 EditorMode::Full,
14043 multi_buffer,
14044 Some(project.clone()),
14045 true,
14046 cx,
14047 )
14048 });
14049
14050 editor.update(cx, |editor, cx| {
14051 editor.tasks.insert(
14052 (buffer.read(cx).remote_id(), 3),
14053 RunnableTasks {
14054 templates: vec![],
14055 offset: MultiBufferOffset(43),
14056 column: 0,
14057 extra_variables: HashMap::default(),
14058 context_range: BufferOffset(43)..BufferOffset(85),
14059 },
14060 );
14061 editor.tasks.insert(
14062 (buffer.read(cx).remote_id(), 8),
14063 RunnableTasks {
14064 templates: vec![],
14065 offset: MultiBufferOffset(86),
14066 column: 0,
14067 extra_variables: HashMap::default(),
14068 context_range: BufferOffset(86)..BufferOffset(191),
14069 },
14070 );
14071
14072 // Test finding task when cursor is inside function body
14073 editor.change_selections(None, cx, |s| {
14074 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14075 });
14076 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14077 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14078
14079 // Test finding task when cursor is on function name
14080 editor.change_selections(None, cx, |s| {
14081 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14082 });
14083 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14084 assert_eq!(row, 8, "Should find task when cursor is on function name");
14085 });
14086}
14087
14088#[gpui::test]
14089async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14090 init_test(cx, |_| {});
14091
14092 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14093 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14094 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14095
14096 let fs = FakeFs::new(cx.executor());
14097 fs.insert_tree(
14098 "/a",
14099 json!({
14100 "first.rs": sample_text_1,
14101 "second.rs": sample_text_2,
14102 "third.rs": sample_text_3,
14103 }),
14104 )
14105 .await;
14106 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14107 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14108 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14109 let worktree = project.update(cx, |project, cx| {
14110 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14111 assert_eq!(worktrees.len(), 1);
14112 worktrees.pop().unwrap()
14113 });
14114 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14115
14116 let buffer_1 = project
14117 .update(cx, |project, cx| {
14118 project.open_buffer((worktree_id, "first.rs"), cx)
14119 })
14120 .await
14121 .unwrap();
14122 let buffer_2 = project
14123 .update(cx, |project, cx| {
14124 project.open_buffer((worktree_id, "second.rs"), cx)
14125 })
14126 .await
14127 .unwrap();
14128 let buffer_3 = project
14129 .update(cx, |project, cx| {
14130 project.open_buffer((worktree_id, "third.rs"), cx)
14131 })
14132 .await
14133 .unwrap();
14134
14135 let multi_buffer = cx.new_model(|cx| {
14136 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14137 multi_buffer.push_excerpts(
14138 buffer_1.clone(),
14139 [
14140 ExcerptRange {
14141 context: Point::new(0, 0)..Point::new(3, 0),
14142 primary: None,
14143 },
14144 ExcerptRange {
14145 context: Point::new(5, 0)..Point::new(7, 0),
14146 primary: None,
14147 },
14148 ExcerptRange {
14149 context: Point::new(9, 0)..Point::new(10, 4),
14150 primary: None,
14151 },
14152 ],
14153 cx,
14154 );
14155 multi_buffer.push_excerpts(
14156 buffer_2.clone(),
14157 [
14158 ExcerptRange {
14159 context: Point::new(0, 0)..Point::new(3, 0),
14160 primary: None,
14161 },
14162 ExcerptRange {
14163 context: Point::new(5, 0)..Point::new(7, 0),
14164 primary: None,
14165 },
14166 ExcerptRange {
14167 context: Point::new(9, 0)..Point::new(10, 4),
14168 primary: None,
14169 },
14170 ],
14171 cx,
14172 );
14173 multi_buffer.push_excerpts(
14174 buffer_3.clone(),
14175 [
14176 ExcerptRange {
14177 context: Point::new(0, 0)..Point::new(3, 0),
14178 primary: None,
14179 },
14180 ExcerptRange {
14181 context: Point::new(5, 0)..Point::new(7, 0),
14182 primary: None,
14183 },
14184 ExcerptRange {
14185 context: Point::new(9, 0)..Point::new(10, 4),
14186 primary: None,
14187 },
14188 ],
14189 cx,
14190 );
14191 multi_buffer
14192 });
14193 let multi_buffer_editor = cx.new_view(|cx| {
14194 Editor::new(
14195 EditorMode::Full,
14196 multi_buffer,
14197 Some(project.clone()),
14198 true,
14199 cx,
14200 )
14201 });
14202
14203 let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
14204 assert_eq!(
14205 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14206 full_text,
14207 );
14208
14209 multi_buffer_editor.update(cx, |editor, cx| {
14210 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14211 });
14212 assert_eq!(
14213 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14214 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14215 "After folding the first buffer, its text should not be displayed"
14216 );
14217
14218 multi_buffer_editor.update(cx, |editor, cx| {
14219 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14220 });
14221 assert_eq!(
14222 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14223 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14224 "After folding the second buffer, its text should not be displayed"
14225 );
14226
14227 multi_buffer_editor.update(cx, |editor, cx| {
14228 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14229 });
14230 assert_eq!(
14231 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14232 "\n\n\n\n\n",
14233 "After folding the third buffer, its text should not be displayed"
14234 );
14235
14236 // Emulate selection inside the fold logic, that should work
14237 multi_buffer_editor.update(cx, |editor, cx| {
14238 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14239 });
14240
14241 multi_buffer_editor.update(cx, |editor, cx| {
14242 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14243 });
14244 assert_eq!(
14245 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14246 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14247 "After unfolding the second buffer, its text should be displayed"
14248 );
14249
14250 multi_buffer_editor.update(cx, |editor, cx| {
14251 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14252 });
14253 assert_eq!(
14254 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14255 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14256 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14257 );
14258
14259 multi_buffer_editor.update(cx, |editor, cx| {
14260 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14261 });
14262 assert_eq!(
14263 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14264 full_text,
14265 "After unfolding the all buffers, all original text should be displayed"
14266 );
14267}
14268
14269#[gpui::test]
14270async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14271 init_test(cx, |_| {});
14272
14273 let sample_text_1 = "1111\n2222\n3333".to_string();
14274 let sample_text_2 = "4444\n5555\n6666".to_string();
14275 let sample_text_3 = "7777\n8888\n9999".to_string();
14276
14277 let fs = FakeFs::new(cx.executor());
14278 fs.insert_tree(
14279 "/a",
14280 json!({
14281 "first.rs": sample_text_1,
14282 "second.rs": sample_text_2,
14283 "third.rs": sample_text_3,
14284 }),
14285 )
14286 .await;
14287 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14288 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14289 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14290 let worktree = project.update(cx, |project, cx| {
14291 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14292 assert_eq!(worktrees.len(), 1);
14293 worktrees.pop().unwrap()
14294 });
14295 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14296
14297 let buffer_1 = project
14298 .update(cx, |project, cx| {
14299 project.open_buffer((worktree_id, "first.rs"), cx)
14300 })
14301 .await
14302 .unwrap();
14303 let buffer_2 = project
14304 .update(cx, |project, cx| {
14305 project.open_buffer((worktree_id, "second.rs"), cx)
14306 })
14307 .await
14308 .unwrap();
14309 let buffer_3 = project
14310 .update(cx, |project, cx| {
14311 project.open_buffer((worktree_id, "third.rs"), cx)
14312 })
14313 .await
14314 .unwrap();
14315
14316 let multi_buffer = cx.new_model(|cx| {
14317 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14318 multi_buffer.push_excerpts(
14319 buffer_1.clone(),
14320 [ExcerptRange {
14321 context: Point::new(0, 0)..Point::new(3, 0),
14322 primary: None,
14323 }],
14324 cx,
14325 );
14326 multi_buffer.push_excerpts(
14327 buffer_2.clone(),
14328 [ExcerptRange {
14329 context: Point::new(0, 0)..Point::new(3, 0),
14330 primary: None,
14331 }],
14332 cx,
14333 );
14334 multi_buffer.push_excerpts(
14335 buffer_3.clone(),
14336 [ExcerptRange {
14337 context: Point::new(0, 0)..Point::new(3, 0),
14338 primary: None,
14339 }],
14340 cx,
14341 );
14342 multi_buffer
14343 });
14344
14345 let multi_buffer_editor = cx.new_view(|cx| {
14346 Editor::new(
14347 EditorMode::Full,
14348 multi_buffer,
14349 Some(project.clone()),
14350 true,
14351 cx,
14352 )
14353 });
14354
14355 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14356 assert_eq!(
14357 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14358 full_text,
14359 );
14360
14361 multi_buffer_editor.update(cx, |editor, cx| {
14362 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14363 });
14364 assert_eq!(
14365 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14366 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14367 "After folding the first buffer, its text should not be displayed"
14368 );
14369
14370 multi_buffer_editor.update(cx, |editor, cx| {
14371 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14372 });
14373
14374 assert_eq!(
14375 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14376 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14377 "After folding the second buffer, its text should not be displayed"
14378 );
14379
14380 multi_buffer_editor.update(cx, |editor, cx| {
14381 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14382 });
14383 assert_eq!(
14384 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14385 "\n\n\n\n\n",
14386 "After folding the third buffer, its text should not be displayed"
14387 );
14388
14389 multi_buffer_editor.update(cx, |editor, cx| {
14390 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14391 });
14392 assert_eq!(
14393 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14394 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14395 "After unfolding the second buffer, its text should be displayed"
14396 );
14397
14398 multi_buffer_editor.update(cx, |editor, cx| {
14399 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14400 });
14401 assert_eq!(
14402 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14403 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14404 "After unfolding the first buffer, its text should be displayed"
14405 );
14406
14407 multi_buffer_editor.update(cx, |editor, cx| {
14408 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14409 });
14410 assert_eq!(
14411 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14412 full_text,
14413 "After unfolding all buffers, all original text should be displayed"
14414 );
14415}
14416
14417#[gpui::test]
14418async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14419 init_test(cx, |_| {});
14420
14421 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14422
14423 let fs = FakeFs::new(cx.executor());
14424 fs.insert_tree(
14425 "/a",
14426 json!({
14427 "main.rs": sample_text,
14428 }),
14429 )
14430 .await;
14431 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14432 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14433 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14434 let worktree = project.update(cx, |project, cx| {
14435 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14436 assert_eq!(worktrees.len(), 1);
14437 worktrees.pop().unwrap()
14438 });
14439 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14440
14441 let buffer_1 = project
14442 .update(cx, |project, cx| {
14443 project.open_buffer((worktree_id, "main.rs"), cx)
14444 })
14445 .await
14446 .unwrap();
14447
14448 let multi_buffer = cx.new_model(|cx| {
14449 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14450 multi_buffer.push_excerpts(
14451 buffer_1.clone(),
14452 [ExcerptRange {
14453 context: Point::new(0, 0)
14454 ..Point::new(
14455 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14456 0,
14457 ),
14458 primary: None,
14459 }],
14460 cx,
14461 );
14462 multi_buffer
14463 });
14464 let multi_buffer_editor = cx.new_view(|cx| {
14465 Editor::new(
14466 EditorMode::Full,
14467 multi_buffer,
14468 Some(project.clone()),
14469 true,
14470 cx,
14471 )
14472 });
14473
14474 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14475 multi_buffer_editor.update(cx, |editor, cx| {
14476 enum TestHighlight {}
14477 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14478 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14479 editor.highlight_text::<TestHighlight>(
14480 vec![highlight_range.clone()],
14481 HighlightStyle::color(Hsla::green()),
14482 cx,
14483 );
14484 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14485 });
14486
14487 let full_text = format!("\n\n\n{sample_text}\n");
14488 assert_eq!(
14489 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14490 full_text,
14491 );
14492}
14493
14494#[gpui::test]
14495fn test_inline_completion_text(cx: &mut TestAppContext) {
14496 init_test(cx, |_| {});
14497
14498 // Simple insertion
14499 {
14500 let window = cx.add_window(|cx| {
14501 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14502 Editor::new(EditorMode::Full, buffer, None, true, cx)
14503 });
14504 let cx = &mut VisualTestContext::from_window(*window, cx);
14505
14506 window
14507 .update(cx, |editor, cx| {
14508 let snapshot = editor.snapshot(cx);
14509 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14510 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14511 let edits = vec![(edit_range, " beautiful".to_string())];
14512
14513 let InlineCompletionText::Edit { text, highlights } =
14514 inline_completion_edit_text(&snapshot, &edits, false, cx)
14515 else {
14516 panic!("Failed to generate inline completion text");
14517 };
14518
14519 assert_eq!(text, "Hello, beautiful world!");
14520 assert_eq!(highlights.len(), 1);
14521 assert_eq!(highlights[0].0, 6..16);
14522 assert_eq!(
14523 highlights[0].1.background_color,
14524 Some(cx.theme().status().created_background)
14525 );
14526 })
14527 .unwrap();
14528 }
14529
14530 // Replacement
14531 {
14532 let window = cx.add_window(|cx| {
14533 let buffer = MultiBuffer::build_simple("This is a test.", cx);
14534 Editor::new(EditorMode::Full, buffer, None, true, cx)
14535 });
14536 let cx = &mut VisualTestContext::from_window(*window, cx);
14537
14538 window
14539 .update(cx, |editor, cx| {
14540 let snapshot = editor.snapshot(cx);
14541 let edits = vec![(
14542 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14543 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
14544 "That".to_string(),
14545 )];
14546
14547 let InlineCompletionText::Edit { text, highlights } =
14548 inline_completion_edit_text(&snapshot, &edits, false, cx)
14549 else {
14550 panic!("Failed to generate inline completion text");
14551 };
14552
14553 assert_eq!(text, "That is a test.");
14554 assert_eq!(highlights.len(), 1);
14555 assert_eq!(highlights[0].0, 0..4);
14556 assert_eq!(
14557 highlights[0].1.background_color,
14558 Some(cx.theme().status().created_background)
14559 );
14560 })
14561 .unwrap();
14562 }
14563
14564 // Multiple edits
14565 {
14566 let window = cx.add_window(|cx| {
14567 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14568 Editor::new(EditorMode::Full, buffer, None, true, cx)
14569 });
14570 let cx = &mut VisualTestContext::from_window(*window, cx);
14571
14572 window
14573 .update(cx, |editor, cx| {
14574 let snapshot = editor.snapshot(cx);
14575 let edits = vec![
14576 (
14577 snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
14578 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
14579 "Greetings".into(),
14580 ),
14581 (
14582 snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
14583 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
14584 " and universe".into(),
14585 ),
14586 ];
14587
14588 let InlineCompletionText::Edit { text, highlights } =
14589 inline_completion_edit_text(&snapshot, &edits, false, cx)
14590 else {
14591 panic!("Failed to generate inline completion text");
14592 };
14593
14594 assert_eq!(text, "Greetings, world and universe!");
14595 assert_eq!(highlights.len(), 2);
14596 assert_eq!(highlights[0].0, 0..9);
14597 assert_eq!(highlights[1].0, 16..29);
14598 assert_eq!(
14599 highlights[0].1.background_color,
14600 Some(cx.theme().status().created_background)
14601 );
14602 assert_eq!(
14603 highlights[1].1.background_color,
14604 Some(cx.theme().status().created_background)
14605 );
14606 })
14607 .unwrap();
14608 }
14609
14610 // Multiple lines with edits
14611 {
14612 let window = cx.add_window(|cx| {
14613 let buffer =
14614 MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
14615 Editor::new(EditorMode::Full, buffer, None, true, cx)
14616 });
14617 let cx = &mut VisualTestContext::from_window(*window, cx);
14618
14619 window
14620 .update(cx, |editor, cx| {
14621 let snapshot = editor.snapshot(cx);
14622 let edits = vec![
14623 (
14624 snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
14625 ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
14626 "modified".to_string(),
14627 ),
14628 (
14629 snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
14630 ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
14631 "New third line".to_string(),
14632 ),
14633 (
14634 snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
14635 ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
14636 " updated".to_string(),
14637 ),
14638 ];
14639
14640 let InlineCompletionText::Edit { text, highlights } =
14641 inline_completion_edit_text(&snapshot, &edits, false, cx)
14642 else {
14643 panic!("Failed to generate inline completion text");
14644 };
14645
14646 assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
14647 assert_eq!(highlights.len(), 3);
14648 assert_eq!(highlights[0].0, 7..15); // "modified"
14649 assert_eq!(highlights[1].0, 16..30); // "New third line"
14650 assert_eq!(highlights[2].0, 37..45); // " updated"
14651
14652 for highlight in &highlights {
14653 assert_eq!(
14654 highlight.1.background_color,
14655 Some(cx.theme().status().created_background)
14656 );
14657 }
14658 })
14659 .unwrap();
14660 }
14661}
14662
14663#[gpui::test]
14664fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
14665 init_test(cx, |_| {});
14666
14667 // Deletion
14668 {
14669 let window = cx.add_window(|cx| {
14670 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14671 Editor::new(EditorMode::Full, buffer, None, true, cx)
14672 });
14673 let cx = &mut VisualTestContext::from_window(*window, cx);
14674
14675 window
14676 .update(cx, |editor, cx| {
14677 let snapshot = editor.snapshot(cx);
14678 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
14679 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
14680 let edits = vec![(edit_range, "".to_string())];
14681
14682 let InlineCompletionText::Edit { text, highlights } =
14683 inline_completion_edit_text(&snapshot, &edits, true, cx)
14684 else {
14685 panic!("Failed to generate inline completion text");
14686 };
14687
14688 assert_eq!(text, "Hello, world!");
14689 assert_eq!(highlights.len(), 1);
14690 assert_eq!(highlights[0].0, 5..11);
14691 assert_eq!(
14692 highlights[0].1.background_color,
14693 Some(cx.theme().status().deleted_background)
14694 );
14695 })
14696 .unwrap();
14697 }
14698
14699 // Insertion
14700 {
14701 let window = cx.add_window(|cx| {
14702 let buffer = MultiBuffer::build_simple("Hello, world!", cx);
14703 Editor::new(EditorMode::Full, buffer, None, true, cx)
14704 });
14705 let cx = &mut VisualTestContext::from_window(*window, cx);
14706
14707 window
14708 .update(cx, |editor, cx| {
14709 let snapshot = editor.snapshot(cx);
14710 let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
14711 ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
14712 let edits = vec![(edit_range, " digital".to_string())];
14713
14714 let InlineCompletionText::Edit { text, highlights } =
14715 inline_completion_edit_text(&snapshot, &edits, true, cx)
14716 else {
14717 panic!("Failed to generate inline completion text");
14718 };
14719
14720 assert_eq!(text, "Hello, digital world!");
14721 assert_eq!(highlights.len(), 1);
14722 assert_eq!(highlights[0].0, 6..14);
14723 assert_eq!(
14724 highlights[0].1.background_color,
14725 Some(cx.theme().status().created_background)
14726 );
14727 })
14728 .unwrap();
14729 }
14730}
14731
14732#[gpui::test]
14733async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
14734 init_test(cx, |_| {});
14735 let capabilities = lsp::ServerCapabilities {
14736 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
14737 prepare_provider: Some(true),
14738 work_done_progress_options: Default::default(),
14739 })),
14740 ..Default::default()
14741 };
14742 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
14743
14744 cx.set_state(indoc! {"
14745 struct Fˇoo {}
14746 "});
14747
14748 cx.update_editor(|editor, cx| {
14749 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
14750 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
14751 editor.highlight_background::<DocumentHighlightRead>(
14752 &[highlight_range],
14753 |c| c.editor_document_highlight_read_background,
14754 cx,
14755 );
14756 });
14757
14758 let mut prepare_rename_handler =
14759 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
14760 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
14761 start: lsp::Position {
14762 line: 0,
14763 character: 7,
14764 },
14765 end: lsp::Position {
14766 line: 0,
14767 character: 10,
14768 },
14769 })))
14770 });
14771 let prepare_rename_task = cx
14772 .update_editor(|e, cx| e.rename(&Rename, cx))
14773 .expect("Prepare rename was not started");
14774 prepare_rename_handler.next().await.unwrap();
14775 prepare_rename_task.await.expect("Prepare rename failed");
14776
14777 let mut rename_handler =
14778 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
14779 let edit = lsp::TextEdit {
14780 range: lsp::Range {
14781 start: lsp::Position {
14782 line: 0,
14783 character: 7,
14784 },
14785 end: lsp::Position {
14786 line: 0,
14787 character: 10,
14788 },
14789 },
14790 new_text: "FooRenamed".to_string(),
14791 };
14792 Ok(Some(lsp::WorkspaceEdit::new(
14793 // Specify the same edit twice
14794 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
14795 )))
14796 });
14797 let rename_task = cx
14798 .update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
14799 .expect("Confirm rename was not started");
14800 rename_handler.next().await.unwrap();
14801 rename_task.await.expect("Confirm rename failed");
14802 cx.run_until_parked();
14803
14804 // Despite two edits, only one is actually applied as those are identical
14805 cx.assert_editor_state(indoc! {"
14806 struct FooRenamedˇ {}
14807 "});
14808}
14809
14810#[gpui::test]
14811async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
14812 init_test(cx, |_| {});
14813 // These capabilities indicate that the server does not support prepare rename.
14814 let capabilities = lsp::ServerCapabilities {
14815 rename_provider: Some(lsp::OneOf::Left(true)),
14816 ..Default::default()
14817 };
14818 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
14819
14820 cx.set_state(indoc! {"
14821 struct Fˇoo {}
14822 "});
14823
14824 cx.update_editor(|editor, cx| {
14825 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
14826 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
14827 editor.highlight_background::<DocumentHighlightRead>(
14828 &[highlight_range],
14829 |c| c.editor_document_highlight_read_background,
14830 cx,
14831 );
14832 });
14833
14834 cx.update_editor(|e, cx| e.rename(&Rename, cx))
14835 .expect("Prepare rename was not started")
14836 .await
14837 .expect("Prepare rename failed");
14838
14839 let mut rename_handler =
14840 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
14841 let edit = lsp::TextEdit {
14842 range: lsp::Range {
14843 start: lsp::Position {
14844 line: 0,
14845 character: 7,
14846 },
14847 end: lsp::Position {
14848 line: 0,
14849 character: 10,
14850 },
14851 },
14852 new_text: "FooRenamed".to_string(),
14853 };
14854 Ok(Some(lsp::WorkspaceEdit::new(
14855 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
14856 )))
14857 });
14858 let rename_task = cx
14859 .update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
14860 .expect("Confirm rename was not started");
14861 rename_handler.next().await.unwrap();
14862 rename_task.await.expect("Confirm rename failed");
14863 cx.run_until_parked();
14864
14865 // Correct range is renamed, as `surrounding_word` is used to find it.
14866 cx.assert_editor_state(indoc! {"
14867 struct FooRenamedˇ {}
14868 "});
14869}
14870
14871fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
14872 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
14873 point..point
14874}
14875
14876fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
14877 let (text, ranges) = marked_text_ranges(marked_text, true);
14878 assert_eq!(view.text(cx), text);
14879 assert_eq!(
14880 view.selections.ranges(cx),
14881 ranges,
14882 "Assert selections are {}",
14883 marked_text
14884 );
14885}
14886
14887pub fn handle_signature_help_request(
14888 cx: &mut EditorLspTestContext,
14889 mocked_response: lsp::SignatureHelp,
14890) -> impl Future<Output = ()> {
14891 let mut request =
14892 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
14893 let mocked_response = mocked_response.clone();
14894 async move { Ok(Some(mocked_response)) }
14895 });
14896
14897 async move {
14898 request.next().await;
14899 }
14900}
14901
14902/// Handle completion request passing a marked string specifying where the completion
14903/// should be triggered from using '|' character, what range should be replaced, and what completions
14904/// should be returned using '<' and '>' to delimit the range
14905pub fn handle_completion_request(
14906 cx: &mut EditorLspTestContext,
14907 marked_string: &str,
14908 completions: Vec<&'static str>,
14909 counter: Arc<AtomicUsize>,
14910) -> impl Future<Output = ()> {
14911 let complete_from_marker: TextRangeMarker = '|'.into();
14912 let replace_range_marker: TextRangeMarker = ('<', '>').into();
14913 let (_, mut marked_ranges) = marked_text_ranges_by(
14914 marked_string,
14915 vec![complete_from_marker.clone(), replace_range_marker.clone()],
14916 );
14917
14918 let complete_from_position =
14919 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
14920 let replace_range =
14921 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
14922
14923 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
14924 let completions = completions.clone();
14925 counter.fetch_add(1, atomic::Ordering::Release);
14926 async move {
14927 assert_eq!(params.text_document_position.text_document.uri, url.clone());
14928 assert_eq!(
14929 params.text_document_position.position,
14930 complete_from_position
14931 );
14932 Ok(Some(lsp::CompletionResponse::Array(
14933 completions
14934 .iter()
14935 .map(|completion_text| lsp::CompletionItem {
14936 label: completion_text.to_string(),
14937 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14938 range: replace_range,
14939 new_text: completion_text.to_string(),
14940 })),
14941 ..Default::default()
14942 })
14943 .collect(),
14944 )))
14945 }
14946 });
14947
14948 async move {
14949 request.next().await;
14950 }
14951}
14952
14953fn handle_resolve_completion_request(
14954 cx: &mut EditorLspTestContext,
14955 edits: Option<Vec<(&'static str, &'static str)>>,
14956) -> impl Future<Output = ()> {
14957 let edits = edits.map(|edits| {
14958 edits
14959 .iter()
14960 .map(|(marked_string, new_text)| {
14961 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
14962 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14963 lsp::TextEdit::new(replace_range, new_text.to_string())
14964 })
14965 .collect::<Vec<_>>()
14966 });
14967
14968 let mut request =
14969 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14970 let edits = edits.clone();
14971 async move {
14972 Ok(lsp::CompletionItem {
14973 additional_text_edits: edits,
14974 ..Default::default()
14975 })
14976 }
14977 });
14978
14979 async move {
14980 request.next().await;
14981 }
14982}
14983
14984pub(crate) fn update_test_language_settings(
14985 cx: &mut TestAppContext,
14986 f: impl Fn(&mut AllLanguageSettingsContent),
14987) {
14988 cx.update(|cx| {
14989 SettingsStore::update_global(cx, |store, cx| {
14990 store.update_user_settings::<AllLanguageSettings>(cx, f);
14991 });
14992 });
14993}
14994
14995pub(crate) fn update_test_project_settings(
14996 cx: &mut TestAppContext,
14997 f: impl Fn(&mut ProjectSettings),
14998) {
14999 cx.update(|cx| {
15000 SettingsStore::update_global(cx, |store, cx| {
15001 store.update_user_settings::<ProjectSettings>(cx, f);
15002 });
15003 });
15004}
15005
15006pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
15007 cx.update(|cx| {
15008 assets::Assets.load_test_fonts(cx);
15009 let store = SettingsStore::test(cx);
15010 cx.set_global(store);
15011 theme::init(theme::LoadThemes::JustBase, cx);
15012 release_channel::init(SemanticVersion::default(), cx);
15013 client::init_settings(cx);
15014 language::init(cx);
15015 Project::init_settings(cx);
15016 workspace::init_settings(cx);
15017 crate::init(cx);
15018 });
15019
15020 update_test_language_settings(cx, f);
15021}
15022
15023#[track_caller]
15024fn assert_hunk_revert(
15025 not_reverted_text_with_selections: &str,
15026 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
15027 expected_reverted_text_with_selections: &str,
15028 base_text: &str,
15029 cx: &mut EditorLspTestContext,
15030) {
15031 cx.set_state(not_reverted_text_with_selections);
15032 cx.set_diff_base(base_text);
15033 cx.executor().run_until_parked();
15034
15035 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
15036 let snapshot = editor.snapshot(cx);
15037 let reverted_hunk_statuses = snapshot
15038 .diff_map
15039 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
15040 .map(|hunk| hunk_status(&hunk))
15041 .collect::<Vec<_>>();
15042
15043 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
15044 reverted_hunk_statuses
15045 });
15046 cx.executor().run_until_parked();
15047 cx.assert_editor_state(expected_reverted_text_with_selections);
15048 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
15049}