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 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3709 cx.set_state(indoc! {"
3710 «The quick brown
3711 fox jumps over
3712 the lazy dogˇ»
3713 "});
3714 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3715 cx.assert_editor_state(indoc! {"
3716 «The Quick Brown
3717 Fox Jumps Over
3718 The Lazy Dogˇ»
3719 "});
3720
3721 // Test multiple line, single selection case
3722 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3723 cx.set_state(indoc! {"
3724 «The quick brown
3725 fox jumps over
3726 the lazy dogˇ»
3727 "});
3728 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3729 cx.assert_editor_state(indoc! {"
3730 «TheQuickBrown
3731 FoxJumpsOver
3732 TheLazyDogˇ»
3733 "});
3734
3735 // From here on out, test more complex cases of manipulate_text()
3736
3737 // Test no selection case - should affect words cursors are in
3738 // Cursor at beginning, middle, and end of word
3739 cx.set_state(indoc! {"
3740 ˇhello big beauˇtiful worldˇ
3741 "});
3742 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3743 cx.assert_editor_state(indoc! {"
3744 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3745 "});
3746
3747 // Test multiple selections on a single line and across multiple lines
3748 cx.set_state(indoc! {"
3749 «Theˇ» quick «brown
3750 foxˇ» jumps «overˇ»
3751 the «lazyˇ» dog
3752 "});
3753 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3754 cx.assert_editor_state(indoc! {"
3755 «THEˇ» quick «BROWN
3756 FOXˇ» jumps «OVERˇ»
3757 the «LAZYˇ» dog
3758 "});
3759
3760 // Test case where text length grows
3761 cx.set_state(indoc! {"
3762 «tschüߡ»
3763 "});
3764 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3765 cx.assert_editor_state(indoc! {"
3766 «TSCHÜSSˇ»
3767 "});
3768
3769 // Test to make sure we don't crash when text shrinks
3770 cx.set_state(indoc! {"
3771 aaa_bbbˇ
3772 "});
3773 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3774 cx.assert_editor_state(indoc! {"
3775 «aaaBbbˇ»
3776 "});
3777
3778 // Test to make sure we all aware of the fact that each word can grow and shrink
3779 // Final selections should be aware of this fact
3780 cx.set_state(indoc! {"
3781 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3782 "});
3783 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3784 cx.assert_editor_state(indoc! {"
3785 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3786 "});
3787
3788 cx.set_state(indoc! {"
3789 «hElLo, WoRld!ˇ»
3790 "});
3791 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3792 cx.assert_editor_state(indoc! {"
3793 «HeLlO, wOrLD!ˇ»
3794 "});
3795}
3796
3797#[gpui::test]
3798fn test_duplicate_line(cx: &mut TestAppContext) {
3799 init_test(cx, |_| {});
3800
3801 let view = cx.add_window(|cx| {
3802 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3803 build_editor(buffer, cx)
3804 });
3805 _ = view.update(cx, |view, cx| {
3806 view.change_selections(None, cx, |s| {
3807 s.select_display_ranges([
3808 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3809 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3810 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3811 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3812 ])
3813 });
3814 view.duplicate_line_down(&DuplicateLineDown, cx);
3815 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3816 assert_eq!(
3817 view.selections.display_ranges(cx),
3818 vec![
3819 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3820 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3821 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3822 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3823 ]
3824 );
3825 });
3826
3827 let view = cx.add_window(|cx| {
3828 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3829 build_editor(buffer, cx)
3830 });
3831 _ = view.update(cx, |view, cx| {
3832 view.change_selections(None, cx, |s| {
3833 s.select_display_ranges([
3834 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3835 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3836 ])
3837 });
3838 view.duplicate_line_down(&DuplicateLineDown, cx);
3839 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3840 assert_eq!(
3841 view.selections.display_ranges(cx),
3842 vec![
3843 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3844 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3845 ]
3846 );
3847 });
3848
3849 // With `move_upwards` the selections stay in place, except for
3850 // the lines inserted above them
3851 let view = cx.add_window(|cx| {
3852 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3853 build_editor(buffer, cx)
3854 });
3855 _ = view.update(cx, |view, cx| {
3856 view.change_selections(None, cx, |s| {
3857 s.select_display_ranges([
3858 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3859 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3860 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3861 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3862 ])
3863 });
3864 view.duplicate_line_up(&DuplicateLineUp, cx);
3865 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3866 assert_eq!(
3867 view.selections.display_ranges(cx),
3868 vec![
3869 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3870 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3871 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3872 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3873 ]
3874 );
3875 });
3876
3877 let view = cx.add_window(|cx| {
3878 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3879 build_editor(buffer, cx)
3880 });
3881 _ = view.update(cx, |view, cx| {
3882 view.change_selections(None, cx, |s| {
3883 s.select_display_ranges([
3884 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3885 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3886 ])
3887 });
3888 view.duplicate_line_up(&DuplicateLineUp, cx);
3889 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3890 assert_eq!(
3891 view.selections.display_ranges(cx),
3892 vec![
3893 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3894 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3895 ]
3896 );
3897 });
3898}
3899
3900#[gpui::test]
3901fn test_move_line_up_down(cx: &mut TestAppContext) {
3902 init_test(cx, |_| {});
3903
3904 let view = cx.add_window(|cx| {
3905 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3906 build_editor(buffer, cx)
3907 });
3908 _ = view.update(cx, |view, cx| {
3909 view.fold_creases(
3910 vec![
3911 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3912 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3913 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3914 ],
3915 true,
3916 cx,
3917 );
3918 view.change_selections(None, cx, |s| {
3919 s.select_display_ranges([
3920 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3921 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3922 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3923 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3924 ])
3925 });
3926 assert_eq!(
3927 view.display_text(cx),
3928 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3929 );
3930
3931 view.move_line_up(&MoveLineUp, cx);
3932 assert_eq!(
3933 view.display_text(cx),
3934 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3935 );
3936 assert_eq!(
3937 view.selections.display_ranges(cx),
3938 vec![
3939 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3940 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3941 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3942 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3943 ]
3944 );
3945 });
3946
3947 _ = view.update(cx, |view, cx| {
3948 view.move_line_down(&MoveLineDown, cx);
3949 assert_eq!(
3950 view.display_text(cx),
3951 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3952 );
3953 assert_eq!(
3954 view.selections.display_ranges(cx),
3955 vec![
3956 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3957 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3958 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3959 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3960 ]
3961 );
3962 });
3963
3964 _ = view.update(cx, |view, cx| {
3965 view.move_line_down(&MoveLineDown, cx);
3966 assert_eq!(
3967 view.display_text(cx),
3968 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3969 );
3970 assert_eq!(
3971 view.selections.display_ranges(cx),
3972 vec![
3973 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3974 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3975 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3976 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3977 ]
3978 );
3979 });
3980
3981 _ = view.update(cx, |view, cx| {
3982 view.move_line_up(&MoveLineUp, cx);
3983 assert_eq!(
3984 view.display_text(cx),
3985 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3986 );
3987 assert_eq!(
3988 view.selections.display_ranges(cx),
3989 vec![
3990 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3991 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3992 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3993 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3994 ]
3995 );
3996 });
3997}
3998
3999#[gpui::test]
4000fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4001 init_test(cx, |_| {});
4002
4003 let editor = cx.add_window(|cx| {
4004 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4005 build_editor(buffer, cx)
4006 });
4007 _ = editor.update(cx, |editor, cx| {
4008 let snapshot = editor.buffer.read(cx).snapshot(cx);
4009 editor.insert_blocks(
4010 [BlockProperties {
4011 style: BlockStyle::Fixed,
4012 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4013 height: 1,
4014 render: Arc::new(|_| div().into_any()),
4015 priority: 0,
4016 }],
4017 Some(Autoscroll::fit()),
4018 cx,
4019 );
4020 editor.change_selections(None, cx, |s| {
4021 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4022 });
4023 editor.move_line_down(&MoveLineDown, cx);
4024 });
4025}
4026
4027#[gpui::test]
4028async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4029 init_test(cx, |_| {});
4030
4031 let mut cx = EditorTestContext::new(cx).await;
4032 cx.set_state(
4033 &"
4034 ˇzero
4035 one
4036 two
4037 three
4038 four
4039 five
4040 "
4041 .unindent(),
4042 );
4043
4044 // Create a four-line block that replaces three lines of text.
4045 cx.update_editor(|editor, cx| {
4046 let snapshot = editor.snapshot(cx);
4047 let snapshot = &snapshot.buffer_snapshot;
4048 let placement = BlockPlacement::Replace(
4049 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4050 );
4051 editor.insert_blocks(
4052 [BlockProperties {
4053 placement,
4054 height: 4,
4055 style: BlockStyle::Sticky,
4056 render: Arc::new(|_| gpui::div().into_any_element()),
4057 priority: 0,
4058 }],
4059 None,
4060 cx,
4061 );
4062 });
4063
4064 // Move down so that the cursor touches the block.
4065 cx.update_editor(|editor, cx| {
4066 editor.move_down(&Default::default(), cx);
4067 });
4068 cx.assert_editor_state(
4069 &"
4070 zero
4071 «one
4072 two
4073 threeˇ»
4074 four
4075 five
4076 "
4077 .unindent(),
4078 );
4079
4080 // Move down past the block.
4081 cx.update_editor(|editor, cx| {
4082 editor.move_down(&Default::default(), cx);
4083 });
4084 cx.assert_editor_state(
4085 &"
4086 zero
4087 one
4088 two
4089 three
4090 ˇfour
4091 five
4092 "
4093 .unindent(),
4094 );
4095}
4096
4097#[gpui::test]
4098fn test_transpose(cx: &mut TestAppContext) {
4099 init_test(cx, |_| {});
4100
4101 _ = cx.add_window(|cx| {
4102 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4103 editor.set_style(EditorStyle::default(), cx);
4104 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4105 editor.transpose(&Default::default(), cx);
4106 assert_eq!(editor.text(cx), "bac");
4107 assert_eq!(editor.selections.ranges(cx), [2..2]);
4108
4109 editor.transpose(&Default::default(), cx);
4110 assert_eq!(editor.text(cx), "bca");
4111 assert_eq!(editor.selections.ranges(cx), [3..3]);
4112
4113 editor.transpose(&Default::default(), cx);
4114 assert_eq!(editor.text(cx), "bac");
4115 assert_eq!(editor.selections.ranges(cx), [3..3]);
4116
4117 editor
4118 });
4119
4120 _ = cx.add_window(|cx| {
4121 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4122 editor.set_style(EditorStyle::default(), cx);
4123 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4124 editor.transpose(&Default::default(), cx);
4125 assert_eq!(editor.text(cx), "acb\nde");
4126 assert_eq!(editor.selections.ranges(cx), [3..3]);
4127
4128 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "acbd\ne");
4131 assert_eq!(editor.selections.ranges(cx), [5..5]);
4132
4133 editor.transpose(&Default::default(), cx);
4134 assert_eq!(editor.text(cx), "acbde\n");
4135 assert_eq!(editor.selections.ranges(cx), [6..6]);
4136
4137 editor.transpose(&Default::default(), cx);
4138 assert_eq!(editor.text(cx), "acbd\ne");
4139 assert_eq!(editor.selections.ranges(cx), [6..6]);
4140
4141 editor
4142 });
4143
4144 _ = cx.add_window(|cx| {
4145 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4146 editor.set_style(EditorStyle::default(), cx);
4147 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4148 editor.transpose(&Default::default(), cx);
4149 assert_eq!(editor.text(cx), "bacd\ne");
4150 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4151
4152 editor.transpose(&Default::default(), cx);
4153 assert_eq!(editor.text(cx), "bcade\n");
4154 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4155
4156 editor.transpose(&Default::default(), cx);
4157 assert_eq!(editor.text(cx), "bcda\ne");
4158 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4159
4160 editor.transpose(&Default::default(), cx);
4161 assert_eq!(editor.text(cx), "bcade\n");
4162 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4163
4164 editor.transpose(&Default::default(), cx);
4165 assert_eq!(editor.text(cx), "bcaed\n");
4166 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4167
4168 editor
4169 });
4170
4171 _ = cx.add_window(|cx| {
4172 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4173 editor.set_style(EditorStyle::default(), cx);
4174 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4175 editor.transpose(&Default::default(), cx);
4176 assert_eq!(editor.text(cx), "🏀🍐✋");
4177 assert_eq!(editor.selections.ranges(cx), [8..8]);
4178
4179 editor.transpose(&Default::default(), cx);
4180 assert_eq!(editor.text(cx), "🏀✋🍐");
4181 assert_eq!(editor.selections.ranges(cx), [11..11]);
4182
4183 editor.transpose(&Default::default(), cx);
4184 assert_eq!(editor.text(cx), "🏀🍐✋");
4185 assert_eq!(editor.selections.ranges(cx), [11..11]);
4186
4187 editor
4188 });
4189}
4190
4191#[gpui::test]
4192async fn test_rewrap(cx: &mut TestAppContext) {
4193 init_test(cx, |_| {});
4194
4195 let mut cx = EditorTestContext::new(cx).await;
4196
4197 let language_with_c_comments = Arc::new(Language::new(
4198 LanguageConfig {
4199 line_comments: vec!["// ".into()],
4200 ..LanguageConfig::default()
4201 },
4202 None,
4203 ));
4204 let language_with_pound_comments = Arc::new(Language::new(
4205 LanguageConfig {
4206 line_comments: vec!["# ".into()],
4207 ..LanguageConfig::default()
4208 },
4209 None,
4210 ));
4211 let markdown_language = Arc::new(Language::new(
4212 LanguageConfig {
4213 name: "Markdown".into(),
4214 ..LanguageConfig::default()
4215 },
4216 None,
4217 ));
4218 let language_with_doc_comments = Arc::new(Language::new(
4219 LanguageConfig {
4220 line_comments: vec!["// ".into(), "/// ".into()],
4221 ..LanguageConfig::default()
4222 },
4223 Some(tree_sitter_rust::LANGUAGE.into()),
4224 ));
4225
4226 let plaintext_language = Arc::new(Language::new(
4227 LanguageConfig {
4228 name: "Plain Text".into(),
4229 ..LanguageConfig::default()
4230 },
4231 None,
4232 ));
4233
4234 assert_rewrap(
4235 indoc! {"
4236 // ˇ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.
4237 "},
4238 indoc! {"
4239 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4240 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4241 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4242 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4243 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4244 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4245 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4246 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4247 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4248 // porttitor id. Aliquam id accumsan eros.
4249 "},
4250 language_with_c_comments.clone(),
4251 &mut cx,
4252 );
4253
4254 // Test that rewrapping works inside of a selection
4255 assert_rewrap(
4256 indoc! {"
4257 «// 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.ˇ»
4258 "},
4259 indoc! {"
4260 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4261 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4262 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4263 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4264 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4265 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4266 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4267 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4268 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4269 // porttitor id. Aliquam id accumsan eros.ˇ»
4270 "},
4271 language_with_c_comments.clone(),
4272 &mut cx,
4273 );
4274
4275 // Test that cursors that expand to the same region are collapsed.
4276 assert_rewrap(
4277 indoc! {"
4278 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4279 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4280 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4281 // ˇ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.
4282 "},
4283 indoc! {"
4284 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4285 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4286 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4287 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4288 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4289 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4290 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4291 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4292 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4293 // porttitor id. Aliquam id accumsan eros.
4294 "},
4295 language_with_c_comments.clone(),
4296 &mut cx,
4297 );
4298
4299 // Test that non-contiguous selections are treated separately.
4300 assert_rewrap(
4301 indoc! {"
4302 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4303 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4304 //
4305 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4306 // ˇ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.
4307 "},
4308 indoc! {"
4309 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4310 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4311 // auctor, eu lacinia sapien scelerisque.
4312 //
4313 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4314 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4315 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4316 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4317 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4318 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4319 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4320 "},
4321 language_with_c_comments.clone(),
4322 &mut cx,
4323 );
4324
4325 // Test that different comment prefixes are supported.
4326 assert_rewrap(
4327 indoc! {"
4328 # ˇ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.
4329 "},
4330 indoc! {"
4331 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4332 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4333 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4334 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4335 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4336 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4337 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4338 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4339 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4340 # accumsan eros.
4341 "},
4342 language_with_pound_comments.clone(),
4343 &mut cx,
4344 );
4345
4346 // Test that rewrapping is ignored outside of comments in most languages.
4347 assert_rewrap(
4348 indoc! {"
4349 /// Adds two numbers.
4350 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4351 fn add(a: u32, b: u32) -> u32 {
4352 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ˇ
4353 }
4354 "},
4355 indoc! {"
4356 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4357 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4358 fn add(a: u32, b: u32) -> u32 {
4359 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ˇ
4360 }
4361 "},
4362 language_with_doc_comments.clone(),
4363 &mut cx,
4364 );
4365
4366 // Test that rewrapping works in Markdown and Plain Text languages.
4367 assert_rewrap(
4368 indoc! {"
4369 # Hello
4370
4371 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.
4372 "},
4373 indoc! {"
4374 # Hello
4375
4376 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4377 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4378 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4379 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4380 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4381 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4382 Integer sit amet scelerisque nisi.
4383 "},
4384 markdown_language,
4385 &mut cx,
4386 );
4387
4388 assert_rewrap(
4389 indoc! {"
4390 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.
4391 "},
4392 indoc! {"
4393 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4394 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4395 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4396 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4397 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4398 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4399 Integer sit amet scelerisque nisi.
4400 "},
4401 plaintext_language,
4402 &mut cx,
4403 );
4404
4405 // Test rewrapping unaligned comments in a selection.
4406 assert_rewrap(
4407 indoc! {"
4408 fn foo() {
4409 if true {
4410 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4411 // Praesent semper egestas tellus id dignissim.ˇ»
4412 do_something();
4413 } else {
4414 //
4415 }
4416 }
4417 "},
4418 indoc! {"
4419 fn foo() {
4420 if true {
4421 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4422 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4423 // egestas tellus id dignissim.ˇ»
4424 do_something();
4425 } else {
4426 //
4427 }
4428 }
4429 "},
4430 language_with_doc_comments.clone(),
4431 &mut cx,
4432 );
4433
4434 assert_rewrap(
4435 indoc! {"
4436 fn foo() {
4437 if true {
4438 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4439 // Praesent semper egestas tellus id dignissim.»
4440 do_something();
4441 } else {
4442 //
4443 }
4444
4445 }
4446 "},
4447 indoc! {"
4448 fn foo() {
4449 if true {
4450 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4451 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4452 // egestas tellus id dignissim.»
4453 do_something();
4454 } else {
4455 //
4456 }
4457
4458 }
4459 "},
4460 language_with_doc_comments.clone(),
4461 &mut cx,
4462 );
4463
4464 #[track_caller]
4465 fn assert_rewrap(
4466 unwrapped_text: &str,
4467 wrapped_text: &str,
4468 language: Arc<Language>,
4469 cx: &mut EditorTestContext,
4470 ) {
4471 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4472 cx.set_state(unwrapped_text);
4473 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4474 cx.assert_editor_state(wrapped_text);
4475 }
4476}
4477
4478#[gpui::test]
4479async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4480 init_test(cx, |_| {});
4481
4482 let mut cx = EditorTestContext::new(cx).await;
4483
4484 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4485 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4486 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4487
4488 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4489 cx.set_state("two ˇfour ˇsix ˇ");
4490 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4491 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4492
4493 // Paste again but with only two cursors. Since the number of cursors doesn't
4494 // match the number of slices in the clipboard, the entire clipboard text
4495 // is pasted at each cursor.
4496 cx.set_state("ˇtwo one✅ four three six five ˇ");
4497 cx.update_editor(|e, cx| {
4498 e.handle_input("( ", cx);
4499 e.paste(&Paste, cx);
4500 e.handle_input(") ", cx);
4501 });
4502 cx.assert_editor_state(
4503 &([
4504 "( one✅ ",
4505 "three ",
4506 "five ) ˇtwo one✅ four three six five ( one✅ ",
4507 "three ",
4508 "five ) ˇ",
4509 ]
4510 .join("\n")),
4511 );
4512
4513 // Cut with three selections, one of which is full-line.
4514 cx.set_state(indoc! {"
4515 1«2ˇ»3
4516 4ˇ567
4517 «8ˇ»9"});
4518 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4519 cx.assert_editor_state(indoc! {"
4520 1ˇ3
4521 ˇ9"});
4522
4523 // Paste with three selections, noticing how the copied selection that was full-line
4524 // gets inserted before the second cursor.
4525 cx.set_state(indoc! {"
4526 1ˇ3
4527 9ˇ
4528 «oˇ»ne"});
4529 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4530 cx.assert_editor_state(indoc! {"
4531 12ˇ3
4532 4567
4533 9ˇ
4534 8ˇne"});
4535
4536 // Copy with a single cursor only, which writes the whole line into the clipboard.
4537 cx.set_state(indoc! {"
4538 The quick brown
4539 fox juˇmps over
4540 the lazy dog"});
4541 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4542 assert_eq!(
4543 cx.read_from_clipboard()
4544 .and_then(|item| item.text().as_deref().map(str::to_string)),
4545 Some("fox jumps over\n".to_string())
4546 );
4547
4548 // Paste with three selections, noticing how the copied full-line selection is inserted
4549 // before the empty selections but replaces the selection that is non-empty.
4550 cx.set_state(indoc! {"
4551 Tˇhe quick brown
4552 «foˇ»x jumps over
4553 tˇhe lazy dog"});
4554 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4555 cx.assert_editor_state(indoc! {"
4556 fox jumps over
4557 Tˇhe quick brown
4558 fox jumps over
4559 ˇx jumps over
4560 fox jumps over
4561 tˇhe lazy dog"});
4562}
4563
4564#[gpui::test]
4565async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4566 init_test(cx, |_| {});
4567
4568 let mut cx = EditorTestContext::new(cx).await;
4569 let language = Arc::new(Language::new(
4570 LanguageConfig::default(),
4571 Some(tree_sitter_rust::LANGUAGE.into()),
4572 ));
4573 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4574
4575 // Cut an indented block, without the leading whitespace.
4576 cx.set_state(indoc! {"
4577 const a: B = (
4578 c(),
4579 «d(
4580 e,
4581 f
4582 )ˇ»
4583 );
4584 "});
4585 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4586 cx.assert_editor_state(indoc! {"
4587 const a: B = (
4588 c(),
4589 ˇ
4590 );
4591 "});
4592
4593 // Paste it at the same position.
4594 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4595 cx.assert_editor_state(indoc! {"
4596 const a: B = (
4597 c(),
4598 d(
4599 e,
4600 f
4601 )ˇ
4602 );
4603 "});
4604
4605 // Paste it at a line with a lower indent level.
4606 cx.set_state(indoc! {"
4607 ˇ
4608 const a: B = (
4609 c(),
4610 );
4611 "});
4612 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4613 cx.assert_editor_state(indoc! {"
4614 d(
4615 e,
4616 f
4617 )ˇ
4618 const a: B = (
4619 c(),
4620 );
4621 "});
4622
4623 // Cut an indented block, with the leading whitespace.
4624 cx.set_state(indoc! {"
4625 const a: B = (
4626 c(),
4627 « d(
4628 e,
4629 f
4630 )
4631 ˇ»);
4632 "});
4633 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4634 cx.assert_editor_state(indoc! {"
4635 const a: B = (
4636 c(),
4637 ˇ);
4638 "});
4639
4640 // Paste it at the same position.
4641 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4642 cx.assert_editor_state(indoc! {"
4643 const a: B = (
4644 c(),
4645 d(
4646 e,
4647 f
4648 )
4649 ˇ);
4650 "});
4651
4652 // Paste it at a line with a higher indent level.
4653 cx.set_state(indoc! {"
4654 const a: B = (
4655 c(),
4656 d(
4657 e,
4658 fˇ
4659 )
4660 );
4661 "});
4662 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4663 cx.assert_editor_state(indoc! {"
4664 const a: B = (
4665 c(),
4666 d(
4667 e,
4668 f d(
4669 e,
4670 f
4671 )
4672 ˇ
4673 )
4674 );
4675 "});
4676}
4677
4678#[gpui::test]
4679fn test_select_all(cx: &mut TestAppContext) {
4680 init_test(cx, |_| {});
4681
4682 let view = cx.add_window(|cx| {
4683 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4684 build_editor(buffer, cx)
4685 });
4686 _ = view.update(cx, |view, cx| {
4687 view.select_all(&SelectAll, cx);
4688 assert_eq!(
4689 view.selections.display_ranges(cx),
4690 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4691 );
4692 });
4693}
4694
4695#[gpui::test]
4696fn test_select_line(cx: &mut TestAppContext) {
4697 init_test(cx, |_| {});
4698
4699 let view = cx.add_window(|cx| {
4700 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4701 build_editor(buffer, cx)
4702 });
4703 _ = view.update(cx, |view, cx| {
4704 view.change_selections(None, cx, |s| {
4705 s.select_display_ranges([
4706 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4707 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4708 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4709 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4710 ])
4711 });
4712 view.select_line(&SelectLine, cx);
4713 assert_eq!(
4714 view.selections.display_ranges(cx),
4715 vec![
4716 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4717 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4718 ]
4719 );
4720 });
4721
4722 _ = view.update(cx, |view, cx| {
4723 view.select_line(&SelectLine, cx);
4724 assert_eq!(
4725 view.selections.display_ranges(cx),
4726 vec![
4727 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4728 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4729 ]
4730 );
4731 });
4732
4733 _ = view.update(cx, |view, cx| {
4734 view.select_line(&SelectLine, cx);
4735 assert_eq!(
4736 view.selections.display_ranges(cx),
4737 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4738 );
4739 });
4740}
4741
4742#[gpui::test]
4743fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4744 init_test(cx, |_| {});
4745
4746 let view = cx.add_window(|cx| {
4747 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4748 build_editor(buffer, cx)
4749 });
4750 _ = view.update(cx, |view, cx| {
4751 view.fold_creases(
4752 vec![
4753 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4754 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4755 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4756 ],
4757 true,
4758 cx,
4759 );
4760 view.change_selections(None, cx, |s| {
4761 s.select_display_ranges([
4762 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4763 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4764 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4765 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4766 ])
4767 });
4768 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4769 });
4770
4771 _ = view.update(cx, |view, cx| {
4772 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4773 assert_eq!(
4774 view.display_text(cx),
4775 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4776 );
4777 assert_eq!(
4778 view.selections.display_ranges(cx),
4779 [
4780 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4781 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4782 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4783 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4784 ]
4785 );
4786 });
4787
4788 _ = view.update(cx, |view, cx| {
4789 view.change_selections(None, cx, |s| {
4790 s.select_display_ranges([
4791 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4792 ])
4793 });
4794 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4795 assert_eq!(
4796 view.display_text(cx),
4797 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4798 );
4799 assert_eq!(
4800 view.selections.display_ranges(cx),
4801 [
4802 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4803 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4804 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4805 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4806 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4807 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4808 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4809 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4810 ]
4811 );
4812 });
4813}
4814
4815#[gpui::test]
4816async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4817 init_test(cx, |_| {});
4818
4819 let mut cx = EditorTestContext::new(cx).await;
4820
4821 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4822 cx.set_state(indoc!(
4823 r#"abc
4824 defˇghi
4825
4826 jk
4827 nlmo
4828 "#
4829 ));
4830
4831 cx.update_editor(|editor, cx| {
4832 editor.add_selection_above(&Default::default(), cx);
4833 });
4834
4835 cx.assert_editor_state(indoc!(
4836 r#"abcˇ
4837 defˇghi
4838
4839 jk
4840 nlmo
4841 "#
4842 ));
4843
4844 cx.update_editor(|editor, cx| {
4845 editor.add_selection_above(&Default::default(), cx);
4846 });
4847
4848 cx.assert_editor_state(indoc!(
4849 r#"abcˇ
4850 defˇghi
4851
4852 jk
4853 nlmo
4854 "#
4855 ));
4856
4857 cx.update_editor(|view, cx| {
4858 view.add_selection_below(&Default::default(), cx);
4859 });
4860
4861 cx.assert_editor_state(indoc!(
4862 r#"abc
4863 defˇghi
4864
4865 jk
4866 nlmo
4867 "#
4868 ));
4869
4870 cx.update_editor(|view, cx| {
4871 view.undo_selection(&Default::default(), cx);
4872 });
4873
4874 cx.assert_editor_state(indoc!(
4875 r#"abcˇ
4876 defˇghi
4877
4878 jk
4879 nlmo
4880 "#
4881 ));
4882
4883 cx.update_editor(|view, cx| {
4884 view.redo_selection(&Default::default(), cx);
4885 });
4886
4887 cx.assert_editor_state(indoc!(
4888 r#"abc
4889 defˇghi
4890
4891 jk
4892 nlmo
4893 "#
4894 ));
4895
4896 cx.update_editor(|view, cx| {
4897 view.add_selection_below(&Default::default(), cx);
4898 });
4899
4900 cx.assert_editor_state(indoc!(
4901 r#"abc
4902 defˇghi
4903
4904 jk
4905 nlmˇo
4906 "#
4907 ));
4908
4909 cx.update_editor(|view, cx| {
4910 view.add_selection_below(&Default::default(), cx);
4911 });
4912
4913 cx.assert_editor_state(indoc!(
4914 r#"abc
4915 defˇghi
4916
4917 jk
4918 nlmˇo
4919 "#
4920 ));
4921
4922 // change selections
4923 cx.set_state(indoc!(
4924 r#"abc
4925 def«ˇg»hi
4926
4927 jk
4928 nlmo
4929 "#
4930 ));
4931
4932 cx.update_editor(|view, cx| {
4933 view.add_selection_below(&Default::default(), cx);
4934 });
4935
4936 cx.assert_editor_state(indoc!(
4937 r#"abc
4938 def«ˇg»hi
4939
4940 jk
4941 nlm«ˇo»
4942 "#
4943 ));
4944
4945 cx.update_editor(|view, cx| {
4946 view.add_selection_below(&Default::default(), cx);
4947 });
4948
4949 cx.assert_editor_state(indoc!(
4950 r#"abc
4951 def«ˇg»hi
4952
4953 jk
4954 nlm«ˇo»
4955 "#
4956 ));
4957
4958 cx.update_editor(|view, cx| {
4959 view.add_selection_above(&Default::default(), cx);
4960 });
4961
4962 cx.assert_editor_state(indoc!(
4963 r#"abc
4964 def«ˇg»hi
4965
4966 jk
4967 nlmo
4968 "#
4969 ));
4970
4971 cx.update_editor(|view, cx| {
4972 view.add_selection_above(&Default::default(), cx);
4973 });
4974
4975 cx.assert_editor_state(indoc!(
4976 r#"abc
4977 def«ˇg»hi
4978
4979 jk
4980 nlmo
4981 "#
4982 ));
4983
4984 // Change selections again
4985 cx.set_state(indoc!(
4986 r#"a«bc
4987 defgˇ»hi
4988
4989 jk
4990 nlmo
4991 "#
4992 ));
4993
4994 cx.update_editor(|view, cx| {
4995 view.add_selection_below(&Default::default(), cx);
4996 });
4997
4998 cx.assert_editor_state(indoc!(
4999 r#"a«bcˇ»
5000 d«efgˇ»hi
5001
5002 j«kˇ»
5003 nlmo
5004 "#
5005 ));
5006
5007 cx.update_editor(|view, cx| {
5008 view.add_selection_below(&Default::default(), cx);
5009 });
5010 cx.assert_editor_state(indoc!(
5011 r#"a«bcˇ»
5012 d«efgˇ»hi
5013
5014 j«kˇ»
5015 n«lmoˇ»
5016 "#
5017 ));
5018 cx.update_editor(|view, cx| {
5019 view.add_selection_above(&Default::default(), cx);
5020 });
5021
5022 cx.assert_editor_state(indoc!(
5023 r#"a«bcˇ»
5024 d«efgˇ»hi
5025
5026 j«kˇ»
5027 nlmo
5028 "#
5029 ));
5030
5031 // Change selections again
5032 cx.set_state(indoc!(
5033 r#"abc
5034 d«ˇefghi
5035
5036 jk
5037 nlm»o
5038 "#
5039 ));
5040
5041 cx.update_editor(|view, cx| {
5042 view.add_selection_above(&Default::default(), cx);
5043 });
5044
5045 cx.assert_editor_state(indoc!(
5046 r#"a«ˇbc»
5047 d«ˇef»ghi
5048
5049 j«ˇk»
5050 n«ˇlm»o
5051 "#
5052 ));
5053
5054 cx.update_editor(|view, cx| {
5055 view.add_selection_below(&Default::default(), cx);
5056 });
5057
5058 cx.assert_editor_state(indoc!(
5059 r#"abc
5060 d«ˇef»ghi
5061
5062 j«ˇk»
5063 n«ˇlm»o
5064 "#
5065 ));
5066}
5067
5068#[gpui::test]
5069async fn test_select_next(cx: &mut gpui::TestAppContext) {
5070 init_test(cx, |_| {});
5071
5072 let mut cx = EditorTestContext::new(cx).await;
5073 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5074
5075 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5076 .unwrap();
5077 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5078
5079 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5080 .unwrap();
5081 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5082
5083 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5084 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5085
5086 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5087 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5088
5089 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5090 .unwrap();
5091 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5092
5093 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5094 .unwrap();
5095 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5096}
5097
5098#[gpui::test]
5099async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5100 init_test(cx, |_| {});
5101
5102 let mut cx = EditorTestContext::new(cx).await;
5103 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5104
5105 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5106 .unwrap();
5107 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5108}
5109
5110#[gpui::test]
5111async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5112 init_test(cx, |_| {});
5113
5114 let mut cx = EditorTestContext::new(cx).await;
5115 cx.set_state(
5116 r#"let foo = 2;
5117lˇet foo = 2;
5118let fooˇ = 2;
5119let foo = 2;
5120let foo = ˇ2;"#,
5121 );
5122
5123 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5124 .unwrap();
5125 cx.assert_editor_state(
5126 r#"let foo = 2;
5127«letˇ» foo = 2;
5128let «fooˇ» = 2;
5129let foo = 2;
5130let foo = «2ˇ»;"#,
5131 );
5132
5133 // noop for multiple selections with different contents
5134 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5135 .unwrap();
5136 cx.assert_editor_state(
5137 r#"let foo = 2;
5138«letˇ» foo = 2;
5139let «fooˇ» = 2;
5140let foo = 2;
5141let foo = «2ˇ»;"#,
5142 );
5143}
5144
5145#[gpui::test]
5146async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5147 init_test(cx, |_| {});
5148
5149 let mut cx =
5150 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5151
5152 cx.assert_editor_state(indoc! {"
5153 ˇbbb
5154 ccc
5155
5156 bbb
5157 ccc
5158 "});
5159 cx.dispatch_action(SelectPrevious::default());
5160 cx.assert_editor_state(indoc! {"
5161 «bbbˇ»
5162 ccc
5163
5164 bbb
5165 ccc
5166 "});
5167 cx.dispatch_action(SelectPrevious::default());
5168 cx.assert_editor_state(indoc! {"
5169 «bbbˇ»
5170 ccc
5171
5172 «bbbˇ»
5173 ccc
5174 "});
5175}
5176
5177#[gpui::test]
5178async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5179 init_test(cx, |_| {});
5180
5181 let mut cx = EditorTestContext::new(cx).await;
5182 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5183
5184 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5185 .unwrap();
5186 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5187
5188 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5189 .unwrap();
5190 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5191
5192 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5193 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5194
5195 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5196 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5197
5198 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5199 .unwrap();
5200 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5201
5202 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5203 .unwrap();
5204 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5205
5206 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5207 .unwrap();
5208 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5209}
5210
5211#[gpui::test]
5212async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5213 init_test(cx, |_| {});
5214
5215 let mut cx = EditorTestContext::new(cx).await;
5216 cx.set_state(
5217 r#"let foo = 2;
5218lˇet foo = 2;
5219let fooˇ = 2;
5220let foo = 2;
5221let foo = ˇ2;"#,
5222 );
5223
5224 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5225 .unwrap();
5226 cx.assert_editor_state(
5227 r#"let foo = 2;
5228«letˇ» foo = 2;
5229let «fooˇ» = 2;
5230let foo = 2;
5231let foo = «2ˇ»;"#,
5232 );
5233
5234 // noop for multiple selections with different contents
5235 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5236 .unwrap();
5237 cx.assert_editor_state(
5238 r#"let foo = 2;
5239«letˇ» foo = 2;
5240let «fooˇ» = 2;
5241let foo = 2;
5242let foo = «2ˇ»;"#,
5243 );
5244}
5245
5246#[gpui::test]
5247async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5248 init_test(cx, |_| {});
5249
5250 let mut cx = EditorTestContext::new(cx).await;
5251 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5252
5253 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5254 .unwrap();
5255 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5256
5257 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5258 .unwrap();
5259 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5260
5261 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5262 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5263
5264 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5265 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5266
5267 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5268 .unwrap();
5269 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5270
5271 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5272 .unwrap();
5273 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5274}
5275
5276#[gpui::test]
5277async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5278 init_test(cx, |_| {});
5279
5280 let language = Arc::new(Language::new(
5281 LanguageConfig::default(),
5282 Some(tree_sitter_rust::LANGUAGE.into()),
5283 ));
5284
5285 let text = r#"
5286 use mod1::mod2::{mod3, mod4};
5287
5288 fn fn_1(param1: bool, param2: &str) {
5289 let var1 = "text";
5290 }
5291 "#
5292 .unindent();
5293
5294 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5295 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5296 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5297
5298 editor
5299 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5300 .await;
5301
5302 editor.update(cx, |view, cx| {
5303 view.change_selections(None, cx, |s| {
5304 s.select_display_ranges([
5305 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5306 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5307 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5308 ]);
5309 });
5310 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5311 });
5312 editor.update(cx, |editor, cx| {
5313 assert_text_with_selections(
5314 editor,
5315 indoc! {r#"
5316 use mod1::mod2::{mod3, «mod4ˇ»};
5317
5318 fn fn_1«ˇ(param1: bool, param2: &str)» {
5319 let var1 = "«textˇ»";
5320 }
5321 "#},
5322 cx,
5323 );
5324 });
5325
5326 editor.update(cx, |view, cx| {
5327 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5328 });
5329 editor.update(cx, |editor, cx| {
5330 assert_text_with_selections(
5331 editor,
5332 indoc! {r#"
5333 use mod1::mod2::«{mod3, mod4}ˇ»;
5334
5335 «ˇfn fn_1(param1: bool, param2: &str) {
5336 let var1 = "text";
5337 }»
5338 "#},
5339 cx,
5340 );
5341 });
5342
5343 editor.update(cx, |view, cx| {
5344 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5345 });
5346 assert_eq!(
5347 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5348 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5349 );
5350
5351 // Trying to expand the selected syntax node one more time has no effect.
5352 editor.update(cx, |view, cx| {
5353 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5354 });
5355 assert_eq!(
5356 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5357 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5358 );
5359
5360 editor.update(cx, |view, cx| {
5361 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5362 });
5363 editor.update(cx, |editor, cx| {
5364 assert_text_with_selections(
5365 editor,
5366 indoc! {r#"
5367 use mod1::mod2::«{mod3, mod4}ˇ»;
5368
5369 «ˇfn fn_1(param1: bool, param2: &str) {
5370 let var1 = "text";
5371 }»
5372 "#},
5373 cx,
5374 );
5375 });
5376
5377 editor.update(cx, |view, cx| {
5378 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5379 });
5380 editor.update(cx, |editor, cx| {
5381 assert_text_with_selections(
5382 editor,
5383 indoc! {r#"
5384 use mod1::mod2::{mod3, «mod4ˇ»};
5385
5386 fn fn_1«ˇ(param1: bool, param2: &str)» {
5387 let var1 = "«textˇ»";
5388 }
5389 "#},
5390 cx,
5391 );
5392 });
5393
5394 editor.update(cx, |view, cx| {
5395 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5396 });
5397 editor.update(cx, |editor, cx| {
5398 assert_text_with_selections(
5399 editor,
5400 indoc! {r#"
5401 use mod1::mod2::{mod3, mo«ˇ»d4};
5402
5403 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5404 let var1 = "te«ˇ»xt";
5405 }
5406 "#},
5407 cx,
5408 );
5409 });
5410
5411 // Trying to shrink the selected syntax node one more time has no effect.
5412 editor.update(cx, |view, cx| {
5413 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5414 });
5415 editor.update(cx, |editor, cx| {
5416 assert_text_with_selections(
5417 editor,
5418 indoc! {r#"
5419 use mod1::mod2::{mod3, mo«ˇ»d4};
5420
5421 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5422 let var1 = "te«ˇ»xt";
5423 }
5424 "#},
5425 cx,
5426 );
5427 });
5428
5429 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5430 // a fold.
5431 editor.update(cx, |view, cx| {
5432 view.fold_creases(
5433 vec![
5434 Crease::simple(
5435 Point::new(0, 21)..Point::new(0, 24),
5436 FoldPlaceholder::test(),
5437 ),
5438 Crease::simple(
5439 Point::new(3, 20)..Point::new(3, 22),
5440 FoldPlaceholder::test(),
5441 ),
5442 ],
5443 true,
5444 cx,
5445 );
5446 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5447 });
5448 editor.update(cx, |editor, cx| {
5449 assert_text_with_selections(
5450 editor,
5451 indoc! {r#"
5452 use mod1::mod2::«{mod3, mod4}ˇ»;
5453
5454 fn fn_1«ˇ(param1: bool, param2: &str)» {
5455 «let var1 = "text";ˇ»
5456 }
5457 "#},
5458 cx,
5459 );
5460 });
5461}
5462
5463#[gpui::test]
5464async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5465 init_test(cx, |_| {});
5466
5467 let language = Arc::new(
5468 Language::new(
5469 LanguageConfig {
5470 brackets: BracketPairConfig {
5471 pairs: vec![
5472 BracketPair {
5473 start: "{".to_string(),
5474 end: "}".to_string(),
5475 close: false,
5476 surround: false,
5477 newline: true,
5478 },
5479 BracketPair {
5480 start: "(".to_string(),
5481 end: ")".to_string(),
5482 close: false,
5483 surround: false,
5484 newline: true,
5485 },
5486 ],
5487 ..Default::default()
5488 },
5489 ..Default::default()
5490 },
5491 Some(tree_sitter_rust::LANGUAGE.into()),
5492 )
5493 .with_indents_query(
5494 r#"
5495 (_ "(" ")" @end) @indent
5496 (_ "{" "}" @end) @indent
5497 "#,
5498 )
5499 .unwrap(),
5500 );
5501
5502 let text = "fn a() {}";
5503
5504 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5505 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5506 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5507 editor
5508 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5509 .await;
5510
5511 editor.update(cx, |editor, cx| {
5512 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5513 editor.newline(&Newline, cx);
5514 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5515 assert_eq!(
5516 editor.selections.ranges(cx),
5517 &[
5518 Point::new(1, 4)..Point::new(1, 4),
5519 Point::new(3, 4)..Point::new(3, 4),
5520 Point::new(5, 0)..Point::new(5, 0)
5521 ]
5522 );
5523 });
5524}
5525
5526#[gpui::test]
5527async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5528 init_test(cx, |_| {});
5529
5530 {
5531 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5532 cx.set_state(indoc! {"
5533 impl A {
5534
5535 fn b() {}
5536
5537 «fn c() {
5538
5539 }ˇ»
5540 }
5541 "});
5542
5543 cx.update_editor(|editor, cx| {
5544 editor.autoindent(&Default::default(), cx);
5545 });
5546
5547 cx.assert_editor_state(indoc! {"
5548 impl A {
5549
5550 fn b() {}
5551
5552 «fn c() {
5553
5554 }ˇ»
5555 }
5556 "});
5557 }
5558
5559 {
5560 let mut cx = EditorTestContext::new_multibuffer(
5561 cx,
5562 [indoc! { "
5563 impl A {
5564 «
5565 // a
5566 fn b(){}
5567 »
5568 «
5569 }
5570 fn c(){}
5571 »
5572 "}],
5573 );
5574
5575 let buffer = cx.update_editor(|editor, cx| {
5576 let buffer = editor.buffer().update(cx, |buffer, _| {
5577 buffer.all_buffers().iter().next().unwrap().clone()
5578 });
5579 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5580 buffer
5581 });
5582
5583 cx.run_until_parked();
5584 cx.update_editor(|editor, cx| {
5585 editor.select_all(&Default::default(), cx);
5586 editor.autoindent(&Default::default(), cx)
5587 });
5588 cx.run_until_parked();
5589
5590 cx.update(|cx| {
5591 pretty_assertions::assert_eq!(
5592 buffer.read(cx).text(),
5593 indoc! { "
5594 impl A {
5595
5596 // a
5597 fn b(){}
5598
5599
5600 }
5601 fn c(){}
5602
5603 " }
5604 )
5605 });
5606 }
5607}
5608
5609#[gpui::test]
5610async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5611 init_test(cx, |_| {});
5612
5613 let mut cx = EditorTestContext::new(cx).await;
5614
5615 let language = Arc::new(Language::new(
5616 LanguageConfig {
5617 brackets: BracketPairConfig {
5618 pairs: vec![
5619 BracketPair {
5620 start: "{".to_string(),
5621 end: "}".to_string(),
5622 close: true,
5623 surround: true,
5624 newline: true,
5625 },
5626 BracketPair {
5627 start: "(".to_string(),
5628 end: ")".to_string(),
5629 close: true,
5630 surround: true,
5631 newline: true,
5632 },
5633 BracketPair {
5634 start: "/*".to_string(),
5635 end: " */".to_string(),
5636 close: true,
5637 surround: true,
5638 newline: true,
5639 },
5640 BracketPair {
5641 start: "[".to_string(),
5642 end: "]".to_string(),
5643 close: false,
5644 surround: false,
5645 newline: true,
5646 },
5647 BracketPair {
5648 start: "\"".to_string(),
5649 end: "\"".to_string(),
5650 close: true,
5651 surround: true,
5652 newline: false,
5653 },
5654 BracketPair {
5655 start: "<".to_string(),
5656 end: ">".to_string(),
5657 close: false,
5658 surround: true,
5659 newline: true,
5660 },
5661 ],
5662 ..Default::default()
5663 },
5664 autoclose_before: "})]".to_string(),
5665 ..Default::default()
5666 },
5667 Some(tree_sitter_rust::LANGUAGE.into()),
5668 ));
5669
5670 cx.language_registry().add(language.clone());
5671 cx.update_buffer(|buffer, cx| {
5672 buffer.set_language(Some(language), cx);
5673 });
5674
5675 cx.set_state(
5676 &r#"
5677 🏀ˇ
5678 εˇ
5679 ❤️ˇ
5680 "#
5681 .unindent(),
5682 );
5683
5684 // autoclose multiple nested brackets at multiple cursors
5685 cx.update_editor(|view, cx| {
5686 view.handle_input("{", cx);
5687 view.handle_input("{", cx);
5688 view.handle_input("{", cx);
5689 });
5690 cx.assert_editor_state(
5691 &"
5692 🏀{{{ˇ}}}
5693 ε{{{ˇ}}}
5694 ❤️{{{ˇ}}}
5695 "
5696 .unindent(),
5697 );
5698
5699 // insert a different closing bracket
5700 cx.update_editor(|view, cx| {
5701 view.handle_input(")", cx);
5702 });
5703 cx.assert_editor_state(
5704 &"
5705 🏀{{{)ˇ}}}
5706 ε{{{)ˇ}}}
5707 ❤️{{{)ˇ}}}
5708 "
5709 .unindent(),
5710 );
5711
5712 // skip over the auto-closed brackets when typing a closing bracket
5713 cx.update_editor(|view, cx| {
5714 view.move_right(&MoveRight, cx);
5715 view.handle_input("}", cx);
5716 view.handle_input("}", cx);
5717 view.handle_input("}", cx);
5718 });
5719 cx.assert_editor_state(
5720 &"
5721 🏀{{{)}}}}ˇ
5722 ε{{{)}}}}ˇ
5723 ❤️{{{)}}}}ˇ
5724 "
5725 .unindent(),
5726 );
5727
5728 // autoclose multi-character pairs
5729 cx.set_state(
5730 &"
5731 ˇ
5732 ˇ
5733 "
5734 .unindent(),
5735 );
5736 cx.update_editor(|view, cx| {
5737 view.handle_input("/", cx);
5738 view.handle_input("*", cx);
5739 });
5740 cx.assert_editor_state(
5741 &"
5742 /*ˇ */
5743 /*ˇ */
5744 "
5745 .unindent(),
5746 );
5747
5748 // one cursor autocloses a multi-character pair, one cursor
5749 // does not autoclose.
5750 cx.set_state(
5751 &"
5752 /ˇ
5753 ˇ
5754 "
5755 .unindent(),
5756 );
5757 cx.update_editor(|view, cx| view.handle_input("*", cx));
5758 cx.assert_editor_state(
5759 &"
5760 /*ˇ */
5761 *ˇ
5762 "
5763 .unindent(),
5764 );
5765
5766 // Don't autoclose if the next character isn't whitespace and isn't
5767 // listed in the language's "autoclose_before" section.
5768 cx.set_state("ˇa b");
5769 cx.update_editor(|view, cx| view.handle_input("{", cx));
5770 cx.assert_editor_state("{ˇa b");
5771
5772 // Don't autoclose if `close` is false for the bracket pair
5773 cx.set_state("ˇ");
5774 cx.update_editor(|view, cx| view.handle_input("[", cx));
5775 cx.assert_editor_state("[ˇ");
5776
5777 // Surround with brackets if text is selected
5778 cx.set_state("«aˇ» b");
5779 cx.update_editor(|view, cx| view.handle_input("{", cx));
5780 cx.assert_editor_state("{«aˇ»} b");
5781
5782 // Autclose pair where the start and end characters are the same
5783 cx.set_state("aˇ");
5784 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5785 cx.assert_editor_state("a\"ˇ\"");
5786 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5787 cx.assert_editor_state("a\"\"ˇ");
5788
5789 // Don't autoclose pair if autoclose is disabled
5790 cx.set_state("ˇ");
5791 cx.update_editor(|view, cx| view.handle_input("<", cx));
5792 cx.assert_editor_state("<ˇ");
5793
5794 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5795 cx.set_state("«aˇ» b");
5796 cx.update_editor(|view, cx| view.handle_input("<", cx));
5797 cx.assert_editor_state("<«aˇ»> b");
5798}
5799
5800#[gpui::test]
5801async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5802 init_test(cx, |settings| {
5803 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5804 });
5805
5806 let mut cx = EditorTestContext::new(cx).await;
5807
5808 let language = Arc::new(Language::new(
5809 LanguageConfig {
5810 brackets: BracketPairConfig {
5811 pairs: vec![
5812 BracketPair {
5813 start: "{".to_string(),
5814 end: "}".to_string(),
5815 close: true,
5816 surround: true,
5817 newline: true,
5818 },
5819 BracketPair {
5820 start: "(".to_string(),
5821 end: ")".to_string(),
5822 close: true,
5823 surround: true,
5824 newline: true,
5825 },
5826 BracketPair {
5827 start: "[".to_string(),
5828 end: "]".to_string(),
5829 close: false,
5830 surround: false,
5831 newline: true,
5832 },
5833 ],
5834 ..Default::default()
5835 },
5836 autoclose_before: "})]".to_string(),
5837 ..Default::default()
5838 },
5839 Some(tree_sitter_rust::LANGUAGE.into()),
5840 ));
5841
5842 cx.language_registry().add(language.clone());
5843 cx.update_buffer(|buffer, cx| {
5844 buffer.set_language(Some(language), cx);
5845 });
5846
5847 cx.set_state(
5848 &"
5849 ˇ
5850 ˇ
5851 ˇ
5852 "
5853 .unindent(),
5854 );
5855
5856 // ensure only matching closing brackets are skipped over
5857 cx.update_editor(|view, cx| {
5858 view.handle_input("}", cx);
5859 view.move_left(&MoveLeft, cx);
5860 view.handle_input(")", cx);
5861 view.move_left(&MoveLeft, cx);
5862 });
5863 cx.assert_editor_state(
5864 &"
5865 ˇ)}
5866 ˇ)}
5867 ˇ)}
5868 "
5869 .unindent(),
5870 );
5871
5872 // skip-over closing brackets at multiple cursors
5873 cx.update_editor(|view, cx| {
5874 view.handle_input(")", cx);
5875 view.handle_input("}", cx);
5876 });
5877 cx.assert_editor_state(
5878 &"
5879 )}ˇ
5880 )}ˇ
5881 )}ˇ
5882 "
5883 .unindent(),
5884 );
5885
5886 // ignore non-close brackets
5887 cx.update_editor(|view, cx| {
5888 view.handle_input("]", cx);
5889 view.move_left(&MoveLeft, cx);
5890 view.handle_input("]", cx);
5891 });
5892 cx.assert_editor_state(
5893 &"
5894 )}]ˇ]
5895 )}]ˇ]
5896 )}]ˇ]
5897 "
5898 .unindent(),
5899 );
5900}
5901
5902#[gpui::test]
5903async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5904 init_test(cx, |_| {});
5905
5906 let mut cx = EditorTestContext::new(cx).await;
5907
5908 let html_language = Arc::new(
5909 Language::new(
5910 LanguageConfig {
5911 name: "HTML".into(),
5912 brackets: BracketPairConfig {
5913 pairs: vec![
5914 BracketPair {
5915 start: "<".into(),
5916 end: ">".into(),
5917 close: true,
5918 ..Default::default()
5919 },
5920 BracketPair {
5921 start: "{".into(),
5922 end: "}".into(),
5923 close: true,
5924 ..Default::default()
5925 },
5926 BracketPair {
5927 start: "(".into(),
5928 end: ")".into(),
5929 close: true,
5930 ..Default::default()
5931 },
5932 ],
5933 ..Default::default()
5934 },
5935 autoclose_before: "})]>".into(),
5936 ..Default::default()
5937 },
5938 Some(tree_sitter_html::language()),
5939 )
5940 .with_injection_query(
5941 r#"
5942 (script_element
5943 (raw_text) @content
5944 (#set! "language" "javascript"))
5945 "#,
5946 )
5947 .unwrap(),
5948 );
5949
5950 let javascript_language = Arc::new(Language::new(
5951 LanguageConfig {
5952 name: "JavaScript".into(),
5953 brackets: BracketPairConfig {
5954 pairs: vec![
5955 BracketPair {
5956 start: "/*".into(),
5957 end: " */".into(),
5958 close: true,
5959 ..Default::default()
5960 },
5961 BracketPair {
5962 start: "{".into(),
5963 end: "}".into(),
5964 close: true,
5965 ..Default::default()
5966 },
5967 BracketPair {
5968 start: "(".into(),
5969 end: ")".into(),
5970 close: true,
5971 ..Default::default()
5972 },
5973 ],
5974 ..Default::default()
5975 },
5976 autoclose_before: "})]>".into(),
5977 ..Default::default()
5978 },
5979 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5980 ));
5981
5982 cx.language_registry().add(html_language.clone());
5983 cx.language_registry().add(javascript_language.clone());
5984
5985 cx.update_buffer(|buffer, cx| {
5986 buffer.set_language(Some(html_language), cx);
5987 });
5988
5989 cx.set_state(
5990 &r#"
5991 <body>ˇ
5992 <script>
5993 var x = 1;ˇ
5994 </script>
5995 </body>ˇ
5996 "#
5997 .unindent(),
5998 );
5999
6000 // Precondition: different languages are active at different locations.
6001 cx.update_editor(|editor, cx| {
6002 let snapshot = editor.snapshot(cx);
6003 let cursors = editor.selections.ranges::<usize>(cx);
6004 let languages = cursors
6005 .iter()
6006 .map(|c| snapshot.language_at(c.start).unwrap().name())
6007 .collect::<Vec<_>>();
6008 assert_eq!(
6009 languages,
6010 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6011 );
6012 });
6013
6014 // Angle brackets autoclose in HTML, but not JavaScript.
6015 cx.update_editor(|editor, cx| {
6016 editor.handle_input("<", cx);
6017 editor.handle_input("a", cx);
6018 });
6019 cx.assert_editor_state(
6020 &r#"
6021 <body><aˇ>
6022 <script>
6023 var x = 1;<aˇ
6024 </script>
6025 </body><aˇ>
6026 "#
6027 .unindent(),
6028 );
6029
6030 // Curly braces and parens autoclose in both HTML and JavaScript.
6031 cx.update_editor(|editor, cx| {
6032 editor.handle_input(" b=", cx);
6033 editor.handle_input("{", cx);
6034 editor.handle_input("c", cx);
6035 editor.handle_input("(", cx);
6036 });
6037 cx.assert_editor_state(
6038 &r#"
6039 <body><a b={c(ˇ)}>
6040 <script>
6041 var x = 1;<a b={c(ˇ)}
6042 </script>
6043 </body><a b={c(ˇ)}>
6044 "#
6045 .unindent(),
6046 );
6047
6048 // Brackets that were already autoclosed are skipped.
6049 cx.update_editor(|editor, cx| {
6050 editor.handle_input(")", cx);
6051 editor.handle_input("d", cx);
6052 editor.handle_input("}", cx);
6053 });
6054 cx.assert_editor_state(
6055 &r#"
6056 <body><a b={c()d}ˇ>
6057 <script>
6058 var x = 1;<a b={c()d}ˇ
6059 </script>
6060 </body><a b={c()d}ˇ>
6061 "#
6062 .unindent(),
6063 );
6064 cx.update_editor(|editor, cx| {
6065 editor.handle_input(">", cx);
6066 });
6067 cx.assert_editor_state(
6068 &r#"
6069 <body><a b={c()d}>ˇ
6070 <script>
6071 var x = 1;<a b={c()d}>ˇ
6072 </script>
6073 </body><a b={c()d}>ˇ
6074 "#
6075 .unindent(),
6076 );
6077
6078 // Reset
6079 cx.set_state(
6080 &r#"
6081 <body>ˇ
6082 <script>
6083 var x = 1;ˇ
6084 </script>
6085 </body>ˇ
6086 "#
6087 .unindent(),
6088 );
6089
6090 cx.update_editor(|editor, cx| {
6091 editor.handle_input("<", cx);
6092 });
6093 cx.assert_editor_state(
6094 &r#"
6095 <body><ˇ>
6096 <script>
6097 var x = 1;<ˇ
6098 </script>
6099 </body><ˇ>
6100 "#
6101 .unindent(),
6102 );
6103
6104 // When backspacing, the closing angle brackets are removed.
6105 cx.update_editor(|editor, cx| {
6106 editor.backspace(&Backspace, cx);
6107 });
6108 cx.assert_editor_state(
6109 &r#"
6110 <body>ˇ
6111 <script>
6112 var x = 1;ˇ
6113 </script>
6114 </body>ˇ
6115 "#
6116 .unindent(),
6117 );
6118
6119 // Block comments autoclose in JavaScript, but not HTML.
6120 cx.update_editor(|editor, cx| {
6121 editor.handle_input("/", cx);
6122 editor.handle_input("*", cx);
6123 });
6124 cx.assert_editor_state(
6125 &r#"
6126 <body>/*ˇ
6127 <script>
6128 var x = 1;/*ˇ */
6129 </script>
6130 </body>/*ˇ
6131 "#
6132 .unindent(),
6133 );
6134}
6135
6136#[gpui::test]
6137async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6138 init_test(cx, |_| {});
6139
6140 let mut cx = EditorTestContext::new(cx).await;
6141
6142 let rust_language = Arc::new(
6143 Language::new(
6144 LanguageConfig {
6145 name: "Rust".into(),
6146 brackets: serde_json::from_value(json!([
6147 { "start": "{", "end": "}", "close": true, "newline": true },
6148 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6149 ]))
6150 .unwrap(),
6151 autoclose_before: "})]>".into(),
6152 ..Default::default()
6153 },
6154 Some(tree_sitter_rust::LANGUAGE.into()),
6155 )
6156 .with_override_query("(string_literal) @string")
6157 .unwrap(),
6158 );
6159
6160 cx.language_registry().add(rust_language.clone());
6161 cx.update_buffer(|buffer, cx| {
6162 buffer.set_language(Some(rust_language), cx);
6163 });
6164
6165 cx.set_state(
6166 &r#"
6167 let x = ˇ
6168 "#
6169 .unindent(),
6170 );
6171
6172 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6173 cx.update_editor(|editor, cx| {
6174 editor.handle_input("\"", cx);
6175 });
6176 cx.assert_editor_state(
6177 &r#"
6178 let x = "ˇ"
6179 "#
6180 .unindent(),
6181 );
6182
6183 // Inserting another quotation mark. The cursor moves across the existing
6184 // automatically-inserted quotation mark.
6185 cx.update_editor(|editor, cx| {
6186 editor.handle_input("\"", cx);
6187 });
6188 cx.assert_editor_state(
6189 &r#"
6190 let x = ""ˇ
6191 "#
6192 .unindent(),
6193 );
6194
6195 // Reset
6196 cx.set_state(
6197 &r#"
6198 let x = ˇ
6199 "#
6200 .unindent(),
6201 );
6202
6203 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6204 cx.update_editor(|editor, cx| {
6205 editor.handle_input("\"", cx);
6206 editor.handle_input(" ", cx);
6207 editor.move_left(&Default::default(), cx);
6208 editor.handle_input("\\", cx);
6209 editor.handle_input("\"", cx);
6210 });
6211 cx.assert_editor_state(
6212 &r#"
6213 let x = "\"ˇ "
6214 "#
6215 .unindent(),
6216 );
6217
6218 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6219 // mark. Nothing is inserted.
6220 cx.update_editor(|editor, cx| {
6221 editor.move_right(&Default::default(), cx);
6222 editor.handle_input("\"", cx);
6223 });
6224 cx.assert_editor_state(
6225 &r#"
6226 let x = "\" "ˇ
6227 "#
6228 .unindent(),
6229 );
6230}
6231
6232#[gpui::test]
6233async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6234 init_test(cx, |_| {});
6235
6236 let language = Arc::new(Language::new(
6237 LanguageConfig {
6238 brackets: BracketPairConfig {
6239 pairs: vec![
6240 BracketPair {
6241 start: "{".to_string(),
6242 end: "}".to_string(),
6243 close: true,
6244 surround: true,
6245 newline: true,
6246 },
6247 BracketPair {
6248 start: "/* ".to_string(),
6249 end: "*/".to_string(),
6250 close: true,
6251 surround: true,
6252 ..Default::default()
6253 },
6254 ],
6255 ..Default::default()
6256 },
6257 ..Default::default()
6258 },
6259 Some(tree_sitter_rust::LANGUAGE.into()),
6260 ));
6261
6262 let text = r#"
6263 a
6264 b
6265 c
6266 "#
6267 .unindent();
6268
6269 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6270 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6271 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6272 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6273 .await;
6274
6275 view.update(cx, |view, cx| {
6276 view.change_selections(None, cx, |s| {
6277 s.select_display_ranges([
6278 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6279 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6280 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6281 ])
6282 });
6283
6284 view.handle_input("{", cx);
6285 view.handle_input("{", cx);
6286 view.handle_input("{", cx);
6287 assert_eq!(
6288 view.text(cx),
6289 "
6290 {{{a}}}
6291 {{{b}}}
6292 {{{c}}}
6293 "
6294 .unindent()
6295 );
6296 assert_eq!(
6297 view.selections.display_ranges(cx),
6298 [
6299 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6300 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6301 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6302 ]
6303 );
6304
6305 view.undo(&Undo, cx);
6306 view.undo(&Undo, cx);
6307 view.undo(&Undo, cx);
6308 assert_eq!(
6309 view.text(cx),
6310 "
6311 a
6312 b
6313 c
6314 "
6315 .unindent()
6316 );
6317 assert_eq!(
6318 view.selections.display_ranges(cx),
6319 [
6320 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6321 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6322 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6323 ]
6324 );
6325
6326 // Ensure inserting the first character of a multi-byte bracket pair
6327 // doesn't surround the selections with the bracket.
6328 view.handle_input("/", cx);
6329 assert_eq!(
6330 view.text(cx),
6331 "
6332 /
6333 /
6334 /
6335 "
6336 .unindent()
6337 );
6338 assert_eq!(
6339 view.selections.display_ranges(cx),
6340 [
6341 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6342 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6343 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6344 ]
6345 );
6346
6347 view.undo(&Undo, cx);
6348 assert_eq!(
6349 view.text(cx),
6350 "
6351 a
6352 b
6353 c
6354 "
6355 .unindent()
6356 );
6357 assert_eq!(
6358 view.selections.display_ranges(cx),
6359 [
6360 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6361 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6362 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6363 ]
6364 );
6365
6366 // Ensure inserting the last character of a multi-byte bracket pair
6367 // doesn't surround the selections with the bracket.
6368 view.handle_input("*", cx);
6369 assert_eq!(
6370 view.text(cx),
6371 "
6372 *
6373 *
6374 *
6375 "
6376 .unindent()
6377 );
6378 assert_eq!(
6379 view.selections.display_ranges(cx),
6380 [
6381 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6382 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6383 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6384 ]
6385 );
6386 });
6387}
6388
6389#[gpui::test]
6390async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6391 init_test(cx, |_| {});
6392
6393 let language = Arc::new(Language::new(
6394 LanguageConfig {
6395 brackets: BracketPairConfig {
6396 pairs: vec![BracketPair {
6397 start: "{".to_string(),
6398 end: "}".to_string(),
6399 close: true,
6400 surround: true,
6401 newline: true,
6402 }],
6403 ..Default::default()
6404 },
6405 autoclose_before: "}".to_string(),
6406 ..Default::default()
6407 },
6408 Some(tree_sitter_rust::LANGUAGE.into()),
6409 ));
6410
6411 let text = r#"
6412 a
6413 b
6414 c
6415 "#
6416 .unindent();
6417
6418 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6419 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6420 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6421 editor
6422 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6423 .await;
6424
6425 editor.update(cx, |editor, cx| {
6426 editor.change_selections(None, cx, |s| {
6427 s.select_ranges([
6428 Point::new(0, 1)..Point::new(0, 1),
6429 Point::new(1, 1)..Point::new(1, 1),
6430 Point::new(2, 1)..Point::new(2, 1),
6431 ])
6432 });
6433
6434 editor.handle_input("{", cx);
6435 editor.handle_input("{", cx);
6436 editor.handle_input("_", cx);
6437 assert_eq!(
6438 editor.text(cx),
6439 "
6440 a{{_}}
6441 b{{_}}
6442 c{{_}}
6443 "
6444 .unindent()
6445 );
6446 assert_eq!(
6447 editor.selections.ranges::<Point>(cx),
6448 [
6449 Point::new(0, 4)..Point::new(0, 4),
6450 Point::new(1, 4)..Point::new(1, 4),
6451 Point::new(2, 4)..Point::new(2, 4)
6452 ]
6453 );
6454
6455 editor.backspace(&Default::default(), cx);
6456 editor.backspace(&Default::default(), 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, 2)..Point::new(0, 2),
6470 Point::new(1, 2)..Point::new(1, 2),
6471 Point::new(2, 2)..Point::new(2, 2)
6472 ]
6473 );
6474
6475 editor.delete_to_previous_word_start(&Default::default(), cx);
6476 assert_eq!(
6477 editor.text(cx),
6478 "
6479 a
6480 b
6481 c
6482 "
6483 .unindent()
6484 );
6485 assert_eq!(
6486 editor.selections.ranges::<Point>(cx),
6487 [
6488 Point::new(0, 1)..Point::new(0, 1),
6489 Point::new(1, 1)..Point::new(1, 1),
6490 Point::new(2, 1)..Point::new(2, 1)
6491 ]
6492 );
6493 });
6494}
6495
6496#[gpui::test]
6497async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6498 init_test(cx, |settings| {
6499 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6500 });
6501
6502 let mut cx = EditorTestContext::new(cx).await;
6503
6504 let language = Arc::new(Language::new(
6505 LanguageConfig {
6506 brackets: BracketPairConfig {
6507 pairs: vec![
6508 BracketPair {
6509 start: "{".to_string(),
6510 end: "}".to_string(),
6511 close: true,
6512 surround: true,
6513 newline: true,
6514 },
6515 BracketPair {
6516 start: "(".to_string(),
6517 end: ")".to_string(),
6518 close: true,
6519 surround: true,
6520 newline: true,
6521 },
6522 BracketPair {
6523 start: "[".to_string(),
6524 end: "]".to_string(),
6525 close: false,
6526 surround: true,
6527 newline: true,
6528 },
6529 ],
6530 ..Default::default()
6531 },
6532 autoclose_before: "})]".to_string(),
6533 ..Default::default()
6534 },
6535 Some(tree_sitter_rust::LANGUAGE.into()),
6536 ));
6537
6538 cx.language_registry().add(language.clone());
6539 cx.update_buffer(|buffer, cx| {
6540 buffer.set_language(Some(language), cx);
6541 });
6542
6543 cx.set_state(
6544 &"
6545 {(ˇ)}
6546 [[ˇ]]
6547 {(ˇ)}
6548 "
6549 .unindent(),
6550 );
6551
6552 cx.update_editor(|view, cx| {
6553 view.backspace(&Default::default(), cx);
6554 view.backspace(&Default::default(), cx);
6555 });
6556
6557 cx.assert_editor_state(
6558 &"
6559 ˇ
6560 ˇ]]
6561 ˇ
6562 "
6563 .unindent(),
6564 );
6565
6566 cx.update_editor(|view, cx| {
6567 view.handle_input("{", cx);
6568 view.handle_input("{", cx);
6569 view.move_right(&MoveRight, cx);
6570 view.move_right(&MoveRight, cx);
6571 view.move_left(&MoveLeft, cx);
6572 view.move_left(&MoveLeft, cx);
6573 view.backspace(&Default::default(), cx);
6574 });
6575
6576 cx.assert_editor_state(
6577 &"
6578 {ˇ}
6579 {ˇ}]]
6580 {ˇ}
6581 "
6582 .unindent(),
6583 );
6584
6585 cx.update_editor(|view, cx| {
6586 view.backspace(&Default::default(), cx);
6587 });
6588
6589 cx.assert_editor_state(
6590 &"
6591 ˇ
6592 ˇ]]
6593 ˇ
6594 "
6595 .unindent(),
6596 );
6597}
6598
6599#[gpui::test]
6600async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6601 init_test(cx, |_| {});
6602
6603 let language = Arc::new(Language::new(
6604 LanguageConfig::default(),
6605 Some(tree_sitter_rust::LANGUAGE.into()),
6606 ));
6607
6608 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6609 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6610 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6611 editor
6612 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6613 .await;
6614
6615 editor.update(cx, |editor, cx| {
6616 editor.set_auto_replace_emoji_shortcode(true);
6617
6618 editor.handle_input("Hello ", cx);
6619 editor.handle_input(":wave", cx);
6620 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6621
6622 editor.handle_input(":", cx);
6623 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6624
6625 editor.handle_input(" :smile", cx);
6626 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6627
6628 editor.handle_input(":", cx);
6629 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6630
6631 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6632 editor.handle_input(":wave", cx);
6633 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6634
6635 editor.handle_input(":", cx);
6636 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6637
6638 editor.handle_input(":1", cx);
6639 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6640
6641 editor.handle_input(":", cx);
6642 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6643
6644 // Ensure shortcode does not get replaced when it is part of a word
6645 editor.handle_input(" Test:wave", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6647
6648 editor.handle_input(":", cx);
6649 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6650
6651 editor.set_auto_replace_emoji_shortcode(false);
6652
6653 // Ensure shortcode does not get replaced when auto replace is off
6654 editor.handle_input(" :wave", cx);
6655 assert_eq!(
6656 editor.text(cx),
6657 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6658 );
6659
6660 editor.handle_input(":", cx);
6661 assert_eq!(
6662 editor.text(cx),
6663 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6664 );
6665 });
6666}
6667
6668#[gpui::test]
6669async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6670 init_test(cx, |_| {});
6671
6672 let (text, insertion_ranges) = marked_text_ranges(
6673 indoc! {"
6674 ˇ
6675 "},
6676 false,
6677 );
6678
6679 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6680 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6681
6682 _ = editor.update(cx, |editor, cx| {
6683 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6684
6685 editor
6686 .insert_snippet(&insertion_ranges, snippet, cx)
6687 .unwrap();
6688
6689 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6690 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6691 assert_eq!(editor.text(cx), expected_text);
6692 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6693 }
6694
6695 assert(
6696 editor,
6697 cx,
6698 indoc! {"
6699 type «» =•
6700 "},
6701 );
6702
6703 assert!(editor.context_menu_visible(), "There should be a matches");
6704 });
6705}
6706
6707#[gpui::test]
6708async fn test_snippets(cx: &mut gpui::TestAppContext) {
6709 init_test(cx, |_| {});
6710
6711 let (text, insertion_ranges) = marked_text_ranges(
6712 indoc! {"
6713 a.ˇ b
6714 a.ˇ b
6715 a.ˇ b
6716 "},
6717 false,
6718 );
6719
6720 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6721 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6722
6723 editor.update(cx, |editor, cx| {
6724 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6725
6726 editor
6727 .insert_snippet(&insertion_ranges, snippet, cx)
6728 .unwrap();
6729
6730 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6731 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6732 assert_eq!(editor.text(cx), expected_text);
6733 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6734 }
6735
6736 assert(
6737 editor,
6738 cx,
6739 indoc! {"
6740 a.f(«one», two, «three») b
6741 a.f(«one», two, «three») b
6742 a.f(«one», two, «three») b
6743 "},
6744 );
6745
6746 // Can't move earlier than the first tab stop
6747 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6748 assert(
6749 editor,
6750 cx,
6751 indoc! {"
6752 a.f(«one», two, «three») b
6753 a.f(«one», two, «three») b
6754 a.f(«one», two, «three») b
6755 "},
6756 );
6757
6758 assert!(editor.move_to_next_snippet_tabstop(cx));
6759 assert(
6760 editor,
6761 cx,
6762 indoc! {"
6763 a.f(one, «two», three) b
6764 a.f(one, «two», three) b
6765 a.f(one, «two», three) b
6766 "},
6767 );
6768
6769 editor.move_to_prev_snippet_tabstop(cx);
6770 assert(
6771 editor,
6772 cx,
6773 indoc! {"
6774 a.f(«one», two, «three») b
6775 a.f(«one», two, «three») b
6776 a.f(«one», two, «three») b
6777 "},
6778 );
6779
6780 assert!(editor.move_to_next_snippet_tabstop(cx));
6781 assert(
6782 editor,
6783 cx,
6784 indoc! {"
6785 a.f(one, «two», three) b
6786 a.f(one, «two», three) b
6787 a.f(one, «two», three) b
6788 "},
6789 );
6790 assert!(editor.move_to_next_snippet_tabstop(cx));
6791 assert(
6792 editor,
6793 cx,
6794 indoc! {"
6795 a.f(one, two, three)ˇ b
6796 a.f(one, two, three)ˇ b
6797 a.f(one, two, three)ˇ b
6798 "},
6799 );
6800
6801 // As soon as the last tab stop is reached, snippet state is gone
6802 editor.move_to_prev_snippet_tabstop(cx);
6803 assert(
6804 editor,
6805 cx,
6806 indoc! {"
6807 a.f(one, two, three)ˇ b
6808 a.f(one, two, three)ˇ b
6809 a.f(one, two, three)ˇ b
6810 "},
6811 );
6812 });
6813}
6814
6815#[gpui::test]
6816async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6817 init_test(cx, |_| {});
6818
6819 let fs = FakeFs::new(cx.executor());
6820 fs.insert_file("/file.rs", Default::default()).await;
6821
6822 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6823
6824 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6825 language_registry.add(rust_lang());
6826 let mut fake_servers = language_registry.register_fake_lsp(
6827 "Rust",
6828 FakeLspAdapter {
6829 capabilities: lsp::ServerCapabilities {
6830 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6831 ..Default::default()
6832 },
6833 ..Default::default()
6834 },
6835 );
6836
6837 let buffer = project
6838 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6839 .await
6840 .unwrap();
6841
6842 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6843 let (editor, cx) =
6844 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6845 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6846 assert!(cx.read(|cx| editor.is_dirty(cx)));
6847
6848 cx.executor().start_waiting();
6849 let fake_server = fake_servers.next().await.unwrap();
6850
6851 let save = editor
6852 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6853 .unwrap();
6854 fake_server
6855 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6856 assert_eq!(
6857 params.text_document.uri,
6858 lsp::Url::from_file_path("/file.rs").unwrap()
6859 );
6860 assert_eq!(params.options.tab_size, 4);
6861 Ok(Some(vec![lsp::TextEdit::new(
6862 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6863 ", ".to_string(),
6864 )]))
6865 })
6866 .next()
6867 .await;
6868 cx.executor().start_waiting();
6869 save.await;
6870
6871 assert_eq!(
6872 editor.update(cx, |editor, cx| editor.text(cx)),
6873 "one, two\nthree\n"
6874 );
6875 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6876
6877 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6878 assert!(cx.read(|cx| editor.is_dirty(cx)));
6879
6880 // Ensure we can still save even if formatting hangs.
6881 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6882 assert_eq!(
6883 params.text_document.uri,
6884 lsp::Url::from_file_path("/file.rs").unwrap()
6885 );
6886 futures::future::pending::<()>().await;
6887 unreachable!()
6888 });
6889 let save = editor
6890 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6891 .unwrap();
6892 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6893 cx.executor().start_waiting();
6894 save.await;
6895 assert_eq!(
6896 editor.update(cx, |editor, cx| editor.text(cx)),
6897 "one\ntwo\nthree\n"
6898 );
6899 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6900
6901 // For non-dirty buffer, no formatting request should be sent
6902 let save = editor
6903 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6904 .unwrap();
6905 let _pending_format_request = fake_server
6906 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6907 panic!("Should not be invoked on non-dirty buffer");
6908 })
6909 .next();
6910 cx.executor().start_waiting();
6911 save.await;
6912
6913 // Set rust language override and assert overridden tabsize is sent to language server
6914 update_test_language_settings(cx, |settings| {
6915 settings.languages.insert(
6916 "Rust".into(),
6917 LanguageSettingsContent {
6918 tab_size: NonZeroU32::new(8),
6919 ..Default::default()
6920 },
6921 );
6922 });
6923
6924 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6925 assert!(cx.read(|cx| editor.is_dirty(cx)));
6926 let save = editor
6927 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6928 .unwrap();
6929 fake_server
6930 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6931 assert_eq!(
6932 params.text_document.uri,
6933 lsp::Url::from_file_path("/file.rs").unwrap()
6934 );
6935 assert_eq!(params.options.tab_size, 8);
6936 Ok(Some(vec![]))
6937 })
6938 .next()
6939 .await;
6940 cx.executor().start_waiting();
6941 save.await;
6942}
6943
6944#[gpui::test]
6945async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6946 init_test(cx, |_| {});
6947
6948 let cols = 4;
6949 let rows = 10;
6950 let sample_text_1 = sample_text(rows, cols, 'a');
6951 assert_eq!(
6952 sample_text_1,
6953 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6954 );
6955 let sample_text_2 = sample_text(rows, cols, 'l');
6956 assert_eq!(
6957 sample_text_2,
6958 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6959 );
6960 let sample_text_3 = sample_text(rows, cols, 'v');
6961 assert_eq!(
6962 sample_text_3,
6963 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6964 );
6965
6966 let fs = FakeFs::new(cx.executor());
6967 fs.insert_tree(
6968 "/a",
6969 json!({
6970 "main.rs": sample_text_1,
6971 "other.rs": sample_text_2,
6972 "lib.rs": sample_text_3,
6973 }),
6974 )
6975 .await;
6976
6977 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6978 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6979 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6980
6981 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6982 language_registry.add(rust_lang());
6983 let mut fake_servers = language_registry.register_fake_lsp(
6984 "Rust",
6985 FakeLspAdapter {
6986 capabilities: lsp::ServerCapabilities {
6987 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6988 ..Default::default()
6989 },
6990 ..Default::default()
6991 },
6992 );
6993
6994 let worktree = project.update(cx, |project, cx| {
6995 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6996 assert_eq!(worktrees.len(), 1);
6997 worktrees.pop().unwrap()
6998 });
6999 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7000
7001 let buffer_1 = project
7002 .update(cx, |project, cx| {
7003 project.open_buffer((worktree_id, "main.rs"), cx)
7004 })
7005 .await
7006 .unwrap();
7007 let buffer_2 = project
7008 .update(cx, |project, cx| {
7009 project.open_buffer((worktree_id, "other.rs"), cx)
7010 })
7011 .await
7012 .unwrap();
7013 let buffer_3 = project
7014 .update(cx, |project, cx| {
7015 project.open_buffer((worktree_id, "lib.rs"), cx)
7016 })
7017 .await
7018 .unwrap();
7019
7020 let multi_buffer = cx.new_model(|cx| {
7021 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7022 multi_buffer.push_excerpts(
7023 buffer_1.clone(),
7024 [
7025 ExcerptRange {
7026 context: Point::new(0, 0)..Point::new(3, 0),
7027 primary: None,
7028 },
7029 ExcerptRange {
7030 context: Point::new(5, 0)..Point::new(7, 0),
7031 primary: None,
7032 },
7033 ExcerptRange {
7034 context: Point::new(9, 0)..Point::new(10, 4),
7035 primary: None,
7036 },
7037 ],
7038 cx,
7039 );
7040 multi_buffer.push_excerpts(
7041 buffer_2.clone(),
7042 [
7043 ExcerptRange {
7044 context: Point::new(0, 0)..Point::new(3, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(5, 0)..Point::new(7, 0),
7049 primary: None,
7050 },
7051 ExcerptRange {
7052 context: Point::new(9, 0)..Point::new(10, 4),
7053 primary: None,
7054 },
7055 ],
7056 cx,
7057 );
7058 multi_buffer.push_excerpts(
7059 buffer_3.clone(),
7060 [
7061 ExcerptRange {
7062 context: Point::new(0, 0)..Point::new(3, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(5, 0)..Point::new(7, 0),
7067 primary: None,
7068 },
7069 ExcerptRange {
7070 context: Point::new(9, 0)..Point::new(10, 4),
7071 primary: None,
7072 },
7073 ],
7074 cx,
7075 );
7076 multi_buffer
7077 });
7078 let multi_buffer_editor = cx.new_view(|cx| {
7079 Editor::new(
7080 EditorMode::Full,
7081 multi_buffer,
7082 Some(project.clone()),
7083 true,
7084 cx,
7085 )
7086 });
7087
7088 multi_buffer_editor.update(cx, |editor, cx| {
7089 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7090 editor.insert("|one|two|three|", cx);
7091 });
7092 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7093 multi_buffer_editor.update(cx, |editor, cx| {
7094 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7095 s.select_ranges(Some(60..70))
7096 });
7097 editor.insert("|four|five|six|", cx);
7098 });
7099 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7100
7101 // First two buffers should be edited, but not the third one.
7102 assert_eq!(
7103 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7104 "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}",
7105 );
7106 buffer_1.update(cx, |buffer, _| {
7107 assert!(buffer.is_dirty());
7108 assert_eq!(
7109 buffer.text(),
7110 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7111 )
7112 });
7113 buffer_2.update(cx, |buffer, _| {
7114 assert!(buffer.is_dirty());
7115 assert_eq!(
7116 buffer.text(),
7117 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7118 )
7119 });
7120 buffer_3.update(cx, |buffer, _| {
7121 assert!(!buffer.is_dirty());
7122 assert_eq!(buffer.text(), sample_text_3,)
7123 });
7124 cx.executor().run_until_parked();
7125
7126 cx.executor().start_waiting();
7127 let save = multi_buffer_editor
7128 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7129 .unwrap();
7130
7131 let fake_server = fake_servers.next().await.unwrap();
7132 fake_server
7133 .server
7134 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7135 Ok(Some(vec![lsp::TextEdit::new(
7136 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7137 format!("[{} formatted]", params.text_document.uri),
7138 )]))
7139 })
7140 .detach();
7141 save.await;
7142
7143 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7144 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7145 assert_eq!(
7146 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7147 "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}",
7148 );
7149 buffer_1.update(cx, |buffer, _| {
7150 assert!(!buffer.is_dirty());
7151 assert_eq!(
7152 buffer.text(),
7153 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7154 )
7155 });
7156 buffer_2.update(cx, |buffer, _| {
7157 assert!(!buffer.is_dirty());
7158 assert_eq!(
7159 buffer.text(),
7160 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7161 )
7162 });
7163 buffer_3.update(cx, |buffer, _| {
7164 assert!(!buffer.is_dirty());
7165 assert_eq!(buffer.text(), sample_text_3,)
7166 });
7167}
7168
7169#[gpui::test]
7170async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7171 init_test(cx, |_| {});
7172
7173 let fs = FakeFs::new(cx.executor());
7174 fs.insert_file("/file.rs", Default::default()).await;
7175
7176 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7177
7178 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7179 language_registry.add(rust_lang());
7180 let mut fake_servers = language_registry.register_fake_lsp(
7181 "Rust",
7182 FakeLspAdapter {
7183 capabilities: lsp::ServerCapabilities {
7184 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7185 ..Default::default()
7186 },
7187 ..Default::default()
7188 },
7189 );
7190
7191 let buffer = project
7192 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7193 .await
7194 .unwrap();
7195
7196 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7197 let (editor, cx) =
7198 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7199 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7200 assert!(cx.read(|cx| editor.is_dirty(cx)));
7201
7202 cx.executor().start_waiting();
7203 let fake_server = fake_servers.next().await.unwrap();
7204
7205 let save = editor
7206 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7207 .unwrap();
7208 fake_server
7209 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7210 assert_eq!(
7211 params.text_document.uri,
7212 lsp::Url::from_file_path("/file.rs").unwrap()
7213 );
7214 assert_eq!(params.options.tab_size, 4);
7215 Ok(Some(vec![lsp::TextEdit::new(
7216 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7217 ", ".to_string(),
7218 )]))
7219 })
7220 .next()
7221 .await;
7222 cx.executor().start_waiting();
7223 save.await;
7224 assert_eq!(
7225 editor.update(cx, |editor, cx| editor.text(cx)),
7226 "one, two\nthree\n"
7227 );
7228 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7229
7230 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7231 assert!(cx.read(|cx| editor.is_dirty(cx)));
7232
7233 // Ensure we can still save even if formatting hangs.
7234 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7235 move |params, _| async move {
7236 assert_eq!(
7237 params.text_document.uri,
7238 lsp::Url::from_file_path("/file.rs").unwrap()
7239 );
7240 futures::future::pending::<()>().await;
7241 unreachable!()
7242 },
7243 );
7244 let save = editor
7245 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7246 .unwrap();
7247 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7248 cx.executor().start_waiting();
7249 save.await;
7250 assert_eq!(
7251 editor.update(cx, |editor, cx| editor.text(cx)),
7252 "one\ntwo\nthree\n"
7253 );
7254 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7255
7256 // For non-dirty buffer, no formatting request should be sent
7257 let save = editor
7258 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7259 .unwrap();
7260 let _pending_format_request = fake_server
7261 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7262 panic!("Should not be invoked on non-dirty buffer");
7263 })
7264 .next();
7265 cx.executor().start_waiting();
7266 save.await;
7267
7268 // Set Rust language override and assert overridden tabsize is sent to language server
7269 update_test_language_settings(cx, |settings| {
7270 settings.languages.insert(
7271 "Rust".into(),
7272 LanguageSettingsContent {
7273 tab_size: NonZeroU32::new(8),
7274 ..Default::default()
7275 },
7276 );
7277 });
7278
7279 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7280 assert!(cx.read(|cx| editor.is_dirty(cx)));
7281 let save = editor
7282 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7283 .unwrap();
7284 fake_server
7285 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7286 assert_eq!(
7287 params.text_document.uri,
7288 lsp::Url::from_file_path("/file.rs").unwrap()
7289 );
7290 assert_eq!(params.options.tab_size, 8);
7291 Ok(Some(vec![]))
7292 })
7293 .next()
7294 .await;
7295 cx.executor().start_waiting();
7296 save.await;
7297}
7298
7299#[gpui::test]
7300async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7301 init_test(cx, |settings| {
7302 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7303 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7304 ))
7305 });
7306
7307 let fs = FakeFs::new(cx.executor());
7308 fs.insert_file("/file.rs", Default::default()).await;
7309
7310 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7311
7312 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7313 language_registry.add(Arc::new(Language::new(
7314 LanguageConfig {
7315 name: "Rust".into(),
7316 matcher: LanguageMatcher {
7317 path_suffixes: vec!["rs".to_string()],
7318 ..Default::default()
7319 },
7320 ..LanguageConfig::default()
7321 },
7322 Some(tree_sitter_rust::LANGUAGE.into()),
7323 )));
7324 update_test_language_settings(cx, |settings| {
7325 // Enable Prettier formatting for the same buffer, and ensure
7326 // LSP is called instead of Prettier.
7327 settings.defaults.prettier = Some(PrettierSettings {
7328 allowed: true,
7329 ..PrettierSettings::default()
7330 });
7331 });
7332 let mut fake_servers = language_registry.register_fake_lsp(
7333 "Rust",
7334 FakeLspAdapter {
7335 capabilities: lsp::ServerCapabilities {
7336 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7337 ..Default::default()
7338 },
7339 ..Default::default()
7340 },
7341 );
7342
7343 let buffer = project
7344 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7345 .await
7346 .unwrap();
7347
7348 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7349 let (editor, cx) =
7350 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7351 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7352
7353 cx.executor().start_waiting();
7354 let fake_server = fake_servers.next().await.unwrap();
7355
7356 let format = editor
7357 .update(cx, |editor, cx| {
7358 editor.perform_format(
7359 project.clone(),
7360 FormatTrigger::Manual,
7361 FormatTarget::Buffer,
7362 cx,
7363 )
7364 })
7365 .unwrap();
7366 fake_server
7367 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7368 assert_eq!(
7369 params.text_document.uri,
7370 lsp::Url::from_file_path("/file.rs").unwrap()
7371 );
7372 assert_eq!(params.options.tab_size, 4);
7373 Ok(Some(vec![lsp::TextEdit::new(
7374 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7375 ", ".to_string(),
7376 )]))
7377 })
7378 .next()
7379 .await;
7380 cx.executor().start_waiting();
7381 format.await;
7382 assert_eq!(
7383 editor.update(cx, |editor, cx| editor.text(cx)),
7384 "one, two\nthree\n"
7385 );
7386
7387 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7388 // Ensure we don't lock if formatting hangs.
7389 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7390 assert_eq!(
7391 params.text_document.uri,
7392 lsp::Url::from_file_path("/file.rs").unwrap()
7393 );
7394 futures::future::pending::<()>().await;
7395 unreachable!()
7396 });
7397 let format = editor
7398 .update(cx, |editor, cx| {
7399 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7400 })
7401 .unwrap();
7402 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7403 cx.executor().start_waiting();
7404 format.await;
7405 assert_eq!(
7406 editor.update(cx, |editor, cx| editor.text(cx)),
7407 "one\ntwo\nthree\n"
7408 );
7409}
7410
7411#[gpui::test]
7412async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7413 init_test(cx, |_| {});
7414
7415 let mut cx = EditorLspTestContext::new_rust(
7416 lsp::ServerCapabilities {
7417 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7418 ..Default::default()
7419 },
7420 cx,
7421 )
7422 .await;
7423
7424 cx.set_state(indoc! {"
7425 one.twoˇ
7426 "});
7427
7428 // The format request takes a long time. When it completes, it inserts
7429 // a newline and an indent before the `.`
7430 cx.lsp
7431 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7432 let executor = cx.background_executor().clone();
7433 async move {
7434 executor.timer(Duration::from_millis(100)).await;
7435 Ok(Some(vec![lsp::TextEdit {
7436 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7437 new_text: "\n ".into(),
7438 }]))
7439 }
7440 });
7441
7442 // Submit a format request.
7443 let format_1 = cx
7444 .update_editor(|editor, cx| editor.format(&Format, cx))
7445 .unwrap();
7446 cx.executor().run_until_parked();
7447
7448 // Submit a second format request.
7449 let format_2 = cx
7450 .update_editor(|editor, cx| editor.format(&Format, cx))
7451 .unwrap();
7452 cx.executor().run_until_parked();
7453
7454 // Wait for both format requests to complete
7455 cx.executor().advance_clock(Duration::from_millis(200));
7456 cx.executor().start_waiting();
7457 format_1.await.unwrap();
7458 cx.executor().start_waiting();
7459 format_2.await.unwrap();
7460
7461 // The formatting edits only happens once.
7462 cx.assert_editor_state(indoc! {"
7463 one
7464 .twoˇ
7465 "});
7466}
7467
7468#[gpui::test]
7469async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7470 init_test(cx, |settings| {
7471 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7472 });
7473
7474 let mut cx = EditorLspTestContext::new_rust(
7475 lsp::ServerCapabilities {
7476 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7477 ..Default::default()
7478 },
7479 cx,
7480 )
7481 .await;
7482
7483 // Set up a buffer white some trailing whitespace and no trailing newline.
7484 cx.set_state(
7485 &[
7486 "one ", //
7487 "twoˇ", //
7488 "three ", //
7489 "four", //
7490 ]
7491 .join("\n"),
7492 );
7493
7494 // Submit a format request.
7495 let format = cx
7496 .update_editor(|editor, cx| editor.format(&Format, cx))
7497 .unwrap();
7498
7499 // Record which buffer changes have been sent to the language server
7500 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7501 cx.lsp
7502 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7503 let buffer_changes = buffer_changes.clone();
7504 move |params, _| {
7505 buffer_changes.lock().extend(
7506 params
7507 .content_changes
7508 .into_iter()
7509 .map(|e| (e.range.unwrap(), e.text)),
7510 );
7511 }
7512 });
7513
7514 // Handle formatting requests to the language server.
7515 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7516 let buffer_changes = buffer_changes.clone();
7517 move |_, _| {
7518 // When formatting is requested, trailing whitespace has already been stripped,
7519 // and the trailing newline has already been added.
7520 assert_eq!(
7521 &buffer_changes.lock()[1..],
7522 &[
7523 (
7524 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7525 "".into()
7526 ),
7527 (
7528 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7529 "".into()
7530 ),
7531 (
7532 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7533 "\n".into()
7534 ),
7535 ]
7536 );
7537
7538 // Insert blank lines between each line of the buffer.
7539 async move {
7540 Ok(Some(vec![
7541 lsp::TextEdit {
7542 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7543 new_text: "\n".into(),
7544 },
7545 lsp::TextEdit {
7546 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7547 new_text: "\n".into(),
7548 },
7549 ]))
7550 }
7551 }
7552 });
7553
7554 // After formatting the buffer, the trailing whitespace is stripped,
7555 // a newline is appended, and the edits provided by the language server
7556 // have been applied.
7557 format.await.unwrap();
7558 cx.assert_editor_state(
7559 &[
7560 "one", //
7561 "", //
7562 "twoˇ", //
7563 "", //
7564 "three", //
7565 "four", //
7566 "", //
7567 ]
7568 .join("\n"),
7569 );
7570
7571 // Undoing the formatting undoes the trailing whitespace removal, the
7572 // trailing newline, and the LSP edits.
7573 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7574 cx.assert_editor_state(
7575 &[
7576 "one ", //
7577 "twoˇ", //
7578 "three ", //
7579 "four", //
7580 ]
7581 .join("\n"),
7582 );
7583}
7584
7585#[gpui::test]
7586async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7587 cx: &mut gpui::TestAppContext,
7588) {
7589 init_test(cx, |_| {});
7590
7591 cx.update(|cx| {
7592 cx.update_global::<SettingsStore, _>(|settings, cx| {
7593 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7594 settings.auto_signature_help = Some(true);
7595 });
7596 });
7597 });
7598
7599 let mut cx = EditorLspTestContext::new_rust(
7600 lsp::ServerCapabilities {
7601 signature_help_provider: Some(lsp::SignatureHelpOptions {
7602 ..Default::default()
7603 }),
7604 ..Default::default()
7605 },
7606 cx,
7607 )
7608 .await;
7609
7610 let language = Language::new(
7611 LanguageConfig {
7612 name: "Rust".into(),
7613 brackets: BracketPairConfig {
7614 pairs: vec![
7615 BracketPair {
7616 start: "{".to_string(),
7617 end: "}".to_string(),
7618 close: true,
7619 surround: true,
7620 newline: true,
7621 },
7622 BracketPair {
7623 start: "(".to_string(),
7624 end: ")".to_string(),
7625 close: true,
7626 surround: true,
7627 newline: true,
7628 },
7629 BracketPair {
7630 start: "/*".to_string(),
7631 end: " */".to_string(),
7632 close: true,
7633 surround: true,
7634 newline: true,
7635 },
7636 BracketPair {
7637 start: "[".to_string(),
7638 end: "]".to_string(),
7639 close: false,
7640 surround: false,
7641 newline: true,
7642 },
7643 BracketPair {
7644 start: "\"".to_string(),
7645 end: "\"".to_string(),
7646 close: true,
7647 surround: true,
7648 newline: false,
7649 },
7650 BracketPair {
7651 start: "<".to_string(),
7652 end: ">".to_string(),
7653 close: false,
7654 surround: true,
7655 newline: true,
7656 },
7657 ],
7658 ..Default::default()
7659 },
7660 autoclose_before: "})]".to_string(),
7661 ..Default::default()
7662 },
7663 Some(tree_sitter_rust::LANGUAGE.into()),
7664 );
7665 let language = Arc::new(language);
7666
7667 cx.language_registry().add(language.clone());
7668 cx.update_buffer(|buffer, cx| {
7669 buffer.set_language(Some(language), cx);
7670 });
7671
7672 cx.set_state(
7673 &r#"
7674 fn main() {
7675 sampleˇ
7676 }
7677 "#
7678 .unindent(),
7679 );
7680
7681 cx.update_editor(|view, cx| {
7682 view.handle_input("(", cx);
7683 });
7684 cx.assert_editor_state(
7685 &"
7686 fn main() {
7687 sample(ˇ)
7688 }
7689 "
7690 .unindent(),
7691 );
7692
7693 let mocked_response = lsp::SignatureHelp {
7694 signatures: vec![lsp::SignatureInformation {
7695 label: "fn sample(param1: u8, param2: u8)".to_string(),
7696 documentation: None,
7697 parameters: Some(vec![
7698 lsp::ParameterInformation {
7699 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7700 documentation: None,
7701 },
7702 lsp::ParameterInformation {
7703 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7704 documentation: None,
7705 },
7706 ]),
7707 active_parameter: None,
7708 }],
7709 active_signature: Some(0),
7710 active_parameter: Some(0),
7711 };
7712 handle_signature_help_request(&mut cx, mocked_response).await;
7713
7714 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7715 .await;
7716
7717 cx.editor(|editor, _| {
7718 let signature_help_state = editor.signature_help_state.popover().cloned();
7719 assert!(signature_help_state.is_some());
7720 let ParsedMarkdown {
7721 text, highlights, ..
7722 } = signature_help_state.unwrap().parsed_content;
7723 assert_eq!(text, "param1: u8, param2: u8");
7724 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7725 });
7726}
7727
7728#[gpui::test]
7729async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7730 init_test(cx, |_| {});
7731
7732 cx.update(|cx| {
7733 cx.update_global::<SettingsStore, _>(|settings, cx| {
7734 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7735 settings.auto_signature_help = Some(false);
7736 settings.show_signature_help_after_edits = Some(false);
7737 });
7738 });
7739 });
7740
7741 let mut cx = EditorLspTestContext::new_rust(
7742 lsp::ServerCapabilities {
7743 signature_help_provider: Some(lsp::SignatureHelpOptions {
7744 ..Default::default()
7745 }),
7746 ..Default::default()
7747 },
7748 cx,
7749 )
7750 .await;
7751
7752 let language = Language::new(
7753 LanguageConfig {
7754 name: "Rust".into(),
7755 brackets: BracketPairConfig {
7756 pairs: vec![
7757 BracketPair {
7758 start: "{".to_string(),
7759 end: "}".to_string(),
7760 close: true,
7761 surround: true,
7762 newline: true,
7763 },
7764 BracketPair {
7765 start: "(".to_string(),
7766 end: ")".to_string(),
7767 close: true,
7768 surround: true,
7769 newline: true,
7770 },
7771 BracketPair {
7772 start: "/*".to_string(),
7773 end: " */".to_string(),
7774 close: true,
7775 surround: true,
7776 newline: true,
7777 },
7778 BracketPair {
7779 start: "[".to_string(),
7780 end: "]".to_string(),
7781 close: false,
7782 surround: false,
7783 newline: true,
7784 },
7785 BracketPair {
7786 start: "\"".to_string(),
7787 end: "\"".to_string(),
7788 close: true,
7789 surround: true,
7790 newline: false,
7791 },
7792 BracketPair {
7793 start: "<".to_string(),
7794 end: ">".to_string(),
7795 close: false,
7796 surround: true,
7797 newline: true,
7798 },
7799 ],
7800 ..Default::default()
7801 },
7802 autoclose_before: "})]".to_string(),
7803 ..Default::default()
7804 },
7805 Some(tree_sitter_rust::LANGUAGE.into()),
7806 );
7807 let language = Arc::new(language);
7808
7809 cx.language_registry().add(language.clone());
7810 cx.update_buffer(|buffer, cx| {
7811 buffer.set_language(Some(language), cx);
7812 });
7813
7814 // Ensure that signature_help is not called when no signature help is enabled.
7815 cx.set_state(
7816 &r#"
7817 fn main() {
7818 sampleˇ
7819 }
7820 "#
7821 .unindent(),
7822 );
7823 cx.update_editor(|view, cx| {
7824 view.handle_input("(", cx);
7825 });
7826 cx.assert_editor_state(
7827 &"
7828 fn main() {
7829 sample(ˇ)
7830 }
7831 "
7832 .unindent(),
7833 );
7834 cx.editor(|editor, _| {
7835 assert!(editor.signature_help_state.task().is_none());
7836 });
7837
7838 let mocked_response = lsp::SignatureHelp {
7839 signatures: vec![lsp::SignatureInformation {
7840 label: "fn sample(param1: u8, param2: u8)".to_string(),
7841 documentation: None,
7842 parameters: Some(vec![
7843 lsp::ParameterInformation {
7844 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7845 documentation: None,
7846 },
7847 lsp::ParameterInformation {
7848 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7849 documentation: None,
7850 },
7851 ]),
7852 active_parameter: None,
7853 }],
7854 active_signature: Some(0),
7855 active_parameter: Some(0),
7856 };
7857
7858 // Ensure that signature_help is called when enabled afte edits
7859 cx.update(|cx| {
7860 cx.update_global::<SettingsStore, _>(|settings, cx| {
7861 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7862 settings.auto_signature_help = Some(false);
7863 settings.show_signature_help_after_edits = Some(true);
7864 });
7865 });
7866 });
7867 cx.set_state(
7868 &r#"
7869 fn main() {
7870 sampleˇ
7871 }
7872 "#
7873 .unindent(),
7874 );
7875 cx.update_editor(|view, cx| {
7876 view.handle_input("(", cx);
7877 });
7878 cx.assert_editor_state(
7879 &"
7880 fn main() {
7881 sample(ˇ)
7882 }
7883 "
7884 .unindent(),
7885 );
7886 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7887 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7888 .await;
7889 cx.update_editor(|editor, _| {
7890 let signature_help_state = editor.signature_help_state.popover().cloned();
7891 assert!(signature_help_state.is_some());
7892 let ParsedMarkdown {
7893 text, highlights, ..
7894 } = signature_help_state.unwrap().parsed_content;
7895 assert_eq!(text, "param1: u8, param2: u8");
7896 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7897 editor.signature_help_state = SignatureHelpState::default();
7898 });
7899
7900 // Ensure that signature_help is called when auto signature help override is enabled
7901 cx.update(|cx| {
7902 cx.update_global::<SettingsStore, _>(|settings, cx| {
7903 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7904 settings.auto_signature_help = Some(true);
7905 settings.show_signature_help_after_edits = Some(false);
7906 });
7907 });
7908 });
7909 cx.set_state(
7910 &r#"
7911 fn main() {
7912 sampleˇ
7913 }
7914 "#
7915 .unindent(),
7916 );
7917 cx.update_editor(|view, cx| {
7918 view.handle_input("(", cx);
7919 });
7920 cx.assert_editor_state(
7921 &"
7922 fn main() {
7923 sample(ˇ)
7924 }
7925 "
7926 .unindent(),
7927 );
7928 handle_signature_help_request(&mut cx, mocked_response).await;
7929 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7930 .await;
7931 cx.editor(|editor, _| {
7932 let signature_help_state = editor.signature_help_state.popover().cloned();
7933 assert!(signature_help_state.is_some());
7934 let ParsedMarkdown {
7935 text, highlights, ..
7936 } = signature_help_state.unwrap().parsed_content;
7937 assert_eq!(text, "param1: u8, param2: u8");
7938 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7939 });
7940}
7941
7942#[gpui::test]
7943async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7944 init_test(cx, |_| {});
7945 cx.update(|cx| {
7946 cx.update_global::<SettingsStore, _>(|settings, cx| {
7947 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7948 settings.auto_signature_help = Some(true);
7949 });
7950 });
7951 });
7952
7953 let mut cx = EditorLspTestContext::new_rust(
7954 lsp::ServerCapabilities {
7955 signature_help_provider: Some(lsp::SignatureHelpOptions {
7956 ..Default::default()
7957 }),
7958 ..Default::default()
7959 },
7960 cx,
7961 )
7962 .await;
7963
7964 // A test that directly calls `show_signature_help`
7965 cx.update_editor(|editor, cx| {
7966 editor.show_signature_help(&ShowSignatureHelp, cx);
7967 });
7968
7969 let mocked_response = lsp::SignatureHelp {
7970 signatures: vec![lsp::SignatureInformation {
7971 label: "fn sample(param1: u8, param2: u8)".to_string(),
7972 documentation: None,
7973 parameters: Some(vec![
7974 lsp::ParameterInformation {
7975 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7976 documentation: None,
7977 },
7978 lsp::ParameterInformation {
7979 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7980 documentation: None,
7981 },
7982 ]),
7983 active_parameter: None,
7984 }],
7985 active_signature: Some(0),
7986 active_parameter: Some(0),
7987 };
7988 handle_signature_help_request(&mut cx, mocked_response).await;
7989
7990 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7991 .await;
7992
7993 cx.editor(|editor, _| {
7994 let signature_help_state = editor.signature_help_state.popover().cloned();
7995 assert!(signature_help_state.is_some());
7996 let ParsedMarkdown {
7997 text, highlights, ..
7998 } = signature_help_state.unwrap().parsed_content;
7999 assert_eq!(text, "param1: u8, param2: u8");
8000 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8001 });
8002
8003 // When exiting outside from inside the brackets, `signature_help` is closed.
8004 cx.set_state(indoc! {"
8005 fn main() {
8006 sample(ˇ);
8007 }
8008
8009 fn sample(param1: u8, param2: u8) {}
8010 "});
8011
8012 cx.update_editor(|editor, cx| {
8013 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8014 });
8015
8016 let mocked_response = lsp::SignatureHelp {
8017 signatures: Vec::new(),
8018 active_signature: None,
8019 active_parameter: None,
8020 };
8021 handle_signature_help_request(&mut cx, mocked_response).await;
8022
8023 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8024 .await;
8025
8026 cx.editor(|editor, _| {
8027 assert!(!editor.signature_help_state.is_shown());
8028 });
8029
8030 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8031 cx.set_state(indoc! {"
8032 fn main() {
8033 sample(ˇ);
8034 }
8035
8036 fn sample(param1: u8, param2: u8) {}
8037 "});
8038
8039 let mocked_response = lsp::SignatureHelp {
8040 signatures: vec![lsp::SignatureInformation {
8041 label: "fn sample(param1: u8, param2: u8)".to_string(),
8042 documentation: None,
8043 parameters: Some(vec![
8044 lsp::ParameterInformation {
8045 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8046 documentation: None,
8047 },
8048 lsp::ParameterInformation {
8049 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8050 documentation: None,
8051 },
8052 ]),
8053 active_parameter: None,
8054 }],
8055 active_signature: Some(0),
8056 active_parameter: Some(0),
8057 };
8058 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8059 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8060 .await;
8061 cx.editor(|editor, _| {
8062 assert!(editor.signature_help_state.is_shown());
8063 });
8064
8065 // Restore the popover with more parameter input
8066 cx.set_state(indoc! {"
8067 fn main() {
8068 sample(param1, param2ˇ);
8069 }
8070
8071 fn sample(param1: u8, param2: u8) {}
8072 "});
8073
8074 let mocked_response = lsp::SignatureHelp {
8075 signatures: vec![lsp::SignatureInformation {
8076 label: "fn sample(param1: u8, param2: u8)".to_string(),
8077 documentation: None,
8078 parameters: Some(vec![
8079 lsp::ParameterInformation {
8080 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8081 documentation: None,
8082 },
8083 lsp::ParameterInformation {
8084 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8085 documentation: None,
8086 },
8087 ]),
8088 active_parameter: None,
8089 }],
8090 active_signature: Some(0),
8091 active_parameter: Some(1),
8092 };
8093 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8094 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8095 .await;
8096
8097 // When selecting a range, the popover is gone.
8098 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8099 cx.update_editor(|editor, cx| {
8100 editor.change_selections(None, cx, |s| {
8101 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8102 })
8103 });
8104 cx.assert_editor_state(indoc! {"
8105 fn main() {
8106 sample(param1, «ˇparam2»);
8107 }
8108
8109 fn sample(param1: u8, param2: u8) {}
8110 "});
8111 cx.editor(|editor, _| {
8112 assert!(!editor.signature_help_state.is_shown());
8113 });
8114
8115 // When unselecting again, the popover is back if within the brackets.
8116 cx.update_editor(|editor, cx| {
8117 editor.change_selections(None, cx, |s| {
8118 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8119 })
8120 });
8121 cx.assert_editor_state(indoc! {"
8122 fn main() {
8123 sample(param1, ˇparam2);
8124 }
8125
8126 fn sample(param1: u8, param2: u8) {}
8127 "});
8128 handle_signature_help_request(&mut cx, mocked_response).await;
8129 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8130 .await;
8131 cx.editor(|editor, _| {
8132 assert!(editor.signature_help_state.is_shown());
8133 });
8134
8135 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8136 cx.update_editor(|editor, cx| {
8137 editor.change_selections(None, cx, |s| {
8138 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8139 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8140 })
8141 });
8142 cx.assert_editor_state(indoc! {"
8143 fn main() {
8144 sample(param1, ˇparam2);
8145 }
8146
8147 fn sample(param1: u8, param2: u8) {}
8148 "});
8149
8150 let mocked_response = lsp::SignatureHelp {
8151 signatures: vec![lsp::SignatureInformation {
8152 label: "fn sample(param1: u8, param2: u8)".to_string(),
8153 documentation: None,
8154 parameters: Some(vec![
8155 lsp::ParameterInformation {
8156 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8157 documentation: None,
8158 },
8159 lsp::ParameterInformation {
8160 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8161 documentation: None,
8162 },
8163 ]),
8164 active_parameter: None,
8165 }],
8166 active_signature: Some(0),
8167 active_parameter: Some(1),
8168 };
8169 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8170 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8171 .await;
8172 cx.update_editor(|editor, cx| {
8173 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8174 });
8175 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8176 .await;
8177 cx.update_editor(|editor, cx| {
8178 editor.change_selections(None, cx, |s| {
8179 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8180 })
8181 });
8182 cx.assert_editor_state(indoc! {"
8183 fn main() {
8184 sample(param1, «ˇparam2»);
8185 }
8186
8187 fn sample(param1: u8, param2: u8) {}
8188 "});
8189 cx.update_editor(|editor, cx| {
8190 editor.change_selections(None, cx, |s| {
8191 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8192 })
8193 });
8194 cx.assert_editor_state(indoc! {"
8195 fn main() {
8196 sample(param1, ˇparam2);
8197 }
8198
8199 fn sample(param1: u8, param2: u8) {}
8200 "});
8201 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8202 .await;
8203}
8204
8205#[gpui::test]
8206async fn test_completion(cx: &mut gpui::TestAppContext) {
8207 init_test(cx, |_| {});
8208
8209 let mut cx = EditorLspTestContext::new_rust(
8210 lsp::ServerCapabilities {
8211 completion_provider: Some(lsp::CompletionOptions {
8212 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8213 resolve_provider: Some(true),
8214 ..Default::default()
8215 }),
8216 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8217 ..Default::default()
8218 },
8219 cx,
8220 )
8221 .await;
8222 let counter = Arc::new(AtomicUsize::new(0));
8223
8224 cx.set_state(indoc! {"
8225 oneˇ
8226 two
8227 three
8228 "});
8229 cx.simulate_keystroke(".");
8230 handle_completion_request(
8231 &mut cx,
8232 indoc! {"
8233 one.|<>
8234 two
8235 three
8236 "},
8237 vec!["first_completion", "second_completion"],
8238 counter.clone(),
8239 )
8240 .await;
8241 cx.condition(|editor, _| editor.context_menu_visible())
8242 .await;
8243 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8244
8245 let _handler = handle_signature_help_request(
8246 &mut cx,
8247 lsp::SignatureHelp {
8248 signatures: vec![lsp::SignatureInformation {
8249 label: "test signature".to_string(),
8250 documentation: None,
8251 parameters: Some(vec![lsp::ParameterInformation {
8252 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8253 documentation: None,
8254 }]),
8255 active_parameter: None,
8256 }],
8257 active_signature: None,
8258 active_parameter: None,
8259 },
8260 );
8261 cx.update_editor(|editor, cx| {
8262 assert!(
8263 !editor.signature_help_state.is_shown(),
8264 "No signature help was called for"
8265 );
8266 editor.show_signature_help(&ShowSignatureHelp, cx);
8267 });
8268 cx.run_until_parked();
8269 cx.update_editor(|editor, _| {
8270 assert!(
8271 !editor.signature_help_state.is_shown(),
8272 "No signature help should be shown when completions menu is open"
8273 );
8274 });
8275
8276 let apply_additional_edits = cx.update_editor(|editor, cx| {
8277 editor.context_menu_next(&Default::default(), cx);
8278 editor
8279 .confirm_completion(&ConfirmCompletion::default(), cx)
8280 .unwrap()
8281 });
8282 cx.assert_editor_state(indoc! {"
8283 one.second_completionˇ
8284 two
8285 three
8286 "});
8287
8288 handle_resolve_completion_request(
8289 &mut cx,
8290 Some(vec![
8291 (
8292 //This overlaps with the primary completion edit which is
8293 //misbehavior from the LSP spec, test that we filter it out
8294 indoc! {"
8295 one.second_ˇcompletion
8296 two
8297 threeˇ
8298 "},
8299 "overlapping additional edit",
8300 ),
8301 (
8302 indoc! {"
8303 one.second_completion
8304 two
8305 threeˇ
8306 "},
8307 "\nadditional edit",
8308 ),
8309 ]),
8310 )
8311 .await;
8312 apply_additional_edits.await.unwrap();
8313 cx.assert_editor_state(indoc! {"
8314 one.second_completionˇ
8315 two
8316 three
8317 additional edit
8318 "});
8319
8320 cx.set_state(indoc! {"
8321 one.second_completion
8322 twoˇ
8323 threeˇ
8324 additional edit
8325 "});
8326 cx.simulate_keystroke(" ");
8327 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8328 cx.simulate_keystroke("s");
8329 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8330
8331 cx.assert_editor_state(indoc! {"
8332 one.second_completion
8333 two sˇ
8334 three sˇ
8335 additional edit
8336 "});
8337 handle_completion_request(
8338 &mut cx,
8339 indoc! {"
8340 one.second_completion
8341 two s
8342 three <s|>
8343 additional edit
8344 "},
8345 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8346 counter.clone(),
8347 )
8348 .await;
8349 cx.condition(|editor, _| editor.context_menu_visible())
8350 .await;
8351 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8352
8353 cx.simulate_keystroke("i");
8354
8355 handle_completion_request(
8356 &mut cx,
8357 indoc! {"
8358 one.second_completion
8359 two si
8360 three <si|>
8361 additional edit
8362 "},
8363 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8364 counter.clone(),
8365 )
8366 .await;
8367 cx.condition(|editor, _| editor.context_menu_visible())
8368 .await;
8369 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8370
8371 let apply_additional_edits = cx.update_editor(|editor, cx| {
8372 editor
8373 .confirm_completion(&ConfirmCompletion::default(), cx)
8374 .unwrap()
8375 });
8376 cx.assert_editor_state(indoc! {"
8377 one.second_completion
8378 two sixth_completionˇ
8379 three sixth_completionˇ
8380 additional edit
8381 "});
8382
8383 handle_resolve_completion_request(&mut cx, None).await;
8384 apply_additional_edits.await.unwrap();
8385
8386 update_test_language_settings(&mut cx, |settings| {
8387 settings.defaults.show_completions_on_input = Some(false);
8388 });
8389 cx.set_state("editorˇ");
8390 cx.simulate_keystroke(".");
8391 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8392 cx.simulate_keystroke("c");
8393 cx.simulate_keystroke("l");
8394 cx.simulate_keystroke("o");
8395 cx.assert_editor_state("editor.cloˇ");
8396 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8397 cx.update_editor(|editor, cx| {
8398 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8399 });
8400 handle_completion_request(
8401 &mut cx,
8402 "editor.<clo|>",
8403 vec!["close", "clobber"],
8404 counter.clone(),
8405 )
8406 .await;
8407 cx.condition(|editor, _| editor.context_menu_visible())
8408 .await;
8409 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8410
8411 let apply_additional_edits = cx.update_editor(|editor, cx| {
8412 editor
8413 .confirm_completion(&ConfirmCompletion::default(), cx)
8414 .unwrap()
8415 });
8416 cx.assert_editor_state("editor.closeˇ");
8417 handle_resolve_completion_request(&mut cx, None).await;
8418 apply_additional_edits.await.unwrap();
8419}
8420
8421#[gpui::test]
8422async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8423 init_test(cx, |_| {});
8424 let mut cx = EditorLspTestContext::new_rust(
8425 lsp::ServerCapabilities {
8426 completion_provider: Some(lsp::CompletionOptions {
8427 trigger_characters: Some(vec![".".to_string()]),
8428 ..Default::default()
8429 }),
8430 ..Default::default()
8431 },
8432 cx,
8433 )
8434 .await;
8435 cx.lsp
8436 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8437 Ok(Some(lsp::CompletionResponse::Array(vec![
8438 lsp::CompletionItem {
8439 label: "first".into(),
8440 ..Default::default()
8441 },
8442 lsp::CompletionItem {
8443 label: "last".into(),
8444 ..Default::default()
8445 },
8446 ])))
8447 });
8448 cx.set_state("variableˇ");
8449 cx.simulate_keystroke(".");
8450 cx.executor().run_until_parked();
8451
8452 cx.update_editor(|editor, _| {
8453 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8454 assert_eq!(
8455 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8456 &["first", "last"]
8457 );
8458 } else {
8459 panic!("expected completion menu to be open");
8460 }
8461 });
8462
8463 cx.update_editor(|editor, cx| {
8464 editor.move_page_down(&MovePageDown::default(), cx);
8465 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8466 assert!(
8467 menu.selected_item == 1,
8468 "expected PageDown to select the last item from the context menu"
8469 );
8470 } else {
8471 panic!("expected completion menu to stay open after PageDown");
8472 }
8473 });
8474
8475 cx.update_editor(|editor, cx| {
8476 editor.move_page_up(&MovePageUp::default(), cx);
8477 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8478 assert!(
8479 menu.selected_item == 0,
8480 "expected PageUp to select the first item from the context menu"
8481 );
8482 } else {
8483 panic!("expected completion menu to stay open after PageUp");
8484 }
8485 });
8486}
8487
8488#[gpui::test]
8489async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8490 init_test(cx, |_| {});
8491 let mut cx = EditorLspTestContext::new_rust(
8492 lsp::ServerCapabilities {
8493 completion_provider: Some(lsp::CompletionOptions {
8494 trigger_characters: Some(vec![".".to_string()]),
8495 ..Default::default()
8496 }),
8497 ..Default::default()
8498 },
8499 cx,
8500 )
8501 .await;
8502 cx.lsp
8503 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8504 Ok(Some(lsp::CompletionResponse::Array(vec![
8505 lsp::CompletionItem {
8506 label: "Range".into(),
8507 sort_text: Some("a".into()),
8508 ..Default::default()
8509 },
8510 lsp::CompletionItem {
8511 label: "r".into(),
8512 sort_text: Some("b".into()),
8513 ..Default::default()
8514 },
8515 lsp::CompletionItem {
8516 label: "ret".into(),
8517 sort_text: Some("c".into()),
8518 ..Default::default()
8519 },
8520 lsp::CompletionItem {
8521 label: "return".into(),
8522 sort_text: Some("d".into()),
8523 ..Default::default()
8524 },
8525 lsp::CompletionItem {
8526 label: "slice".into(),
8527 sort_text: Some("d".into()),
8528 ..Default::default()
8529 },
8530 ])))
8531 });
8532 cx.set_state("rˇ");
8533 cx.executor().run_until_parked();
8534 cx.update_editor(|editor, cx| {
8535 editor.show_completions(
8536 &ShowCompletions {
8537 trigger: Some("r".into()),
8538 },
8539 cx,
8540 );
8541 });
8542 cx.executor().run_until_parked();
8543
8544 cx.update_editor(|editor, _| {
8545 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8546 assert_eq!(
8547 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8548 &["r", "ret", "Range", "return"]
8549 );
8550 } else {
8551 panic!("expected completion menu to be open");
8552 }
8553 });
8554}
8555
8556#[gpui::test]
8557async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8558 init_test(cx, |_| {});
8559
8560 let mut cx = EditorLspTestContext::new_rust(
8561 lsp::ServerCapabilities {
8562 completion_provider: Some(lsp::CompletionOptions {
8563 trigger_characters: Some(vec![".".to_string()]),
8564 resolve_provider: Some(true),
8565 ..Default::default()
8566 }),
8567 ..Default::default()
8568 },
8569 cx,
8570 )
8571 .await;
8572
8573 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8574 cx.simulate_keystroke(".");
8575 let completion_item = lsp::CompletionItem {
8576 label: "Some".into(),
8577 kind: Some(lsp::CompletionItemKind::SNIPPET),
8578 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8579 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8580 kind: lsp::MarkupKind::Markdown,
8581 value: "```rust\nSome(2)\n```".to_string(),
8582 })),
8583 deprecated: Some(false),
8584 sort_text: Some("Some".to_string()),
8585 filter_text: Some("Some".to_string()),
8586 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8587 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8588 range: lsp::Range {
8589 start: lsp::Position {
8590 line: 0,
8591 character: 22,
8592 },
8593 end: lsp::Position {
8594 line: 0,
8595 character: 22,
8596 },
8597 },
8598 new_text: "Some(2)".to_string(),
8599 })),
8600 additional_text_edits: Some(vec![lsp::TextEdit {
8601 range: lsp::Range {
8602 start: lsp::Position {
8603 line: 0,
8604 character: 20,
8605 },
8606 end: lsp::Position {
8607 line: 0,
8608 character: 22,
8609 },
8610 },
8611 new_text: "".to_string(),
8612 }]),
8613 ..Default::default()
8614 };
8615
8616 let closure_completion_item = completion_item.clone();
8617 let counter = Arc::new(AtomicUsize::new(0));
8618 let counter_clone = counter.clone();
8619 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8620 let task_completion_item = closure_completion_item.clone();
8621 counter_clone.fetch_add(1, atomic::Ordering::Release);
8622 async move {
8623 Ok(Some(lsp::CompletionResponse::Array(vec![
8624 task_completion_item,
8625 ])))
8626 }
8627 });
8628
8629 cx.condition(|editor, _| editor.context_menu_visible())
8630 .await;
8631 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8632 assert!(request.next().await.is_some());
8633 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8634
8635 cx.simulate_keystroke("S");
8636 cx.simulate_keystroke("o");
8637 cx.simulate_keystroke("m");
8638 cx.condition(|editor, _| editor.context_menu_visible())
8639 .await;
8640 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8641 assert!(request.next().await.is_some());
8642 assert!(request.next().await.is_some());
8643 assert!(request.next().await.is_some());
8644 request.close();
8645 assert!(request.next().await.is_none());
8646 assert_eq!(
8647 counter.load(atomic::Ordering::Acquire),
8648 4,
8649 "With the completions menu open, only one LSP request should happen per input"
8650 );
8651}
8652
8653#[gpui::test]
8654async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8655 init_test(cx, |_| {});
8656 let mut cx = EditorTestContext::new(cx).await;
8657 let language = Arc::new(Language::new(
8658 LanguageConfig {
8659 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8660 ..Default::default()
8661 },
8662 Some(tree_sitter_rust::LANGUAGE.into()),
8663 ));
8664 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8665
8666 // If multiple selections intersect a line, the line is only toggled once.
8667 cx.set_state(indoc! {"
8668 fn a() {
8669 «//b();
8670 ˇ»// «c();
8671 //ˇ» d();
8672 }
8673 "});
8674
8675 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8676
8677 cx.assert_editor_state(indoc! {"
8678 fn a() {
8679 «b();
8680 c();
8681 ˇ» d();
8682 }
8683 "});
8684
8685 // The comment prefix is inserted at the same column for every line in a
8686 // selection.
8687 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8688
8689 cx.assert_editor_state(indoc! {"
8690 fn a() {
8691 // «b();
8692 // c();
8693 ˇ»// d();
8694 }
8695 "});
8696
8697 // If a selection ends at the beginning of a line, that line is not toggled.
8698 cx.set_selections_state(indoc! {"
8699 fn a() {
8700 // b();
8701 «// c();
8702 ˇ» // d();
8703 }
8704 "});
8705
8706 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8707
8708 cx.assert_editor_state(indoc! {"
8709 fn a() {
8710 // b();
8711 «c();
8712 ˇ» // d();
8713 }
8714 "});
8715
8716 // If a selection span a single line and is empty, the line is toggled.
8717 cx.set_state(indoc! {"
8718 fn a() {
8719 a();
8720 b();
8721 ˇ
8722 }
8723 "});
8724
8725 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8726
8727 cx.assert_editor_state(indoc! {"
8728 fn a() {
8729 a();
8730 b();
8731 //•ˇ
8732 }
8733 "});
8734
8735 // If a selection span multiple lines, empty lines are not toggled.
8736 cx.set_state(indoc! {"
8737 fn a() {
8738 «a();
8739
8740 c();ˇ»
8741 }
8742 "});
8743
8744 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8745
8746 cx.assert_editor_state(indoc! {"
8747 fn a() {
8748 // «a();
8749
8750 // c();ˇ»
8751 }
8752 "});
8753
8754 // If a selection includes multiple comment prefixes, all lines are uncommented.
8755 cx.set_state(indoc! {"
8756 fn a() {
8757 «// a();
8758 /// b();
8759 //! c();ˇ»
8760 }
8761 "});
8762
8763 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8764
8765 cx.assert_editor_state(indoc! {"
8766 fn a() {
8767 «a();
8768 b();
8769 c();ˇ»
8770 }
8771 "});
8772}
8773
8774#[gpui::test]
8775async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8776 init_test(cx, |_| {});
8777 let mut cx = EditorTestContext::new(cx).await;
8778 let language = Arc::new(Language::new(
8779 LanguageConfig {
8780 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8781 ..Default::default()
8782 },
8783 Some(tree_sitter_rust::LANGUAGE.into()),
8784 ));
8785 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8786
8787 let toggle_comments = &ToggleComments {
8788 advance_downwards: false,
8789 ignore_indent: true,
8790 };
8791
8792 // If multiple selections intersect a line, the line is only toggled once.
8793 cx.set_state(indoc! {"
8794 fn a() {
8795 // «b();
8796 // c();
8797 // ˇ» d();
8798 }
8799 "});
8800
8801 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8802
8803 cx.assert_editor_state(indoc! {"
8804 fn a() {
8805 «b();
8806 c();
8807 ˇ» d();
8808 }
8809 "});
8810
8811 // The comment prefix is inserted at the beginning of each line
8812 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8813
8814 cx.assert_editor_state(indoc! {"
8815 fn a() {
8816 // «b();
8817 // c();
8818 // ˇ» d();
8819 }
8820 "});
8821
8822 // If a selection ends at the beginning of a line, that line is not toggled.
8823 cx.set_selections_state(indoc! {"
8824 fn a() {
8825 // b();
8826 // «c();
8827 ˇ»// d();
8828 }
8829 "});
8830
8831 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8832
8833 cx.assert_editor_state(indoc! {"
8834 fn a() {
8835 // b();
8836 «c();
8837 ˇ»// d();
8838 }
8839 "});
8840
8841 // If a selection span a single line and is empty, the line is toggled.
8842 cx.set_state(indoc! {"
8843 fn a() {
8844 a();
8845 b();
8846 ˇ
8847 }
8848 "});
8849
8850 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8851
8852 cx.assert_editor_state(indoc! {"
8853 fn a() {
8854 a();
8855 b();
8856 //ˇ
8857 }
8858 "});
8859
8860 // If a selection span multiple lines, empty lines are not toggled.
8861 cx.set_state(indoc! {"
8862 fn a() {
8863 «a();
8864
8865 c();ˇ»
8866 }
8867 "});
8868
8869 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8870
8871 cx.assert_editor_state(indoc! {"
8872 fn a() {
8873 // «a();
8874
8875 // c();ˇ»
8876 }
8877 "});
8878
8879 // If a selection includes multiple comment prefixes, all lines are uncommented.
8880 cx.set_state(indoc! {"
8881 fn a() {
8882 // «a();
8883 /// b();
8884 //! c();ˇ»
8885 }
8886 "});
8887
8888 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8889
8890 cx.assert_editor_state(indoc! {"
8891 fn a() {
8892 «a();
8893 b();
8894 c();ˇ»
8895 }
8896 "});
8897}
8898
8899#[gpui::test]
8900async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8901 init_test(cx, |_| {});
8902
8903 let language = Arc::new(Language::new(
8904 LanguageConfig {
8905 line_comments: vec!["// ".into()],
8906 ..Default::default()
8907 },
8908 Some(tree_sitter_rust::LANGUAGE.into()),
8909 ));
8910
8911 let mut cx = EditorTestContext::new(cx).await;
8912
8913 cx.language_registry().add(language.clone());
8914 cx.update_buffer(|buffer, cx| {
8915 buffer.set_language(Some(language), cx);
8916 });
8917
8918 let toggle_comments = &ToggleComments {
8919 advance_downwards: true,
8920 ignore_indent: false,
8921 };
8922
8923 // Single cursor on one line -> advance
8924 // Cursor moves horizontally 3 characters as well on non-blank line
8925 cx.set_state(indoc!(
8926 "fn a() {
8927 ˇdog();
8928 cat();
8929 }"
8930 ));
8931 cx.update_editor(|editor, cx| {
8932 editor.toggle_comments(toggle_comments, cx);
8933 });
8934 cx.assert_editor_state(indoc!(
8935 "fn a() {
8936 // dog();
8937 catˇ();
8938 }"
8939 ));
8940
8941 // Single selection on one line -> don't advance
8942 cx.set_state(indoc!(
8943 "fn a() {
8944 «dog()ˇ»;
8945 cat();
8946 }"
8947 ));
8948 cx.update_editor(|editor, cx| {
8949 editor.toggle_comments(toggle_comments, cx);
8950 });
8951 cx.assert_editor_state(indoc!(
8952 "fn a() {
8953 // «dog()ˇ»;
8954 cat();
8955 }"
8956 ));
8957
8958 // Multiple cursors on one line -> advance
8959 cx.set_state(indoc!(
8960 "fn a() {
8961 ˇdˇog();
8962 cat();
8963 }"
8964 ));
8965 cx.update_editor(|editor, cx| {
8966 editor.toggle_comments(toggle_comments, cx);
8967 });
8968 cx.assert_editor_state(indoc!(
8969 "fn a() {
8970 // dog();
8971 catˇ(ˇ);
8972 }"
8973 ));
8974
8975 // Multiple cursors on one line, with selection -> don't advance
8976 cx.set_state(indoc!(
8977 "fn a() {
8978 ˇdˇog«()ˇ»;
8979 cat();
8980 }"
8981 ));
8982 cx.update_editor(|editor, cx| {
8983 editor.toggle_comments(toggle_comments, cx);
8984 });
8985 cx.assert_editor_state(indoc!(
8986 "fn a() {
8987 // ˇdˇog«()ˇ»;
8988 cat();
8989 }"
8990 ));
8991
8992 // Single cursor on one line -> advance
8993 // Cursor moves to column 0 on blank line
8994 cx.set_state(indoc!(
8995 "fn a() {
8996 ˇdog();
8997
8998 cat();
8999 }"
9000 ));
9001 cx.update_editor(|editor, cx| {
9002 editor.toggle_comments(toggle_comments, cx);
9003 });
9004 cx.assert_editor_state(indoc!(
9005 "fn a() {
9006 // dog();
9007 ˇ
9008 cat();
9009 }"
9010 ));
9011
9012 // Single cursor on one line -> advance
9013 // Cursor starts and ends at column 0
9014 cx.set_state(indoc!(
9015 "fn a() {
9016 ˇ dog();
9017 cat();
9018 }"
9019 ));
9020 cx.update_editor(|editor, cx| {
9021 editor.toggle_comments(toggle_comments, cx);
9022 });
9023 cx.assert_editor_state(indoc!(
9024 "fn a() {
9025 // dog();
9026 ˇ cat();
9027 }"
9028 ));
9029}
9030
9031#[gpui::test]
9032async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9033 init_test(cx, |_| {});
9034
9035 let mut cx = EditorTestContext::new(cx).await;
9036
9037 let html_language = Arc::new(
9038 Language::new(
9039 LanguageConfig {
9040 name: "HTML".into(),
9041 block_comment: Some(("<!-- ".into(), " -->".into())),
9042 ..Default::default()
9043 },
9044 Some(tree_sitter_html::language()),
9045 )
9046 .with_injection_query(
9047 r#"
9048 (script_element
9049 (raw_text) @content
9050 (#set! "language" "javascript"))
9051 "#,
9052 )
9053 .unwrap(),
9054 );
9055
9056 let javascript_language = Arc::new(Language::new(
9057 LanguageConfig {
9058 name: "JavaScript".into(),
9059 line_comments: vec!["// ".into()],
9060 ..Default::default()
9061 },
9062 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9063 ));
9064
9065 cx.language_registry().add(html_language.clone());
9066 cx.language_registry().add(javascript_language.clone());
9067 cx.update_buffer(|buffer, cx| {
9068 buffer.set_language(Some(html_language), cx);
9069 });
9070
9071 // Toggle comments for empty selections
9072 cx.set_state(
9073 &r#"
9074 <p>A</p>ˇ
9075 <p>B</p>ˇ
9076 <p>C</p>ˇ
9077 "#
9078 .unindent(),
9079 );
9080 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9081 cx.assert_editor_state(
9082 &r#"
9083 <!-- <p>A</p>ˇ -->
9084 <!-- <p>B</p>ˇ -->
9085 <!-- <p>C</p>ˇ -->
9086 "#
9087 .unindent(),
9088 );
9089 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9090 cx.assert_editor_state(
9091 &r#"
9092 <p>A</p>ˇ
9093 <p>B</p>ˇ
9094 <p>C</p>ˇ
9095 "#
9096 .unindent(),
9097 );
9098
9099 // Toggle comments for mixture of empty and non-empty selections, where
9100 // multiple selections occupy a given line.
9101 cx.set_state(
9102 &r#"
9103 <p>A«</p>
9104 <p>ˇ»B</p>ˇ
9105 <p>C«</p>
9106 <p>ˇ»D</p>ˇ
9107 "#
9108 .unindent(),
9109 );
9110
9111 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9112 cx.assert_editor_state(
9113 &r#"
9114 <!-- <p>A«</p>
9115 <p>ˇ»B</p>ˇ -->
9116 <!-- <p>C«</p>
9117 <p>ˇ»D</p>ˇ -->
9118 "#
9119 .unindent(),
9120 );
9121 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9122 cx.assert_editor_state(
9123 &r#"
9124 <p>A«</p>
9125 <p>ˇ»B</p>ˇ
9126 <p>C«</p>
9127 <p>ˇ»D</p>ˇ
9128 "#
9129 .unindent(),
9130 );
9131
9132 // Toggle comments when different languages are active for different
9133 // selections.
9134 cx.set_state(
9135 &r#"
9136 ˇ<script>
9137 ˇvar x = new Y();
9138 ˇ</script>
9139 "#
9140 .unindent(),
9141 );
9142 cx.executor().run_until_parked();
9143 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9144 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9145 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9146 cx.assert_editor_state(
9147 &r#"
9148 <!-- ˇ<script> -->
9149 // ˇvar x = new Y();
9150 // ˇ</script>
9151 "#
9152 .unindent(),
9153 );
9154}
9155
9156#[gpui::test]
9157fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9158 init_test(cx, |_| {});
9159
9160 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9161 let multibuffer = cx.new_model(|cx| {
9162 let mut multibuffer = MultiBuffer::new(ReadWrite);
9163 multibuffer.push_excerpts(
9164 buffer.clone(),
9165 [
9166 ExcerptRange {
9167 context: Point::new(0, 0)..Point::new(0, 4),
9168 primary: None,
9169 },
9170 ExcerptRange {
9171 context: Point::new(1, 0)..Point::new(1, 4),
9172 primary: None,
9173 },
9174 ],
9175 cx,
9176 );
9177 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9178 multibuffer
9179 });
9180
9181 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9182 view.update(cx, |view, cx| {
9183 assert_eq!(view.text(cx), "aaaa\nbbbb");
9184 view.change_selections(None, cx, |s| {
9185 s.select_ranges([
9186 Point::new(0, 0)..Point::new(0, 0),
9187 Point::new(1, 0)..Point::new(1, 0),
9188 ])
9189 });
9190
9191 view.handle_input("X", cx);
9192 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9193 assert_eq!(
9194 view.selections.ranges(cx),
9195 [
9196 Point::new(0, 1)..Point::new(0, 1),
9197 Point::new(1, 1)..Point::new(1, 1),
9198 ]
9199 );
9200
9201 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9202 view.change_selections(None, cx, |s| {
9203 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9204 });
9205 view.backspace(&Default::default(), cx);
9206 assert_eq!(view.text(cx), "Xa\nbbb");
9207 assert_eq!(
9208 view.selections.ranges(cx),
9209 [Point::new(1, 0)..Point::new(1, 0)]
9210 );
9211
9212 view.change_selections(None, cx, |s| {
9213 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9214 });
9215 view.backspace(&Default::default(), cx);
9216 assert_eq!(view.text(cx), "X\nbb");
9217 assert_eq!(
9218 view.selections.ranges(cx),
9219 [Point::new(0, 1)..Point::new(0, 1)]
9220 );
9221 });
9222}
9223
9224#[gpui::test]
9225fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9226 init_test(cx, |_| {});
9227
9228 let markers = vec![('[', ']').into(), ('(', ')').into()];
9229 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9230 indoc! {"
9231 [aaaa
9232 (bbbb]
9233 cccc)",
9234 },
9235 markers.clone(),
9236 );
9237 let excerpt_ranges = markers.into_iter().map(|marker| {
9238 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9239 ExcerptRange {
9240 context,
9241 primary: None,
9242 }
9243 });
9244 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9245 let multibuffer = cx.new_model(|cx| {
9246 let mut multibuffer = MultiBuffer::new(ReadWrite);
9247 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9248 multibuffer
9249 });
9250
9251 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9252 view.update(cx, |view, cx| {
9253 let (expected_text, selection_ranges) = marked_text_ranges(
9254 indoc! {"
9255 aaaa
9256 bˇbbb
9257 bˇbbˇb
9258 cccc"
9259 },
9260 true,
9261 );
9262 assert_eq!(view.text(cx), expected_text);
9263 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9264
9265 view.handle_input("X", cx);
9266
9267 let (expected_text, expected_selections) = marked_text_ranges(
9268 indoc! {"
9269 aaaa
9270 bXˇbbXb
9271 bXˇbbXˇb
9272 cccc"
9273 },
9274 false,
9275 );
9276 assert_eq!(view.text(cx), expected_text);
9277 assert_eq!(view.selections.ranges(cx), expected_selections);
9278
9279 view.newline(&Newline, cx);
9280 let (expected_text, expected_selections) = marked_text_ranges(
9281 indoc! {"
9282 aaaa
9283 bX
9284 ˇbbX
9285 b
9286 bX
9287 ˇbbX
9288 ˇb
9289 cccc"
9290 },
9291 false,
9292 );
9293 assert_eq!(view.text(cx), expected_text);
9294 assert_eq!(view.selections.ranges(cx), expected_selections);
9295 });
9296}
9297
9298#[gpui::test]
9299fn test_refresh_selections(cx: &mut TestAppContext) {
9300 init_test(cx, |_| {});
9301
9302 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9303 let mut excerpt1_id = None;
9304 let multibuffer = cx.new_model(|cx| {
9305 let mut multibuffer = MultiBuffer::new(ReadWrite);
9306 excerpt1_id = multibuffer
9307 .push_excerpts(
9308 buffer.clone(),
9309 [
9310 ExcerptRange {
9311 context: Point::new(0, 0)..Point::new(1, 4),
9312 primary: None,
9313 },
9314 ExcerptRange {
9315 context: Point::new(1, 0)..Point::new(2, 4),
9316 primary: None,
9317 },
9318 ],
9319 cx,
9320 )
9321 .into_iter()
9322 .next();
9323 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9324 multibuffer
9325 });
9326
9327 let editor = cx.add_window(|cx| {
9328 let mut editor = build_editor(multibuffer.clone(), cx);
9329 let snapshot = editor.snapshot(cx);
9330 editor.change_selections(None, cx, |s| {
9331 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9332 });
9333 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9334 assert_eq!(
9335 editor.selections.ranges(cx),
9336 [
9337 Point::new(1, 3)..Point::new(1, 3),
9338 Point::new(2, 1)..Point::new(2, 1),
9339 ]
9340 );
9341 editor
9342 });
9343
9344 // Refreshing selections is a no-op when excerpts haven't changed.
9345 _ = editor.update(cx, |editor, cx| {
9346 editor.change_selections(None, cx, |s| s.refresh());
9347 assert_eq!(
9348 editor.selections.ranges(cx),
9349 [
9350 Point::new(1, 3)..Point::new(1, 3),
9351 Point::new(2, 1)..Point::new(2, 1),
9352 ]
9353 );
9354 });
9355
9356 multibuffer.update(cx, |multibuffer, cx| {
9357 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9358 });
9359 _ = editor.update(cx, |editor, cx| {
9360 // Removing an excerpt causes the first selection to become degenerate.
9361 assert_eq!(
9362 editor.selections.ranges(cx),
9363 [
9364 Point::new(0, 0)..Point::new(0, 0),
9365 Point::new(0, 1)..Point::new(0, 1)
9366 ]
9367 );
9368
9369 // Refreshing selections will relocate the first selection to the original buffer
9370 // location.
9371 editor.change_selections(None, cx, |s| s.refresh());
9372 assert_eq!(
9373 editor.selections.ranges(cx),
9374 [
9375 Point::new(0, 1)..Point::new(0, 1),
9376 Point::new(0, 3)..Point::new(0, 3)
9377 ]
9378 );
9379 assert!(editor.selections.pending_anchor().is_some());
9380 });
9381}
9382
9383#[gpui::test]
9384fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9385 init_test(cx, |_| {});
9386
9387 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9388 let mut excerpt1_id = None;
9389 let multibuffer = cx.new_model(|cx| {
9390 let mut multibuffer = MultiBuffer::new(ReadWrite);
9391 excerpt1_id = multibuffer
9392 .push_excerpts(
9393 buffer.clone(),
9394 [
9395 ExcerptRange {
9396 context: Point::new(0, 0)..Point::new(1, 4),
9397 primary: None,
9398 },
9399 ExcerptRange {
9400 context: Point::new(1, 0)..Point::new(2, 4),
9401 primary: None,
9402 },
9403 ],
9404 cx,
9405 )
9406 .into_iter()
9407 .next();
9408 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9409 multibuffer
9410 });
9411
9412 let editor = cx.add_window(|cx| {
9413 let mut editor = build_editor(multibuffer.clone(), cx);
9414 let snapshot = editor.snapshot(cx);
9415 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9416 assert_eq!(
9417 editor.selections.ranges(cx),
9418 [Point::new(1, 3)..Point::new(1, 3)]
9419 );
9420 editor
9421 });
9422
9423 multibuffer.update(cx, |multibuffer, cx| {
9424 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9425 });
9426 _ = editor.update(cx, |editor, cx| {
9427 assert_eq!(
9428 editor.selections.ranges(cx),
9429 [Point::new(0, 0)..Point::new(0, 0)]
9430 );
9431
9432 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9433 editor.change_selections(None, cx, |s| s.refresh());
9434 assert_eq!(
9435 editor.selections.ranges(cx),
9436 [Point::new(0, 3)..Point::new(0, 3)]
9437 );
9438 assert!(editor.selections.pending_anchor().is_some());
9439 });
9440}
9441
9442#[gpui::test]
9443async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9444 init_test(cx, |_| {});
9445
9446 let language = Arc::new(
9447 Language::new(
9448 LanguageConfig {
9449 brackets: BracketPairConfig {
9450 pairs: vec![
9451 BracketPair {
9452 start: "{".to_string(),
9453 end: "}".to_string(),
9454 close: true,
9455 surround: true,
9456 newline: true,
9457 },
9458 BracketPair {
9459 start: "/* ".to_string(),
9460 end: " */".to_string(),
9461 close: true,
9462 surround: true,
9463 newline: true,
9464 },
9465 ],
9466 ..Default::default()
9467 },
9468 ..Default::default()
9469 },
9470 Some(tree_sitter_rust::LANGUAGE.into()),
9471 )
9472 .with_indents_query("")
9473 .unwrap(),
9474 );
9475
9476 let text = concat!(
9477 "{ }\n", //
9478 " x\n", //
9479 " /* */\n", //
9480 "x\n", //
9481 "{{} }\n", //
9482 );
9483
9484 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9485 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9486 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9487 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9488 .await;
9489
9490 view.update(cx, |view, cx| {
9491 view.change_selections(None, cx, |s| {
9492 s.select_display_ranges([
9493 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9494 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9495 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9496 ])
9497 });
9498 view.newline(&Newline, cx);
9499
9500 assert_eq!(
9501 view.buffer().read(cx).read(cx).text(),
9502 concat!(
9503 "{ \n", // Suppress rustfmt
9504 "\n", //
9505 "}\n", //
9506 " x\n", //
9507 " /* \n", //
9508 " \n", //
9509 " */\n", //
9510 "x\n", //
9511 "{{} \n", //
9512 "}\n", //
9513 )
9514 );
9515 });
9516}
9517
9518#[gpui::test]
9519fn test_highlighted_ranges(cx: &mut TestAppContext) {
9520 init_test(cx, |_| {});
9521
9522 let editor = cx.add_window(|cx| {
9523 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9524 build_editor(buffer.clone(), cx)
9525 });
9526
9527 _ = editor.update(cx, |editor, cx| {
9528 struct Type1;
9529 struct Type2;
9530
9531 let buffer = editor.buffer.read(cx).snapshot(cx);
9532
9533 let anchor_range =
9534 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9535
9536 editor.highlight_background::<Type1>(
9537 &[
9538 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9539 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9540 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9541 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9542 ],
9543 |_| Hsla::red(),
9544 cx,
9545 );
9546 editor.highlight_background::<Type2>(
9547 &[
9548 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9549 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9550 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9551 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9552 ],
9553 |_| Hsla::green(),
9554 cx,
9555 );
9556
9557 let snapshot = editor.snapshot(cx);
9558 let mut highlighted_ranges = editor.background_highlights_in_range(
9559 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9560 &snapshot,
9561 cx.theme().colors(),
9562 );
9563 // Enforce a consistent ordering based on color without relying on the ordering of the
9564 // highlight's `TypeId` which is non-executor.
9565 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9566 assert_eq!(
9567 highlighted_ranges,
9568 &[
9569 (
9570 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9571 Hsla::red(),
9572 ),
9573 (
9574 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9575 Hsla::red(),
9576 ),
9577 (
9578 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9579 Hsla::green(),
9580 ),
9581 (
9582 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9583 Hsla::green(),
9584 ),
9585 ]
9586 );
9587 assert_eq!(
9588 editor.background_highlights_in_range(
9589 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9590 &snapshot,
9591 cx.theme().colors(),
9592 ),
9593 &[(
9594 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9595 Hsla::red(),
9596 )]
9597 );
9598 });
9599}
9600
9601#[gpui::test]
9602async fn test_following(cx: &mut gpui::TestAppContext) {
9603 init_test(cx, |_| {});
9604
9605 let fs = FakeFs::new(cx.executor());
9606 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9607
9608 let buffer = project.update(cx, |project, cx| {
9609 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9610 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9611 });
9612 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9613 let follower = cx.update(|cx| {
9614 cx.open_window(
9615 WindowOptions {
9616 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9617 gpui::Point::new(px(0.), px(0.)),
9618 gpui::Point::new(px(10.), px(80.)),
9619 ))),
9620 ..Default::default()
9621 },
9622 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9623 )
9624 .unwrap()
9625 });
9626
9627 let is_still_following = Rc::new(RefCell::new(true));
9628 let follower_edit_event_count = Rc::new(RefCell::new(0));
9629 let pending_update = Rc::new(RefCell::new(None));
9630 _ = follower.update(cx, {
9631 let update = pending_update.clone();
9632 let is_still_following = is_still_following.clone();
9633 let follower_edit_event_count = follower_edit_event_count.clone();
9634 |_, cx| {
9635 cx.subscribe(
9636 &leader.root_view(cx).unwrap(),
9637 move |_, leader, event, cx| {
9638 leader
9639 .read(cx)
9640 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9641 },
9642 )
9643 .detach();
9644
9645 cx.subscribe(
9646 &follower.root_view(cx).unwrap(),
9647 move |_, _, event: &EditorEvent, _cx| {
9648 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9649 *is_still_following.borrow_mut() = false;
9650 }
9651
9652 if let EditorEvent::BufferEdited = event {
9653 *follower_edit_event_count.borrow_mut() += 1;
9654 }
9655 },
9656 )
9657 .detach();
9658 }
9659 });
9660
9661 // Update the selections only
9662 _ = leader.update(cx, |leader, cx| {
9663 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9664 });
9665 follower
9666 .update(cx, |follower, cx| {
9667 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9668 })
9669 .unwrap()
9670 .await
9671 .unwrap();
9672 _ = follower.update(cx, |follower, cx| {
9673 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9674 });
9675 assert!(*is_still_following.borrow());
9676 assert_eq!(*follower_edit_event_count.borrow(), 0);
9677
9678 // Update the scroll position only
9679 _ = leader.update(cx, |leader, cx| {
9680 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9681 });
9682 follower
9683 .update(cx, |follower, cx| {
9684 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9685 })
9686 .unwrap()
9687 .await
9688 .unwrap();
9689 assert_eq!(
9690 follower
9691 .update(cx, |follower, cx| follower.scroll_position(cx))
9692 .unwrap(),
9693 gpui::Point::new(1.5, 3.5)
9694 );
9695 assert!(*is_still_following.borrow());
9696 assert_eq!(*follower_edit_event_count.borrow(), 0);
9697
9698 // Update the selections and scroll position. The follower's scroll position is updated
9699 // via autoscroll, not via the leader's exact scroll position.
9700 _ = leader.update(cx, |leader, cx| {
9701 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9702 leader.request_autoscroll(Autoscroll::newest(), cx);
9703 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9704 });
9705 follower
9706 .update(cx, |follower, cx| {
9707 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9708 })
9709 .unwrap()
9710 .await
9711 .unwrap();
9712 _ = follower.update(cx, |follower, cx| {
9713 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9714 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9715 });
9716 assert!(*is_still_following.borrow());
9717
9718 // Creating a pending selection that precedes another selection
9719 _ = leader.update(cx, |leader, cx| {
9720 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9721 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9722 });
9723 follower
9724 .update(cx, |follower, cx| {
9725 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9726 })
9727 .unwrap()
9728 .await
9729 .unwrap();
9730 _ = follower.update(cx, |follower, cx| {
9731 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9732 });
9733 assert!(*is_still_following.borrow());
9734
9735 // Extend the pending selection so that it surrounds another selection
9736 _ = leader.update(cx, |leader, cx| {
9737 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9738 });
9739 follower
9740 .update(cx, |follower, cx| {
9741 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9742 })
9743 .unwrap()
9744 .await
9745 .unwrap();
9746 _ = follower.update(cx, |follower, cx| {
9747 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9748 });
9749
9750 // Scrolling locally breaks the follow
9751 _ = follower.update(cx, |follower, cx| {
9752 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9753 follower.set_scroll_anchor(
9754 ScrollAnchor {
9755 anchor: top_anchor,
9756 offset: gpui::Point::new(0.0, 0.5),
9757 },
9758 cx,
9759 );
9760 });
9761 assert!(!(*is_still_following.borrow()));
9762}
9763
9764#[gpui::test]
9765async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9766 init_test(cx, |_| {});
9767
9768 let fs = FakeFs::new(cx.executor());
9769 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9770 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9771 let pane = workspace
9772 .update(cx, |workspace, _| workspace.active_pane().clone())
9773 .unwrap();
9774
9775 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9776
9777 let leader = pane.update(cx, |_, cx| {
9778 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9779 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9780 });
9781
9782 // Start following the editor when it has no excerpts.
9783 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9784 let follower_1 = cx
9785 .update_window(*workspace.deref(), |_, cx| {
9786 Editor::from_state_proto(
9787 workspace.root_view(cx).unwrap(),
9788 ViewId {
9789 creator: Default::default(),
9790 id: 0,
9791 },
9792 &mut state_message,
9793 cx,
9794 )
9795 })
9796 .unwrap()
9797 .unwrap()
9798 .await
9799 .unwrap();
9800
9801 let update_message = Rc::new(RefCell::new(None));
9802 follower_1.update(cx, {
9803 let update = update_message.clone();
9804 |_, cx| {
9805 cx.subscribe(&leader, move |_, leader, event, cx| {
9806 leader
9807 .read(cx)
9808 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9809 })
9810 .detach();
9811 }
9812 });
9813
9814 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9815 (
9816 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9817 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9818 )
9819 });
9820
9821 // Insert some excerpts.
9822 leader.update(cx, |leader, cx| {
9823 leader.buffer.update(cx, |multibuffer, cx| {
9824 let excerpt_ids = multibuffer.push_excerpts(
9825 buffer_1.clone(),
9826 [
9827 ExcerptRange {
9828 context: 1..6,
9829 primary: None,
9830 },
9831 ExcerptRange {
9832 context: 12..15,
9833 primary: None,
9834 },
9835 ExcerptRange {
9836 context: 0..3,
9837 primary: None,
9838 },
9839 ],
9840 cx,
9841 );
9842 multibuffer.insert_excerpts_after(
9843 excerpt_ids[0],
9844 buffer_2.clone(),
9845 [
9846 ExcerptRange {
9847 context: 8..12,
9848 primary: None,
9849 },
9850 ExcerptRange {
9851 context: 0..6,
9852 primary: None,
9853 },
9854 ],
9855 cx,
9856 );
9857 });
9858 });
9859
9860 // Apply the update of adding the excerpts.
9861 follower_1
9862 .update(cx, |follower, cx| {
9863 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9864 })
9865 .await
9866 .unwrap();
9867 assert_eq!(
9868 follower_1.update(cx, |editor, cx| editor.text(cx)),
9869 leader.update(cx, |editor, cx| editor.text(cx))
9870 );
9871 update_message.borrow_mut().take();
9872
9873 // Start following separately after it already has excerpts.
9874 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9875 let follower_2 = cx
9876 .update_window(*workspace.deref(), |_, cx| {
9877 Editor::from_state_proto(
9878 workspace.root_view(cx).unwrap().clone(),
9879 ViewId {
9880 creator: Default::default(),
9881 id: 0,
9882 },
9883 &mut state_message,
9884 cx,
9885 )
9886 })
9887 .unwrap()
9888 .unwrap()
9889 .await
9890 .unwrap();
9891 assert_eq!(
9892 follower_2.update(cx, |editor, cx| editor.text(cx)),
9893 leader.update(cx, |editor, cx| editor.text(cx))
9894 );
9895
9896 // Remove some excerpts.
9897 leader.update(cx, |leader, cx| {
9898 leader.buffer.update(cx, |multibuffer, cx| {
9899 let excerpt_ids = multibuffer.excerpt_ids();
9900 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9901 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9902 });
9903 });
9904
9905 // Apply the update of removing the excerpts.
9906 follower_1
9907 .update(cx, |follower, cx| {
9908 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9909 })
9910 .await
9911 .unwrap();
9912 follower_2
9913 .update(cx, |follower, cx| {
9914 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9915 })
9916 .await
9917 .unwrap();
9918 update_message.borrow_mut().take();
9919 assert_eq!(
9920 follower_1.update(cx, |editor, cx| editor.text(cx)),
9921 leader.update(cx, |editor, cx| editor.text(cx))
9922 );
9923}
9924
9925#[gpui::test]
9926async fn go_to_prev_overlapping_diagnostic(
9927 executor: BackgroundExecutor,
9928 cx: &mut gpui::TestAppContext,
9929) {
9930 init_test(cx, |_| {});
9931
9932 let mut cx = EditorTestContext::new(cx).await;
9933 let lsp_store =
9934 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9935
9936 cx.set_state(indoc! {"
9937 ˇfn func(abc def: i32) -> u32 {
9938 }
9939 "});
9940
9941 cx.update(|cx| {
9942 lsp_store.update(cx, |lsp_store, cx| {
9943 lsp_store
9944 .update_diagnostics(
9945 LanguageServerId(0),
9946 lsp::PublishDiagnosticsParams {
9947 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9948 version: None,
9949 diagnostics: vec![
9950 lsp::Diagnostic {
9951 range: lsp::Range::new(
9952 lsp::Position::new(0, 11),
9953 lsp::Position::new(0, 12),
9954 ),
9955 severity: Some(lsp::DiagnosticSeverity::ERROR),
9956 ..Default::default()
9957 },
9958 lsp::Diagnostic {
9959 range: lsp::Range::new(
9960 lsp::Position::new(0, 12),
9961 lsp::Position::new(0, 15),
9962 ),
9963 severity: Some(lsp::DiagnosticSeverity::ERROR),
9964 ..Default::default()
9965 },
9966 lsp::Diagnostic {
9967 range: lsp::Range::new(
9968 lsp::Position::new(0, 25),
9969 lsp::Position::new(0, 28),
9970 ),
9971 severity: Some(lsp::DiagnosticSeverity::ERROR),
9972 ..Default::default()
9973 },
9974 ],
9975 },
9976 &[],
9977 cx,
9978 )
9979 .unwrap()
9980 });
9981 });
9982
9983 executor.run_until_parked();
9984
9985 cx.update_editor(|editor, cx| {
9986 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9987 });
9988
9989 cx.assert_editor_state(indoc! {"
9990 fn func(abc def: i32) -> ˇu32 {
9991 }
9992 "});
9993
9994 cx.update_editor(|editor, cx| {
9995 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9996 });
9997
9998 cx.assert_editor_state(indoc! {"
9999 fn func(abc ˇdef: i32) -> u32 {
10000 }
10001 "});
10002
10003 cx.update_editor(|editor, cx| {
10004 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10005 });
10006
10007 cx.assert_editor_state(indoc! {"
10008 fn func(abcˇ def: i32) -> u32 {
10009 }
10010 "});
10011
10012 cx.update_editor(|editor, cx| {
10013 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10014 });
10015
10016 cx.assert_editor_state(indoc! {"
10017 fn func(abc def: i32) -> ˇu32 {
10018 }
10019 "});
10020}
10021
10022#[gpui::test]
10023async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10024 init_test(cx, |_| {});
10025
10026 let mut cx = EditorTestContext::new(cx).await;
10027
10028 cx.set_state(indoc! {"
10029 fn func(abˇc def: i32) -> u32 {
10030 }
10031 "});
10032 let lsp_store =
10033 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10034
10035 cx.update(|cx| {
10036 lsp_store.update(cx, |lsp_store, cx| {
10037 lsp_store.update_diagnostics(
10038 LanguageServerId(0),
10039 lsp::PublishDiagnosticsParams {
10040 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10041 version: None,
10042 diagnostics: vec![lsp::Diagnostic {
10043 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10044 severity: Some(lsp::DiagnosticSeverity::ERROR),
10045 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10046 ..Default::default()
10047 }],
10048 },
10049 &[],
10050 cx,
10051 )
10052 })
10053 }).unwrap();
10054 cx.run_until_parked();
10055 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10056 cx.run_until_parked();
10057 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10058}
10059
10060#[gpui::test]
10061async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10062 init_test(cx, |_| {});
10063
10064 let mut cx = EditorTestContext::new(cx).await;
10065
10066 let diff_base = r#"
10067 use some::mod;
10068
10069 const A: u32 = 42;
10070
10071 fn main() {
10072 println!("hello");
10073
10074 println!("world");
10075 }
10076 "#
10077 .unindent();
10078
10079 // Edits are modified, removed, modified, added
10080 cx.set_state(
10081 &r#"
10082 use some::modified;
10083
10084 ˇ
10085 fn main() {
10086 println!("hello there");
10087
10088 println!("around the");
10089 println!("world");
10090 }
10091 "#
10092 .unindent(),
10093 );
10094
10095 cx.set_diff_base(&diff_base);
10096 executor.run_until_parked();
10097
10098 cx.update_editor(|editor, cx| {
10099 //Wrap around the bottom of the buffer
10100 for _ in 0..3 {
10101 editor.go_to_next_hunk(&GoToHunk, cx);
10102 }
10103 });
10104
10105 cx.assert_editor_state(
10106 &r#"
10107 ˇuse some::modified;
10108
10109
10110 fn main() {
10111 println!("hello there");
10112
10113 println!("around the");
10114 println!("world");
10115 }
10116 "#
10117 .unindent(),
10118 );
10119
10120 cx.update_editor(|editor, cx| {
10121 //Wrap around the top of the buffer
10122 for _ in 0..2 {
10123 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10124 }
10125 });
10126
10127 cx.assert_editor_state(
10128 &r#"
10129 use some::modified;
10130
10131
10132 fn main() {
10133 ˇ println!("hello there");
10134
10135 println!("around the");
10136 println!("world");
10137 }
10138 "#
10139 .unindent(),
10140 );
10141
10142 cx.update_editor(|editor, cx| {
10143 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10144 });
10145
10146 cx.assert_editor_state(
10147 &r#"
10148 use some::modified;
10149
10150 ˇ
10151 fn main() {
10152 println!("hello there");
10153
10154 println!("around the");
10155 println!("world");
10156 }
10157 "#
10158 .unindent(),
10159 );
10160
10161 cx.update_editor(|editor, cx| {
10162 for _ in 0..3 {
10163 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10164 }
10165 });
10166
10167 cx.assert_editor_state(
10168 &r#"
10169 use some::modified;
10170
10171
10172 fn main() {
10173 ˇ println!("hello there");
10174
10175 println!("around the");
10176 println!("world");
10177 }
10178 "#
10179 .unindent(),
10180 );
10181
10182 cx.update_editor(|editor, cx| {
10183 editor.fold(&Fold, cx);
10184
10185 //Make sure that the fold only gets one hunk
10186 for _ in 0..4 {
10187 editor.go_to_next_hunk(&GoToHunk, cx);
10188 }
10189 });
10190
10191 cx.assert_editor_state(
10192 &r#"
10193 ˇuse some::modified;
10194
10195
10196 fn main() {
10197 println!("hello there");
10198
10199 println!("around the");
10200 println!("world");
10201 }
10202 "#
10203 .unindent(),
10204 );
10205}
10206
10207#[test]
10208fn test_split_words() {
10209 fn split(text: &str) -> Vec<&str> {
10210 split_words(text).collect()
10211 }
10212
10213 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10214 assert_eq!(split("hello_world"), &["hello_", "world"]);
10215 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10216 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10217 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10218 assert_eq!(split("helloworld"), &["helloworld"]);
10219
10220 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10221}
10222
10223#[gpui::test]
10224async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10225 init_test(cx, |_| {});
10226
10227 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10228 let mut assert = |before, after| {
10229 let _state_context = cx.set_state(before);
10230 cx.update_editor(|editor, cx| {
10231 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10232 });
10233 cx.assert_editor_state(after);
10234 };
10235
10236 // Outside bracket jumps to outside of matching bracket
10237 assert("console.logˇ(var);", "console.log(var)ˇ;");
10238 assert("console.log(var)ˇ;", "console.logˇ(var);");
10239
10240 // Inside bracket jumps to inside of matching bracket
10241 assert("console.log(ˇvar);", "console.log(varˇ);");
10242 assert("console.log(varˇ);", "console.log(ˇvar);");
10243
10244 // When outside a bracket and inside, favor jumping to the inside bracket
10245 assert(
10246 "console.log('foo', [1, 2, 3]ˇ);",
10247 "console.log(ˇ'foo', [1, 2, 3]);",
10248 );
10249 assert(
10250 "console.log(ˇ'foo', [1, 2, 3]);",
10251 "console.log('foo', [1, 2, 3]ˇ);",
10252 );
10253
10254 // Bias forward if two options are equally likely
10255 assert(
10256 "let result = curried_fun()ˇ();",
10257 "let result = curried_fun()()ˇ;",
10258 );
10259
10260 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10261 assert(
10262 indoc! {"
10263 function test() {
10264 console.log('test')ˇ
10265 }"},
10266 indoc! {"
10267 function test() {
10268 console.logˇ('test')
10269 }"},
10270 );
10271}
10272
10273#[gpui::test]
10274async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10275 init_test(cx, |_| {});
10276
10277 let fs = FakeFs::new(cx.executor());
10278 fs.insert_tree(
10279 "/a",
10280 json!({
10281 "main.rs": "fn main() { let a = 5; }",
10282 "other.rs": "// Test file",
10283 }),
10284 )
10285 .await;
10286 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10287
10288 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10289 language_registry.add(Arc::new(Language::new(
10290 LanguageConfig {
10291 name: "Rust".into(),
10292 matcher: LanguageMatcher {
10293 path_suffixes: vec!["rs".to_string()],
10294 ..Default::default()
10295 },
10296 brackets: BracketPairConfig {
10297 pairs: vec![BracketPair {
10298 start: "{".to_string(),
10299 end: "}".to_string(),
10300 close: true,
10301 surround: true,
10302 newline: true,
10303 }],
10304 disabled_scopes_by_bracket_ix: Vec::new(),
10305 },
10306 ..Default::default()
10307 },
10308 Some(tree_sitter_rust::LANGUAGE.into()),
10309 )));
10310 let mut fake_servers = language_registry.register_fake_lsp(
10311 "Rust",
10312 FakeLspAdapter {
10313 capabilities: lsp::ServerCapabilities {
10314 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10315 first_trigger_character: "{".to_string(),
10316 more_trigger_character: None,
10317 }),
10318 ..Default::default()
10319 },
10320 ..Default::default()
10321 },
10322 );
10323
10324 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10325
10326 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10327
10328 let worktree_id = workspace
10329 .update(cx, |workspace, cx| {
10330 workspace.project().update(cx, |project, cx| {
10331 project.worktrees(cx).next().unwrap().read(cx).id()
10332 })
10333 })
10334 .unwrap();
10335
10336 let buffer = project
10337 .update(cx, |project, cx| {
10338 project.open_local_buffer("/a/main.rs", cx)
10339 })
10340 .await
10341 .unwrap();
10342 let editor_handle = workspace
10343 .update(cx, |workspace, cx| {
10344 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10345 })
10346 .unwrap()
10347 .await
10348 .unwrap()
10349 .downcast::<Editor>()
10350 .unwrap();
10351
10352 cx.executor().start_waiting();
10353 let fake_server = fake_servers.next().await.unwrap();
10354
10355 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10356 assert_eq!(
10357 params.text_document_position.text_document.uri,
10358 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10359 );
10360 assert_eq!(
10361 params.text_document_position.position,
10362 lsp::Position::new(0, 21),
10363 );
10364
10365 Ok(Some(vec![lsp::TextEdit {
10366 new_text: "]".to_string(),
10367 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10368 }]))
10369 });
10370
10371 editor_handle.update(cx, |editor, cx| {
10372 editor.focus(cx);
10373 editor.change_selections(None, cx, |s| {
10374 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10375 });
10376 editor.handle_input("{", cx);
10377 });
10378
10379 cx.executor().run_until_parked();
10380
10381 buffer.update(cx, |buffer, _| {
10382 assert_eq!(
10383 buffer.text(),
10384 "fn main() { let a = {5}; }",
10385 "No extra braces from on type formatting should appear in the buffer"
10386 )
10387 });
10388}
10389
10390#[gpui::test]
10391async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10392 init_test(cx, |_| {});
10393
10394 let fs = FakeFs::new(cx.executor());
10395 fs.insert_tree(
10396 "/a",
10397 json!({
10398 "main.rs": "fn main() { let a = 5; }",
10399 "other.rs": "// Test file",
10400 }),
10401 )
10402 .await;
10403
10404 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10405
10406 let server_restarts = Arc::new(AtomicUsize::new(0));
10407 let closure_restarts = Arc::clone(&server_restarts);
10408 let language_server_name = "test language server";
10409 let language_name: LanguageName = "Rust".into();
10410
10411 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10412 language_registry.add(Arc::new(Language::new(
10413 LanguageConfig {
10414 name: language_name.clone(),
10415 matcher: LanguageMatcher {
10416 path_suffixes: vec!["rs".to_string()],
10417 ..Default::default()
10418 },
10419 ..Default::default()
10420 },
10421 Some(tree_sitter_rust::LANGUAGE.into()),
10422 )));
10423 let mut fake_servers = language_registry.register_fake_lsp(
10424 "Rust",
10425 FakeLspAdapter {
10426 name: language_server_name,
10427 initialization_options: Some(json!({
10428 "testOptionValue": true
10429 })),
10430 initializer: Some(Box::new(move |fake_server| {
10431 let task_restarts = Arc::clone(&closure_restarts);
10432 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10433 task_restarts.fetch_add(1, atomic::Ordering::Release);
10434 futures::future::ready(Ok(()))
10435 });
10436 })),
10437 ..Default::default()
10438 },
10439 );
10440
10441 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10442 let _buffer = project
10443 .update(cx, |project, cx| {
10444 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10445 })
10446 .await
10447 .unwrap();
10448 let _fake_server = fake_servers.next().await.unwrap();
10449 update_test_language_settings(cx, |language_settings| {
10450 language_settings.languages.insert(
10451 language_name.clone(),
10452 LanguageSettingsContent {
10453 tab_size: NonZeroU32::new(8),
10454 ..Default::default()
10455 },
10456 );
10457 });
10458 cx.executor().run_until_parked();
10459 assert_eq!(
10460 server_restarts.load(atomic::Ordering::Acquire),
10461 0,
10462 "Should not restart LSP server on an unrelated change"
10463 );
10464
10465 update_test_project_settings(cx, |project_settings| {
10466 project_settings.lsp.insert(
10467 "Some other server name".into(),
10468 LspSettings {
10469 binary: None,
10470 settings: None,
10471 initialization_options: Some(json!({
10472 "some other init value": false
10473 })),
10474 },
10475 );
10476 });
10477 cx.executor().run_until_parked();
10478 assert_eq!(
10479 server_restarts.load(atomic::Ordering::Acquire),
10480 0,
10481 "Should not restart LSP server on an unrelated LSP settings change"
10482 );
10483
10484 update_test_project_settings(cx, |project_settings| {
10485 project_settings.lsp.insert(
10486 language_server_name.into(),
10487 LspSettings {
10488 binary: None,
10489 settings: None,
10490 initialization_options: Some(json!({
10491 "anotherInitValue": false
10492 })),
10493 },
10494 );
10495 });
10496 cx.executor().run_until_parked();
10497 assert_eq!(
10498 server_restarts.load(atomic::Ordering::Acquire),
10499 1,
10500 "Should restart LSP server on a related LSP settings change"
10501 );
10502
10503 update_test_project_settings(cx, |project_settings| {
10504 project_settings.lsp.insert(
10505 language_server_name.into(),
10506 LspSettings {
10507 binary: None,
10508 settings: None,
10509 initialization_options: Some(json!({
10510 "anotherInitValue": false
10511 })),
10512 },
10513 );
10514 });
10515 cx.executor().run_until_parked();
10516 assert_eq!(
10517 server_restarts.load(atomic::Ordering::Acquire),
10518 1,
10519 "Should not restart LSP server on a related LSP settings change that is the same"
10520 );
10521
10522 update_test_project_settings(cx, |project_settings| {
10523 project_settings.lsp.insert(
10524 language_server_name.into(),
10525 LspSettings {
10526 binary: None,
10527 settings: None,
10528 initialization_options: None,
10529 },
10530 );
10531 });
10532 cx.executor().run_until_parked();
10533 assert_eq!(
10534 server_restarts.load(atomic::Ordering::Acquire),
10535 2,
10536 "Should restart LSP server on another related LSP settings change"
10537 );
10538}
10539
10540#[gpui::test]
10541async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10542 init_test(cx, |_| {});
10543
10544 let mut cx = EditorLspTestContext::new_rust(
10545 lsp::ServerCapabilities {
10546 completion_provider: Some(lsp::CompletionOptions {
10547 trigger_characters: Some(vec![".".to_string()]),
10548 resolve_provider: Some(true),
10549 ..Default::default()
10550 }),
10551 ..Default::default()
10552 },
10553 cx,
10554 )
10555 .await;
10556
10557 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10558 cx.simulate_keystroke(".");
10559 let completion_item = lsp::CompletionItem {
10560 label: "some".into(),
10561 kind: Some(lsp::CompletionItemKind::SNIPPET),
10562 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10563 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10564 kind: lsp::MarkupKind::Markdown,
10565 value: "```rust\nSome(2)\n```".to_string(),
10566 })),
10567 deprecated: Some(false),
10568 sort_text: Some("fffffff2".to_string()),
10569 filter_text: Some("some".to_string()),
10570 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10571 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10572 range: lsp::Range {
10573 start: lsp::Position {
10574 line: 0,
10575 character: 22,
10576 },
10577 end: lsp::Position {
10578 line: 0,
10579 character: 22,
10580 },
10581 },
10582 new_text: "Some(2)".to_string(),
10583 })),
10584 additional_text_edits: Some(vec![lsp::TextEdit {
10585 range: lsp::Range {
10586 start: lsp::Position {
10587 line: 0,
10588 character: 20,
10589 },
10590 end: lsp::Position {
10591 line: 0,
10592 character: 22,
10593 },
10594 },
10595 new_text: "".to_string(),
10596 }]),
10597 ..Default::default()
10598 };
10599
10600 let closure_completion_item = completion_item.clone();
10601 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10602 let task_completion_item = closure_completion_item.clone();
10603 async move {
10604 Ok(Some(lsp::CompletionResponse::Array(vec![
10605 task_completion_item,
10606 ])))
10607 }
10608 });
10609
10610 request.next().await;
10611
10612 cx.condition(|editor, _| editor.context_menu_visible())
10613 .await;
10614 let apply_additional_edits = cx.update_editor(|editor, cx| {
10615 editor
10616 .confirm_completion(&ConfirmCompletion::default(), cx)
10617 .unwrap()
10618 });
10619 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10620
10621 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10622 let task_completion_item = completion_item.clone();
10623 async move { Ok(task_completion_item) }
10624 })
10625 .next()
10626 .await
10627 .unwrap();
10628 apply_additional_edits.await.unwrap();
10629 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10630}
10631
10632#[gpui::test]
10633async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) {
10634 init_test(cx, |_| {});
10635
10636 let mut cx = EditorLspTestContext::new_rust(
10637 lsp::ServerCapabilities {
10638 completion_provider: Some(lsp::CompletionOptions {
10639 trigger_characters: Some(vec![".".to_string()]),
10640 resolve_provider: Some(true),
10641 ..Default::default()
10642 }),
10643 ..Default::default()
10644 },
10645 cx,
10646 )
10647 .await;
10648
10649 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10650 cx.simulate_keystroke(".");
10651
10652 let completion_item = lsp::CompletionItem {
10653 label: "unresolved".to_string(),
10654 detail: None,
10655 documentation: None,
10656 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10657 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10658 new_text: ".unresolved".to_string(),
10659 })),
10660 ..lsp::CompletionItem::default()
10661 };
10662
10663 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10664 let item = completion_item.clone();
10665 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10666 })
10667 .next()
10668 .await;
10669
10670 cx.condition(|editor, _| editor.context_menu_visible())
10671 .await;
10672 cx.update_editor(|editor, _| {
10673 let context_menu = editor.context_menu.read();
10674 let context_menu = context_menu
10675 .as_ref()
10676 .expect("Should have the context menu deployed");
10677 match context_menu {
10678 CodeContextMenu::Completions(completions_menu) => {
10679 let completions = completions_menu.completions.read();
10680 assert_eq!(completions.len(), 1, "Should have one completion");
10681 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10682 }
10683 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10684 }
10685 });
10686
10687 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10688 Ok(lsp::CompletionItem {
10689 label: "resolved".to_string(),
10690 detail: Some("Now resolved!".to_string()),
10691 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10692 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10693 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10694 new_text: ".resolved".to_string(),
10695 })),
10696 ..lsp::CompletionItem::default()
10697 })
10698 })
10699 .next()
10700 .await;
10701 cx.run_until_parked();
10702
10703 cx.update_editor(|editor, _| {
10704 let context_menu = editor.context_menu.read();
10705 let context_menu = context_menu
10706 .as_ref()
10707 .expect("Should have the context menu deployed");
10708 match context_menu {
10709 CodeContextMenu::Completions(completions_menu) => {
10710 let completions = completions_menu.completions.read();
10711 assert_eq!(completions.len(), 1, "Should have one completion");
10712 assert_eq!(
10713 completions.get(0).unwrap().label.text,
10714 "resolved",
10715 "Should update the completion label after resolving"
10716 );
10717 }
10718 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10719 }
10720 });
10721}
10722
10723#[gpui::test]
10724async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10725 init_test(cx, |_| {});
10726
10727 let item_0 = lsp::CompletionItem {
10728 label: "abs".into(),
10729 insert_text: Some("abs".into()),
10730 data: Some(json!({ "very": "special"})),
10731 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10732 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10733 lsp::InsertReplaceEdit {
10734 new_text: "abs".to_string(),
10735 insert: lsp::Range::default(),
10736 replace: lsp::Range::default(),
10737 },
10738 )),
10739 ..lsp::CompletionItem::default()
10740 };
10741 let items = iter::once(item_0.clone())
10742 .chain((11..51).map(|i| lsp::CompletionItem {
10743 label: format!("item_{}", i),
10744 insert_text: Some(format!("item_{}", i)),
10745 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10746 ..lsp::CompletionItem::default()
10747 }))
10748 .collect::<Vec<_>>();
10749
10750 let default_commit_characters = vec!["?".to_string()];
10751 let default_data = json!({ "default": "data"});
10752 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10753 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10754 let default_edit_range = lsp::Range {
10755 start: lsp::Position {
10756 line: 0,
10757 character: 5,
10758 },
10759 end: lsp::Position {
10760 line: 0,
10761 character: 5,
10762 },
10763 };
10764
10765 let item_0_out = lsp::CompletionItem {
10766 commit_characters: Some(default_commit_characters.clone()),
10767 insert_text_format: Some(default_insert_text_format),
10768 ..item_0
10769 };
10770 let items_out = iter::once(item_0_out)
10771 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
10772 commit_characters: Some(default_commit_characters.clone()),
10773 data: Some(default_data.clone()),
10774 insert_text_mode: Some(default_insert_text_mode),
10775 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10776 range: default_edit_range,
10777 new_text: item.label.clone(),
10778 })),
10779 ..item.clone()
10780 }))
10781 .collect::<Vec<lsp::CompletionItem>>();
10782
10783 let mut cx = EditorLspTestContext::new_rust(
10784 lsp::ServerCapabilities {
10785 completion_provider: Some(lsp::CompletionOptions {
10786 trigger_characters: Some(vec![".".to_string()]),
10787 resolve_provider: Some(true),
10788 ..Default::default()
10789 }),
10790 ..Default::default()
10791 },
10792 cx,
10793 )
10794 .await;
10795
10796 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10797 cx.simulate_keystroke(".");
10798
10799 let completion_data = default_data.clone();
10800 let completion_characters = default_commit_characters.clone();
10801 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10802 let default_data = completion_data.clone();
10803 let default_commit_characters = completion_characters.clone();
10804 let items = items.clone();
10805 async move {
10806 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10807 items,
10808 item_defaults: Some(lsp::CompletionListItemDefaults {
10809 data: Some(default_data.clone()),
10810 commit_characters: Some(default_commit_characters.clone()),
10811 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10812 default_edit_range,
10813 )),
10814 insert_text_format: Some(default_insert_text_format),
10815 insert_text_mode: Some(default_insert_text_mode),
10816 }),
10817 ..lsp::CompletionList::default()
10818 })))
10819 }
10820 })
10821 .next()
10822 .await;
10823
10824 let resolved_items = Arc::new(Mutex::new(Vec::new()));
10825 cx.lsp
10826 .server
10827 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10828 let closure_resolved_items = resolved_items.clone();
10829 move |item_to_resolve, _| {
10830 let closure_resolved_items = closure_resolved_items.clone();
10831 async move {
10832 closure_resolved_items.lock().push(item_to_resolve.clone());
10833 Ok(item_to_resolve)
10834 }
10835 }
10836 })
10837 .detach();
10838
10839 cx.condition(|editor, _| editor.context_menu_visible())
10840 .await;
10841 cx.run_until_parked();
10842 cx.update_editor(|editor, _| {
10843 let menu = editor.context_menu.read();
10844 match menu.as_ref().expect("should have the completions menu") {
10845 CodeContextMenu::Completions(completions_menu) => {
10846 assert_eq!(
10847 completions_menu
10848 .matches
10849 .iter()
10850 .map(|c| c.string.clone())
10851 .collect::<Vec<String>>(),
10852 items_out
10853 .iter()
10854 .map(|completion| completion.label.clone())
10855 .collect::<Vec<String>>()
10856 );
10857 }
10858 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10859 }
10860 });
10861 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
10862 // with 4 from the end.
10863 assert_eq!(
10864 *resolved_items.lock(),
10865 [
10866 &items_out[0..16],
10867 &items_out[items_out.len() - 4..items_out.len()]
10868 ]
10869 .concat()
10870 .iter()
10871 .cloned()
10872 .collect::<Vec<lsp::CompletionItem>>()
10873 );
10874 resolved_items.lock().clear();
10875
10876 cx.update_editor(|editor, cx| {
10877 editor.context_menu_prev(&ContextMenuPrev, cx);
10878 });
10879 cx.run_until_parked();
10880 // Completions that have already been resolved are skipped.
10881 assert_eq!(
10882 *resolved_items.lock(),
10883 [
10884 // Selected item is always resolved even if it was resolved before.
10885 &items_out[items_out.len() - 1..items_out.len()],
10886 &items_out[items_out.len() - 16..items_out.len() - 4]
10887 ]
10888 .concat()
10889 .iter()
10890 .cloned()
10891 .collect::<Vec<lsp::CompletionItem>>()
10892 );
10893 resolved_items.lock().clear();
10894}
10895
10896#[gpui::test]
10897async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10898 init_test(cx, |_| {});
10899
10900 let mut cx = EditorLspTestContext::new(
10901 Language::new(
10902 LanguageConfig {
10903 matcher: LanguageMatcher {
10904 path_suffixes: vec!["jsx".into()],
10905 ..Default::default()
10906 },
10907 overrides: [(
10908 "element".into(),
10909 LanguageConfigOverride {
10910 word_characters: Override::Set(['-'].into_iter().collect()),
10911 ..Default::default()
10912 },
10913 )]
10914 .into_iter()
10915 .collect(),
10916 ..Default::default()
10917 },
10918 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10919 )
10920 .with_override_query("(jsx_self_closing_element) @element")
10921 .unwrap(),
10922 lsp::ServerCapabilities {
10923 completion_provider: Some(lsp::CompletionOptions {
10924 trigger_characters: Some(vec![":".to_string()]),
10925 ..Default::default()
10926 }),
10927 ..Default::default()
10928 },
10929 cx,
10930 )
10931 .await;
10932
10933 cx.lsp
10934 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10935 Ok(Some(lsp::CompletionResponse::Array(vec![
10936 lsp::CompletionItem {
10937 label: "bg-blue".into(),
10938 ..Default::default()
10939 },
10940 lsp::CompletionItem {
10941 label: "bg-red".into(),
10942 ..Default::default()
10943 },
10944 lsp::CompletionItem {
10945 label: "bg-yellow".into(),
10946 ..Default::default()
10947 },
10948 ])))
10949 });
10950
10951 cx.set_state(r#"<p class="bgˇ" />"#);
10952
10953 // Trigger completion when typing a dash, because the dash is an extra
10954 // word character in the 'element' scope, which contains the cursor.
10955 cx.simulate_keystroke("-");
10956 cx.executor().run_until_parked();
10957 cx.update_editor(|editor, _| {
10958 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10959 assert_eq!(
10960 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10961 &["bg-red", "bg-blue", "bg-yellow"]
10962 );
10963 } else {
10964 panic!("expected completion menu to be open");
10965 }
10966 });
10967
10968 cx.simulate_keystroke("l");
10969 cx.executor().run_until_parked();
10970 cx.update_editor(|editor, _| {
10971 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10972 assert_eq!(
10973 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10974 &["bg-blue", "bg-yellow"]
10975 );
10976 } else {
10977 panic!("expected completion menu to be open");
10978 }
10979 });
10980
10981 // When filtering completions, consider the character after the '-' to
10982 // be the start of a subword.
10983 cx.set_state(r#"<p class="yelˇ" />"#);
10984 cx.simulate_keystroke("l");
10985 cx.executor().run_until_parked();
10986 cx.update_editor(|editor, _| {
10987 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10988 assert_eq!(
10989 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10990 &["bg-yellow"]
10991 );
10992 } else {
10993 panic!("expected completion menu to be open");
10994 }
10995 });
10996}
10997
10998#[gpui::test]
10999async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11000 init_test(cx, |settings| {
11001 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11002 FormatterList(vec![Formatter::Prettier].into()),
11003 ))
11004 });
11005
11006 let fs = FakeFs::new(cx.executor());
11007 fs.insert_file("/file.ts", Default::default()).await;
11008
11009 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11010 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11011
11012 language_registry.add(Arc::new(Language::new(
11013 LanguageConfig {
11014 name: "TypeScript".into(),
11015 matcher: LanguageMatcher {
11016 path_suffixes: vec!["ts".to_string()],
11017 ..Default::default()
11018 },
11019 ..Default::default()
11020 },
11021 Some(tree_sitter_rust::LANGUAGE.into()),
11022 )));
11023 update_test_language_settings(cx, |settings| {
11024 settings.defaults.prettier = Some(PrettierSettings {
11025 allowed: true,
11026 ..PrettierSettings::default()
11027 });
11028 });
11029
11030 let test_plugin = "test_plugin";
11031 let _ = language_registry.register_fake_lsp(
11032 "TypeScript",
11033 FakeLspAdapter {
11034 prettier_plugins: vec![test_plugin],
11035 ..Default::default()
11036 },
11037 );
11038
11039 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11040 let buffer = project
11041 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11042 .await
11043 .unwrap();
11044
11045 let buffer_text = "one\ntwo\nthree\n";
11046 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11047 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11048 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11049
11050 editor
11051 .update(cx, |editor, cx| {
11052 editor.perform_format(
11053 project.clone(),
11054 FormatTrigger::Manual,
11055 FormatTarget::Buffer,
11056 cx,
11057 )
11058 })
11059 .unwrap()
11060 .await;
11061 assert_eq!(
11062 editor.update(cx, |editor, cx| editor.text(cx)),
11063 buffer_text.to_string() + prettier_format_suffix,
11064 "Test prettier formatting was not applied to the original buffer text",
11065 );
11066
11067 update_test_language_settings(cx, |settings| {
11068 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11069 });
11070 let format = editor.update(cx, |editor, cx| {
11071 editor.perform_format(
11072 project.clone(),
11073 FormatTrigger::Manual,
11074 FormatTarget::Buffer,
11075 cx,
11076 )
11077 });
11078 format.await.unwrap();
11079 assert_eq!(
11080 editor.update(cx, |editor, cx| editor.text(cx)),
11081 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11082 "Autoformatting (via test prettier) was not applied to the original buffer text",
11083 );
11084}
11085
11086#[gpui::test]
11087async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11088 init_test(cx, |_| {});
11089 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11090 let base_text = indoc! {r#"
11091 struct Row;
11092 struct Row1;
11093 struct Row2;
11094
11095 struct Row4;
11096 struct Row5;
11097 struct Row6;
11098
11099 struct Row8;
11100 struct Row9;
11101 struct Row10;"#};
11102
11103 // When addition hunks are not adjacent to carets, no hunk revert is performed
11104 assert_hunk_revert(
11105 indoc! {r#"struct Row;
11106 struct Row1;
11107 struct Row1.1;
11108 struct Row1.2;
11109 struct Row2;ˇ
11110
11111 struct Row4;
11112 struct Row5;
11113 struct Row6;
11114
11115 struct Row8;
11116 ˇstruct Row9;
11117 struct Row9.1;
11118 struct Row9.2;
11119 struct Row9.3;
11120 struct Row10;"#},
11121 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11122 indoc! {r#"struct Row;
11123 struct Row1;
11124 struct Row1.1;
11125 struct Row1.2;
11126 struct Row2;ˇ
11127
11128 struct Row4;
11129 struct Row5;
11130 struct Row6;
11131
11132 struct Row8;
11133 ˇstruct Row9;
11134 struct Row9.1;
11135 struct Row9.2;
11136 struct Row9.3;
11137 struct Row10;"#},
11138 base_text,
11139 &mut cx,
11140 );
11141 // Same for selections
11142 assert_hunk_revert(
11143 indoc! {r#"struct Row;
11144 struct Row1;
11145 struct Row2;
11146 struct Row2.1;
11147 struct Row2.2;
11148 «ˇ
11149 struct Row4;
11150 struct» Row5;
11151 «struct Row6;
11152 ˇ»
11153 struct Row9.1;
11154 struct Row9.2;
11155 struct Row9.3;
11156 struct Row8;
11157 struct Row9;
11158 struct Row10;"#},
11159 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11160 indoc! {r#"struct Row;
11161 struct Row1;
11162 struct Row2;
11163 struct Row2.1;
11164 struct Row2.2;
11165 «ˇ
11166 struct Row4;
11167 struct» Row5;
11168 «struct Row6;
11169 ˇ»
11170 struct Row9.1;
11171 struct Row9.2;
11172 struct Row9.3;
11173 struct Row8;
11174 struct Row9;
11175 struct Row10;"#},
11176 base_text,
11177 &mut cx,
11178 );
11179
11180 // When carets and selections intersect the addition hunks, those are reverted.
11181 // Adjacent carets got merged.
11182 assert_hunk_revert(
11183 indoc! {r#"struct Row;
11184 ˇ// something on the top
11185 struct Row1;
11186 struct Row2;
11187 struct Roˇw3.1;
11188 struct Row2.2;
11189 struct Row2.3;ˇ
11190
11191 struct Row4;
11192 struct ˇRow5.1;
11193 struct Row5.2;
11194 struct «Rowˇ»5.3;
11195 struct Row5;
11196 struct Row6;
11197 ˇ
11198 struct Row9.1;
11199 struct «Rowˇ»9.2;
11200 struct «ˇRow»9.3;
11201 struct Row8;
11202 struct Row9;
11203 «ˇ// something on bottom»
11204 struct Row10;"#},
11205 vec![
11206 DiffHunkStatus::Added,
11207 DiffHunkStatus::Added,
11208 DiffHunkStatus::Added,
11209 DiffHunkStatus::Added,
11210 DiffHunkStatus::Added,
11211 ],
11212 indoc! {r#"struct Row;
11213 ˇstruct Row1;
11214 struct Row2;
11215 ˇ
11216 struct Row4;
11217 ˇstruct Row5;
11218 struct Row6;
11219 ˇ
11220 ˇstruct Row8;
11221 struct Row9;
11222 ˇstruct Row10;"#},
11223 base_text,
11224 &mut cx,
11225 );
11226}
11227
11228#[gpui::test]
11229async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11230 init_test(cx, |_| {});
11231 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11232 let base_text = indoc! {r#"
11233 struct Row;
11234 struct Row1;
11235 struct Row2;
11236
11237 struct Row4;
11238 struct Row5;
11239 struct Row6;
11240
11241 struct Row8;
11242 struct Row9;
11243 struct Row10;"#};
11244
11245 // Modification hunks behave the same as the addition ones.
11246 assert_hunk_revert(
11247 indoc! {r#"struct Row;
11248 struct Row1;
11249 struct Row33;
11250 ˇ
11251 struct Row4;
11252 struct Row5;
11253 struct Row6;
11254 ˇ
11255 struct Row99;
11256 struct Row9;
11257 struct Row10;"#},
11258 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11259 indoc! {r#"struct Row;
11260 struct Row1;
11261 struct Row33;
11262 ˇ
11263 struct Row4;
11264 struct Row5;
11265 struct Row6;
11266 ˇ
11267 struct Row99;
11268 struct Row9;
11269 struct Row10;"#},
11270 base_text,
11271 &mut cx,
11272 );
11273 assert_hunk_revert(
11274 indoc! {r#"struct Row;
11275 struct Row1;
11276 struct Row33;
11277 «ˇ
11278 struct Row4;
11279 struct» Row5;
11280 «struct Row6;
11281 ˇ»
11282 struct Row99;
11283 struct Row9;
11284 struct Row10;"#},
11285 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11286 indoc! {r#"struct Row;
11287 struct Row1;
11288 struct Row33;
11289 «ˇ
11290 struct Row4;
11291 struct» Row5;
11292 «struct Row6;
11293 ˇ»
11294 struct Row99;
11295 struct Row9;
11296 struct Row10;"#},
11297 base_text,
11298 &mut cx,
11299 );
11300
11301 assert_hunk_revert(
11302 indoc! {r#"ˇstruct Row1.1;
11303 struct Row1;
11304 «ˇstr»uct Row22;
11305
11306 struct ˇRow44;
11307 struct Row5;
11308 struct «Rˇ»ow66;ˇ
11309
11310 «struˇ»ct Row88;
11311 struct Row9;
11312 struct Row1011;ˇ"#},
11313 vec![
11314 DiffHunkStatus::Modified,
11315 DiffHunkStatus::Modified,
11316 DiffHunkStatus::Modified,
11317 DiffHunkStatus::Modified,
11318 DiffHunkStatus::Modified,
11319 DiffHunkStatus::Modified,
11320 ],
11321 indoc! {r#"struct Row;
11322 ˇstruct Row1;
11323 struct Row2;
11324 ˇ
11325 struct Row4;
11326 ˇstruct Row5;
11327 struct Row6;
11328 ˇ
11329 struct Row8;
11330 ˇstruct Row9;
11331 struct Row10;ˇ"#},
11332 base_text,
11333 &mut cx,
11334 );
11335}
11336
11337#[gpui::test]
11338async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11339 init_test(cx, |_| {});
11340 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11341 let base_text = indoc! {r#"struct Row;
11342struct Row1;
11343struct Row2;
11344
11345struct Row4;
11346struct Row5;
11347struct Row6;
11348
11349struct Row8;
11350struct Row9;
11351struct Row10;"#};
11352
11353 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11354 assert_hunk_revert(
11355 indoc! {r#"struct Row;
11356 struct Row2;
11357
11358 ˇstruct Row4;
11359 struct Row5;
11360 struct Row6;
11361 ˇ
11362 struct Row8;
11363 struct Row10;"#},
11364 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11365 indoc! {r#"struct Row;
11366 struct Row2;
11367
11368 ˇstruct Row4;
11369 struct Row5;
11370 struct Row6;
11371 ˇ
11372 struct Row8;
11373 struct Row10;"#},
11374 base_text,
11375 &mut cx,
11376 );
11377 assert_hunk_revert(
11378 indoc! {r#"struct Row;
11379 struct Row2;
11380
11381 «ˇstruct Row4;
11382 struct» Row5;
11383 «struct Row6;
11384 ˇ»
11385 struct Row8;
11386 struct Row10;"#},
11387 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11388 indoc! {r#"struct Row;
11389 struct Row2;
11390
11391 «ˇstruct Row4;
11392 struct» Row5;
11393 «struct Row6;
11394 ˇ»
11395 struct Row8;
11396 struct Row10;"#},
11397 base_text,
11398 &mut cx,
11399 );
11400
11401 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11402 assert_hunk_revert(
11403 indoc! {r#"struct Row;
11404 ˇstruct Row2;
11405
11406 struct Row4;
11407 struct Row5;
11408 struct Row6;
11409
11410 struct Row8;ˇ
11411 struct Row10;"#},
11412 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11413 indoc! {r#"struct Row;
11414 struct Row1;
11415 ˇstruct Row2;
11416
11417 struct Row4;
11418 struct Row5;
11419 struct Row6;
11420
11421 struct Row8;ˇ
11422 struct Row9;
11423 struct Row10;"#},
11424 base_text,
11425 &mut cx,
11426 );
11427 assert_hunk_revert(
11428 indoc! {r#"struct Row;
11429 struct Row2«ˇ;
11430 struct Row4;
11431 struct» Row5;
11432 «struct Row6;
11433
11434 struct Row8;ˇ»
11435 struct Row10;"#},
11436 vec![
11437 DiffHunkStatus::Removed,
11438 DiffHunkStatus::Removed,
11439 DiffHunkStatus::Removed,
11440 ],
11441 indoc! {r#"struct Row;
11442 struct Row1;
11443 struct Row2«ˇ;
11444
11445 struct Row4;
11446 struct» Row5;
11447 «struct Row6;
11448
11449 struct Row8;ˇ»
11450 struct Row9;
11451 struct Row10;"#},
11452 base_text,
11453 &mut cx,
11454 );
11455}
11456
11457#[gpui::test]
11458async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11459 init_test(cx, |_| {});
11460
11461 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11462 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11463 let base_text_3 =
11464 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11465
11466 let text_1 = edit_first_char_of_every_line(base_text_1);
11467 let text_2 = edit_first_char_of_every_line(base_text_2);
11468 let text_3 = edit_first_char_of_every_line(base_text_3);
11469
11470 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11471 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11472 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11473
11474 let multibuffer = cx.new_model(|cx| {
11475 let mut multibuffer = MultiBuffer::new(ReadWrite);
11476 multibuffer.push_excerpts(
11477 buffer_1.clone(),
11478 [
11479 ExcerptRange {
11480 context: Point::new(0, 0)..Point::new(3, 0),
11481 primary: None,
11482 },
11483 ExcerptRange {
11484 context: Point::new(5, 0)..Point::new(7, 0),
11485 primary: None,
11486 },
11487 ExcerptRange {
11488 context: Point::new(9, 0)..Point::new(10, 4),
11489 primary: None,
11490 },
11491 ],
11492 cx,
11493 );
11494 multibuffer.push_excerpts(
11495 buffer_2.clone(),
11496 [
11497 ExcerptRange {
11498 context: Point::new(0, 0)..Point::new(3, 0),
11499 primary: None,
11500 },
11501 ExcerptRange {
11502 context: Point::new(5, 0)..Point::new(7, 0),
11503 primary: None,
11504 },
11505 ExcerptRange {
11506 context: Point::new(9, 0)..Point::new(10, 4),
11507 primary: None,
11508 },
11509 ],
11510 cx,
11511 );
11512 multibuffer.push_excerpts(
11513 buffer_3.clone(),
11514 [
11515 ExcerptRange {
11516 context: Point::new(0, 0)..Point::new(3, 0),
11517 primary: None,
11518 },
11519 ExcerptRange {
11520 context: Point::new(5, 0)..Point::new(7, 0),
11521 primary: None,
11522 },
11523 ExcerptRange {
11524 context: Point::new(9, 0)..Point::new(10, 4),
11525 primary: None,
11526 },
11527 ],
11528 cx,
11529 );
11530 multibuffer
11531 });
11532
11533 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11534 editor.update(cx, |editor, cx| {
11535 for (buffer, diff_base) in [
11536 (buffer_1.clone(), base_text_1),
11537 (buffer_2.clone(), base_text_2),
11538 (buffer_3.clone(), base_text_3),
11539 ] {
11540 let change_set = cx.new_model(|cx| {
11541 BufferChangeSet::new_with_base_text(
11542 diff_base.to_string(),
11543 buffer.read(cx).text_snapshot(),
11544 cx,
11545 )
11546 });
11547 editor.diff_map.add_change_set(change_set, cx)
11548 }
11549 });
11550 cx.executor().run_until_parked();
11551
11552 editor.update(cx, |editor, cx| {
11553 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}");
11554 editor.select_all(&SelectAll, cx);
11555 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11556 });
11557 cx.executor().run_until_parked();
11558
11559 // When all ranges are selected, all buffer hunks are reverted.
11560 editor.update(cx, |editor, cx| {
11561 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");
11562 });
11563 buffer_1.update(cx, |buffer, _| {
11564 assert_eq!(buffer.text(), base_text_1);
11565 });
11566 buffer_2.update(cx, |buffer, _| {
11567 assert_eq!(buffer.text(), base_text_2);
11568 });
11569 buffer_3.update(cx, |buffer, _| {
11570 assert_eq!(buffer.text(), base_text_3);
11571 });
11572
11573 editor.update(cx, |editor, cx| {
11574 editor.undo(&Default::default(), cx);
11575 });
11576
11577 editor.update(cx, |editor, cx| {
11578 editor.change_selections(None, cx, |s| {
11579 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11580 });
11581 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11582 });
11583
11584 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11585 // but not affect buffer_2 and its related excerpts.
11586 editor.update(cx, |editor, cx| {
11587 assert_eq!(
11588 editor.text(cx),
11589 "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}"
11590 );
11591 });
11592 buffer_1.update(cx, |buffer, _| {
11593 assert_eq!(buffer.text(), base_text_1);
11594 });
11595 buffer_2.update(cx, |buffer, _| {
11596 assert_eq!(
11597 buffer.text(),
11598 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11599 );
11600 });
11601 buffer_3.update(cx, |buffer, _| {
11602 assert_eq!(
11603 buffer.text(),
11604 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11605 );
11606 });
11607
11608 fn edit_first_char_of_every_line(text: &str) -> String {
11609 text.split('\n')
11610 .map(|line| format!("X{}", &line[1..]))
11611 .collect::<Vec<_>>()
11612 .join("\n")
11613 }
11614}
11615
11616#[gpui::test]
11617async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11618 init_test(cx, |_| {});
11619
11620 let cols = 4;
11621 let rows = 10;
11622 let sample_text_1 = sample_text(rows, cols, 'a');
11623 assert_eq!(
11624 sample_text_1,
11625 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11626 );
11627 let sample_text_2 = sample_text(rows, cols, 'l');
11628 assert_eq!(
11629 sample_text_2,
11630 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11631 );
11632 let sample_text_3 = sample_text(rows, cols, 'v');
11633 assert_eq!(
11634 sample_text_3,
11635 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11636 );
11637
11638 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11639 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11640 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11641
11642 let multi_buffer = cx.new_model(|cx| {
11643 let mut multibuffer = MultiBuffer::new(ReadWrite);
11644 multibuffer.push_excerpts(
11645 buffer_1.clone(),
11646 [
11647 ExcerptRange {
11648 context: Point::new(0, 0)..Point::new(3, 0),
11649 primary: None,
11650 },
11651 ExcerptRange {
11652 context: Point::new(5, 0)..Point::new(7, 0),
11653 primary: None,
11654 },
11655 ExcerptRange {
11656 context: Point::new(9, 0)..Point::new(10, 4),
11657 primary: None,
11658 },
11659 ],
11660 cx,
11661 );
11662 multibuffer.push_excerpts(
11663 buffer_2.clone(),
11664 [
11665 ExcerptRange {
11666 context: Point::new(0, 0)..Point::new(3, 0),
11667 primary: None,
11668 },
11669 ExcerptRange {
11670 context: Point::new(5, 0)..Point::new(7, 0),
11671 primary: None,
11672 },
11673 ExcerptRange {
11674 context: Point::new(9, 0)..Point::new(10, 4),
11675 primary: None,
11676 },
11677 ],
11678 cx,
11679 );
11680 multibuffer.push_excerpts(
11681 buffer_3.clone(),
11682 [
11683 ExcerptRange {
11684 context: Point::new(0, 0)..Point::new(3, 0),
11685 primary: None,
11686 },
11687 ExcerptRange {
11688 context: Point::new(5, 0)..Point::new(7, 0),
11689 primary: None,
11690 },
11691 ExcerptRange {
11692 context: Point::new(9, 0)..Point::new(10, 4),
11693 primary: None,
11694 },
11695 ],
11696 cx,
11697 );
11698 multibuffer
11699 });
11700
11701 let fs = FakeFs::new(cx.executor());
11702 fs.insert_tree(
11703 "/a",
11704 json!({
11705 "main.rs": sample_text_1,
11706 "other.rs": sample_text_2,
11707 "lib.rs": sample_text_3,
11708 }),
11709 )
11710 .await;
11711 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11712 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11713 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11714 let multi_buffer_editor = cx.new_view(|cx| {
11715 Editor::new(
11716 EditorMode::Full,
11717 multi_buffer,
11718 Some(project.clone()),
11719 true,
11720 cx,
11721 )
11722 });
11723 let multibuffer_item_id = workspace
11724 .update(cx, |workspace, cx| {
11725 assert!(
11726 workspace.active_item(cx).is_none(),
11727 "active item should be None before the first item is added"
11728 );
11729 workspace.add_item_to_active_pane(
11730 Box::new(multi_buffer_editor.clone()),
11731 None,
11732 true,
11733 cx,
11734 );
11735 let active_item = workspace
11736 .active_item(cx)
11737 .expect("should have an active item after adding the multi buffer");
11738 assert!(
11739 !active_item.is_singleton(cx),
11740 "A multi buffer was expected to active after adding"
11741 );
11742 active_item.item_id()
11743 })
11744 .unwrap();
11745 cx.executor().run_until_parked();
11746
11747 multi_buffer_editor.update(cx, |editor, cx| {
11748 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11749 editor.open_excerpts(&OpenExcerpts, cx);
11750 });
11751 cx.executor().run_until_parked();
11752 let first_item_id = workspace
11753 .update(cx, |workspace, cx| {
11754 let active_item = workspace
11755 .active_item(cx)
11756 .expect("should have an active item after navigating into the 1st buffer");
11757 let first_item_id = active_item.item_id();
11758 assert_ne!(
11759 first_item_id, multibuffer_item_id,
11760 "Should navigate into the 1st buffer and activate it"
11761 );
11762 assert!(
11763 active_item.is_singleton(cx),
11764 "New active item should be a singleton buffer"
11765 );
11766 assert_eq!(
11767 active_item
11768 .act_as::<Editor>(cx)
11769 .expect("should have navigated into an editor for the 1st buffer")
11770 .read(cx)
11771 .text(cx),
11772 sample_text_1
11773 );
11774
11775 workspace
11776 .go_back(workspace.active_pane().downgrade(), cx)
11777 .detach_and_log_err(cx);
11778
11779 first_item_id
11780 })
11781 .unwrap();
11782 cx.executor().run_until_parked();
11783 workspace
11784 .update(cx, |workspace, cx| {
11785 let active_item = workspace
11786 .active_item(cx)
11787 .expect("should have an active item after navigating back");
11788 assert_eq!(
11789 active_item.item_id(),
11790 multibuffer_item_id,
11791 "Should navigate back to the multi buffer"
11792 );
11793 assert!(!active_item.is_singleton(cx));
11794 })
11795 .unwrap();
11796
11797 multi_buffer_editor.update(cx, |editor, cx| {
11798 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11799 s.select_ranges(Some(39..40))
11800 });
11801 editor.open_excerpts(&OpenExcerpts, cx);
11802 });
11803 cx.executor().run_until_parked();
11804 let second_item_id = workspace
11805 .update(cx, |workspace, cx| {
11806 let active_item = workspace
11807 .active_item(cx)
11808 .expect("should have an active item after navigating into the 2nd buffer");
11809 let second_item_id = active_item.item_id();
11810 assert_ne!(
11811 second_item_id, multibuffer_item_id,
11812 "Should navigate away from the multibuffer"
11813 );
11814 assert_ne!(
11815 second_item_id, first_item_id,
11816 "Should navigate into the 2nd buffer and activate it"
11817 );
11818 assert!(
11819 active_item.is_singleton(cx),
11820 "New active item should be a singleton buffer"
11821 );
11822 assert_eq!(
11823 active_item
11824 .act_as::<Editor>(cx)
11825 .expect("should have navigated into an editor")
11826 .read(cx)
11827 .text(cx),
11828 sample_text_2
11829 );
11830
11831 workspace
11832 .go_back(workspace.active_pane().downgrade(), cx)
11833 .detach_and_log_err(cx);
11834
11835 second_item_id
11836 })
11837 .unwrap();
11838 cx.executor().run_until_parked();
11839 workspace
11840 .update(cx, |workspace, cx| {
11841 let active_item = workspace
11842 .active_item(cx)
11843 .expect("should have an active item after navigating back from the 2nd buffer");
11844 assert_eq!(
11845 active_item.item_id(),
11846 multibuffer_item_id,
11847 "Should navigate back from the 2nd buffer to the multi buffer"
11848 );
11849 assert!(!active_item.is_singleton(cx));
11850 })
11851 .unwrap();
11852
11853 multi_buffer_editor.update(cx, |editor, cx| {
11854 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11855 s.select_ranges(Some(70..70))
11856 });
11857 editor.open_excerpts(&OpenExcerpts, cx);
11858 });
11859 cx.executor().run_until_parked();
11860 workspace
11861 .update(cx, |workspace, cx| {
11862 let active_item = workspace
11863 .active_item(cx)
11864 .expect("should have an active item after navigating into the 3rd buffer");
11865 let third_item_id = active_item.item_id();
11866 assert_ne!(
11867 third_item_id, multibuffer_item_id,
11868 "Should navigate into the 3rd buffer and activate it"
11869 );
11870 assert_ne!(third_item_id, first_item_id);
11871 assert_ne!(third_item_id, second_item_id);
11872 assert!(
11873 active_item.is_singleton(cx),
11874 "New active item should be a singleton buffer"
11875 );
11876 assert_eq!(
11877 active_item
11878 .act_as::<Editor>(cx)
11879 .expect("should have navigated into an editor")
11880 .read(cx)
11881 .text(cx),
11882 sample_text_3
11883 );
11884
11885 workspace
11886 .go_back(workspace.active_pane().downgrade(), cx)
11887 .detach_and_log_err(cx);
11888 })
11889 .unwrap();
11890 cx.executor().run_until_parked();
11891 workspace
11892 .update(cx, |workspace, cx| {
11893 let active_item = workspace
11894 .active_item(cx)
11895 .expect("should have an active item after navigating back from the 3rd buffer");
11896 assert_eq!(
11897 active_item.item_id(),
11898 multibuffer_item_id,
11899 "Should navigate back from the 3rd buffer to the multi buffer"
11900 );
11901 assert!(!active_item.is_singleton(cx));
11902 })
11903 .unwrap();
11904}
11905
11906#[gpui::test]
11907async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11908 init_test(cx, |_| {});
11909
11910 let mut cx = EditorTestContext::new(cx).await;
11911
11912 let diff_base = r#"
11913 use some::mod;
11914
11915 const A: u32 = 42;
11916
11917 fn main() {
11918 println!("hello");
11919
11920 println!("world");
11921 }
11922 "#
11923 .unindent();
11924
11925 cx.set_state(
11926 &r#"
11927 use some::modified;
11928
11929 ˇ
11930 fn main() {
11931 println!("hello there");
11932
11933 println!("around the");
11934 println!("world");
11935 }
11936 "#
11937 .unindent(),
11938 );
11939
11940 cx.set_diff_base(&diff_base);
11941 executor.run_until_parked();
11942
11943 cx.update_editor(|editor, cx| {
11944 editor.go_to_next_hunk(&GoToHunk, cx);
11945 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11946 });
11947 executor.run_until_parked();
11948 cx.assert_state_with_diff(
11949 r#"
11950 use some::modified;
11951
11952
11953 fn main() {
11954 - println!("hello");
11955 + ˇ println!("hello there");
11956
11957 println!("around the");
11958 println!("world");
11959 }
11960 "#
11961 .unindent(),
11962 );
11963
11964 cx.update_editor(|editor, cx| {
11965 for _ in 0..3 {
11966 editor.go_to_next_hunk(&GoToHunk, cx);
11967 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11968 }
11969 });
11970 executor.run_until_parked();
11971 cx.assert_state_with_diff(
11972 r#"
11973 - use some::mod;
11974 + use some::modified;
11975
11976 - const A: u32 = 42;
11977 ˇ
11978 fn main() {
11979 - println!("hello");
11980 + println!("hello there");
11981
11982 + println!("around the");
11983 println!("world");
11984 }
11985 "#
11986 .unindent(),
11987 );
11988
11989 cx.update_editor(|editor, cx| {
11990 editor.cancel(&Cancel, cx);
11991 });
11992
11993 cx.assert_state_with_diff(
11994 r#"
11995 use some::modified;
11996
11997 ˇ
11998 fn main() {
11999 println!("hello there");
12000
12001 println!("around the");
12002 println!("world");
12003 }
12004 "#
12005 .unindent(),
12006 );
12007}
12008
12009#[gpui::test]
12010async fn test_diff_base_change_with_expanded_diff_hunks(
12011 executor: BackgroundExecutor,
12012 cx: &mut gpui::TestAppContext,
12013) {
12014 init_test(cx, |_| {});
12015
12016 let mut cx = EditorTestContext::new(cx).await;
12017
12018 let diff_base = r#"
12019 use some::mod1;
12020 use some::mod2;
12021
12022 const A: u32 = 42;
12023 const B: u32 = 42;
12024 const C: u32 = 42;
12025
12026 fn main() {
12027 println!("hello");
12028
12029 println!("world");
12030 }
12031 "#
12032 .unindent();
12033
12034 cx.set_state(
12035 &r#"
12036 use some::mod2;
12037
12038 const A: u32 = 42;
12039 const C: u32 = 42;
12040
12041 fn main(ˇ) {
12042 //println!("hello");
12043
12044 println!("world");
12045 //
12046 //
12047 }
12048 "#
12049 .unindent(),
12050 );
12051
12052 cx.set_diff_base(&diff_base);
12053 executor.run_until_parked();
12054
12055 cx.update_editor(|editor, cx| {
12056 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12057 });
12058 executor.run_until_parked();
12059 cx.assert_state_with_diff(
12060 r#"
12061 - use some::mod1;
12062 use some::mod2;
12063
12064 const A: u32 = 42;
12065 - const B: u32 = 42;
12066 const C: u32 = 42;
12067
12068 fn main(ˇ) {
12069 - println!("hello");
12070 + //println!("hello");
12071
12072 println!("world");
12073 + //
12074 + //
12075 }
12076 "#
12077 .unindent(),
12078 );
12079
12080 cx.set_diff_base("new diff base!");
12081 executor.run_until_parked();
12082 cx.assert_state_with_diff(
12083 r#"
12084 use some::mod2;
12085
12086 const A: u32 = 42;
12087 const C: u32 = 42;
12088
12089 fn main(ˇ) {
12090 //println!("hello");
12091
12092 println!("world");
12093 //
12094 //
12095 }
12096 "#
12097 .unindent(),
12098 );
12099
12100 cx.update_editor(|editor, cx| {
12101 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12102 });
12103 executor.run_until_parked();
12104 cx.assert_state_with_diff(
12105 r#"
12106 - new diff base!
12107 + use some::mod2;
12108 +
12109 + const A: u32 = 42;
12110 + const C: u32 = 42;
12111 +
12112 + fn main(ˇ) {
12113 + //println!("hello");
12114 +
12115 + println!("world");
12116 + //
12117 + //
12118 + }
12119 "#
12120 .unindent(),
12121 );
12122}
12123
12124#[gpui::test]
12125async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12126 init_test(cx, |_| {});
12127
12128 let mut cx = EditorTestContext::new(cx).await;
12129
12130 let diff_base = r#"
12131 use some::mod1;
12132 use some::mod2;
12133
12134 const A: u32 = 42;
12135 const B: u32 = 42;
12136 const C: u32 = 42;
12137
12138 fn main() {
12139 println!("hello");
12140
12141 println!("world");
12142 }
12143
12144 fn another() {
12145 println!("another");
12146 }
12147
12148 fn another2() {
12149 println!("another2");
12150 }
12151 "#
12152 .unindent();
12153
12154 cx.set_state(
12155 &r#"
12156 «use some::mod2;
12157
12158 const A: u32 = 42;
12159 const C: u32 = 42;
12160
12161 fn main() {
12162 //println!("hello");
12163
12164 println!("world");
12165 //
12166 //ˇ»
12167 }
12168
12169 fn another() {
12170 println!("another");
12171 println!("another");
12172 }
12173
12174 println!("another2");
12175 }
12176 "#
12177 .unindent(),
12178 );
12179
12180 cx.set_diff_base(&diff_base);
12181 executor.run_until_parked();
12182
12183 cx.update_editor(|editor, cx| {
12184 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12185 });
12186 executor.run_until_parked();
12187
12188 cx.assert_state_with_diff(
12189 r#"
12190 - use some::mod1;
12191 «use some::mod2;
12192
12193 const A: u32 = 42;
12194 - const B: u32 = 42;
12195 const C: u32 = 42;
12196
12197 fn main() {
12198 - println!("hello");
12199 + //println!("hello");
12200
12201 println!("world");
12202 + //
12203 + //ˇ»
12204 }
12205
12206 fn another() {
12207 println!("another");
12208 + println!("another");
12209 }
12210
12211 - fn another2() {
12212 println!("another2");
12213 }
12214 "#
12215 .unindent(),
12216 );
12217
12218 // Fold across some of the diff hunks. They should no longer appear expanded.
12219 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12220 cx.executor().run_until_parked();
12221
12222 // Hunks are not shown if their position is within a fold
12223 cx.assert_state_with_diff(
12224 r#"
12225 «use some::mod2;
12226
12227 const A: u32 = 42;
12228 const C: u32 = 42;
12229
12230 fn main() {
12231 //println!("hello");
12232
12233 println!("world");
12234 //
12235 //ˇ»
12236 }
12237
12238 fn another() {
12239 println!("another");
12240 + println!("another");
12241 }
12242
12243 - fn another2() {
12244 println!("another2");
12245 }
12246 "#
12247 .unindent(),
12248 );
12249
12250 cx.update_editor(|editor, cx| {
12251 editor.select_all(&SelectAll, cx);
12252 editor.unfold_lines(&UnfoldLines, cx);
12253 });
12254 cx.executor().run_until_parked();
12255
12256 // The deletions reappear when unfolding.
12257 cx.assert_state_with_diff(
12258 r#"
12259 - use some::mod1;
12260 «use some::mod2;
12261
12262 const A: u32 = 42;
12263 - const B: u32 = 42;
12264 const C: u32 = 42;
12265
12266 fn main() {
12267 - println!("hello");
12268 + //println!("hello");
12269
12270 println!("world");
12271 + //
12272 + //
12273 }
12274
12275 fn another() {
12276 println!("another");
12277 + println!("another");
12278 }
12279
12280 - fn another2() {
12281 println!("another2");
12282 }
12283 ˇ»"#
12284 .unindent(),
12285 );
12286}
12287
12288#[gpui::test]
12289async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12290 init_test(cx, |_| {});
12291
12292 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12293 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12294 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12295 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12296 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12297 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12298
12299 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12300 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12301 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12302
12303 let multi_buffer = cx.new_model(|cx| {
12304 let mut multibuffer = MultiBuffer::new(ReadWrite);
12305 multibuffer.push_excerpts(
12306 buffer_1.clone(),
12307 [
12308 ExcerptRange {
12309 context: Point::new(0, 0)..Point::new(3, 0),
12310 primary: None,
12311 },
12312 ExcerptRange {
12313 context: Point::new(5, 0)..Point::new(7, 0),
12314 primary: None,
12315 },
12316 ExcerptRange {
12317 context: Point::new(9, 0)..Point::new(10, 3),
12318 primary: None,
12319 },
12320 ],
12321 cx,
12322 );
12323 multibuffer.push_excerpts(
12324 buffer_2.clone(),
12325 [
12326 ExcerptRange {
12327 context: Point::new(0, 0)..Point::new(3, 0),
12328 primary: None,
12329 },
12330 ExcerptRange {
12331 context: Point::new(5, 0)..Point::new(7, 0),
12332 primary: None,
12333 },
12334 ExcerptRange {
12335 context: Point::new(9, 0)..Point::new(10, 3),
12336 primary: None,
12337 },
12338 ],
12339 cx,
12340 );
12341 multibuffer.push_excerpts(
12342 buffer_3.clone(),
12343 [
12344 ExcerptRange {
12345 context: Point::new(0, 0)..Point::new(3, 0),
12346 primary: None,
12347 },
12348 ExcerptRange {
12349 context: Point::new(5, 0)..Point::new(7, 0),
12350 primary: None,
12351 },
12352 ExcerptRange {
12353 context: Point::new(9, 0)..Point::new(10, 3),
12354 primary: None,
12355 },
12356 ],
12357 cx,
12358 );
12359 multibuffer
12360 });
12361
12362 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12363 editor
12364 .update(cx, |editor, cx| {
12365 for (buffer, diff_base) in [
12366 (buffer_1.clone(), file_1_old),
12367 (buffer_2.clone(), file_2_old),
12368 (buffer_3.clone(), file_3_old),
12369 ] {
12370 let change_set = cx.new_model(|cx| {
12371 BufferChangeSet::new_with_base_text(
12372 diff_base.to_string(),
12373 buffer.read(cx).text_snapshot(),
12374 cx,
12375 )
12376 });
12377 editor.diff_map.add_change_set(change_set, cx)
12378 }
12379 })
12380 .unwrap();
12381
12382 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12383 cx.run_until_parked();
12384
12385 cx.assert_editor_state(
12386 &"
12387 ˇaaa
12388 ccc
12389 ddd
12390
12391 ggg
12392 hhh
12393
12394
12395 lll
12396 mmm
12397 NNN
12398
12399 qqq
12400 rrr
12401
12402 uuu
12403 111
12404 222
12405 333
12406
12407 666
12408 777
12409
12410 000
12411 !!!"
12412 .unindent(),
12413 );
12414
12415 cx.update_editor(|editor, cx| {
12416 editor.select_all(&SelectAll, cx);
12417 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12418 });
12419 cx.executor().run_until_parked();
12420
12421 cx.assert_state_with_diff(
12422 "
12423 «aaa
12424 - bbb
12425 ccc
12426 ddd
12427
12428 ggg
12429 hhh
12430
12431
12432 lll
12433 mmm
12434 - nnn
12435 + NNN
12436
12437 qqq
12438 rrr
12439
12440 uuu
12441 111
12442 222
12443 333
12444
12445 + 666
12446 777
12447
12448 000
12449 !!!ˇ»"
12450 .unindent(),
12451 );
12452}
12453
12454#[gpui::test]
12455async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12456 init_test(cx, |_| {});
12457
12458 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12459 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12460
12461 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12462 let multi_buffer = cx.new_model(|cx| {
12463 let mut multibuffer = MultiBuffer::new(ReadWrite);
12464 multibuffer.push_excerpts(
12465 buffer.clone(),
12466 [
12467 ExcerptRange {
12468 context: Point::new(0, 0)..Point::new(2, 0),
12469 primary: None,
12470 },
12471 ExcerptRange {
12472 context: Point::new(5, 0)..Point::new(7, 0),
12473 primary: None,
12474 },
12475 ],
12476 cx,
12477 );
12478 multibuffer
12479 });
12480
12481 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12482 editor
12483 .update(cx, |editor, cx| {
12484 let buffer = buffer.read(cx).text_snapshot();
12485 let change_set = cx
12486 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12487 editor.diff_map.add_change_set(change_set, cx)
12488 })
12489 .unwrap();
12490
12491 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12492 cx.run_until_parked();
12493
12494 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12495 cx.executor().run_until_parked();
12496
12497 cx.assert_state_with_diff(
12498 "
12499 ˇaaa
12500 - bbb
12501 + BBB
12502
12503 - ddd
12504 - eee
12505 + EEE
12506 fff
12507 "
12508 .unindent(),
12509 );
12510}
12511
12512#[gpui::test]
12513async fn test_edits_around_expanded_insertion_hunks(
12514 executor: BackgroundExecutor,
12515 cx: &mut gpui::TestAppContext,
12516) {
12517 init_test(cx, |_| {});
12518
12519 let mut cx = EditorTestContext::new(cx).await;
12520
12521 let diff_base = r#"
12522 use some::mod1;
12523 use some::mod2;
12524
12525 const A: u32 = 42;
12526
12527 fn main() {
12528 println!("hello");
12529
12530 println!("world");
12531 }
12532 "#
12533 .unindent();
12534 executor.run_until_parked();
12535 cx.set_state(
12536 &r#"
12537 use some::mod1;
12538 use some::mod2;
12539
12540 const A: u32 = 42;
12541 const B: u32 = 42;
12542 const C: u32 = 42;
12543 ˇ
12544
12545 fn main() {
12546 println!("hello");
12547
12548 println!("world");
12549 }
12550 "#
12551 .unindent(),
12552 );
12553
12554 cx.set_diff_base(&diff_base);
12555 executor.run_until_parked();
12556
12557 cx.update_editor(|editor, cx| {
12558 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12559 });
12560 executor.run_until_parked();
12561
12562 cx.assert_state_with_diff(
12563 r#"
12564 use some::mod1;
12565 use some::mod2;
12566
12567 const A: u32 = 42;
12568 + const B: u32 = 42;
12569 + const C: u32 = 42;
12570 + ˇ
12571
12572 fn main() {
12573 println!("hello");
12574
12575 println!("world");
12576 }
12577 "#
12578 .unindent(),
12579 );
12580
12581 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12582 executor.run_until_parked();
12583
12584 cx.assert_state_with_diff(
12585 r#"
12586 use some::mod1;
12587 use some::mod2;
12588
12589 const A: u32 = 42;
12590 + const B: u32 = 42;
12591 + const C: u32 = 42;
12592 + const D: u32 = 42;
12593 + ˇ
12594
12595 fn main() {
12596 println!("hello");
12597
12598 println!("world");
12599 }
12600 "#
12601 .unindent(),
12602 );
12603
12604 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12605 executor.run_until_parked();
12606
12607 cx.assert_state_with_diff(
12608 r#"
12609 use some::mod1;
12610 use some::mod2;
12611
12612 const A: u32 = 42;
12613 + const B: u32 = 42;
12614 + const C: u32 = 42;
12615 + const D: u32 = 42;
12616 + const E: u32 = 42;
12617 + ˇ
12618
12619 fn main() {
12620 println!("hello");
12621
12622 println!("world");
12623 }
12624 "#
12625 .unindent(),
12626 );
12627
12628 cx.update_editor(|editor, cx| {
12629 editor.delete_line(&DeleteLine, cx);
12630 });
12631 executor.run_until_parked();
12632
12633 cx.assert_state_with_diff(
12634 r#"
12635 use some::mod1;
12636 use some::mod2;
12637
12638 const A: u32 = 42;
12639 + const B: u32 = 42;
12640 + const C: u32 = 42;
12641 + const D: u32 = 42;
12642 + const E: u32 = 42;
12643 ˇ
12644 fn main() {
12645 println!("hello");
12646
12647 println!("world");
12648 }
12649 "#
12650 .unindent(),
12651 );
12652
12653 cx.update_editor(|editor, cx| {
12654 editor.move_up(&MoveUp, cx);
12655 editor.delete_line(&DeleteLine, cx);
12656 editor.move_up(&MoveUp, cx);
12657 editor.delete_line(&DeleteLine, cx);
12658 editor.move_up(&MoveUp, cx);
12659 editor.delete_line(&DeleteLine, cx);
12660 });
12661 executor.run_until_parked();
12662 cx.assert_state_with_diff(
12663 r#"
12664 use some::mod1;
12665 use some::mod2;
12666
12667 const A: u32 = 42;
12668 + const B: u32 = 42;
12669 ˇ
12670 fn main() {
12671 println!("hello");
12672
12673 println!("world");
12674 }
12675 "#
12676 .unindent(),
12677 );
12678
12679 cx.update_editor(|editor, cx| {
12680 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12681 editor.delete_line(&DeleteLine, cx);
12682 });
12683 executor.run_until_parked();
12684 cx.assert_state_with_diff(
12685 r#"
12686 use some::mod1;
12687 - use some::mod2;
12688 -
12689 - const A: u32 = 42;
12690 ˇ
12691 fn main() {
12692 println!("hello");
12693
12694 println!("world");
12695 }
12696 "#
12697 .unindent(),
12698 );
12699}
12700
12701#[gpui::test]
12702async fn test_edits_around_expanded_deletion_hunks(
12703 executor: BackgroundExecutor,
12704 cx: &mut gpui::TestAppContext,
12705) {
12706 init_test(cx, |_| {});
12707
12708 let mut cx = EditorTestContext::new(cx).await;
12709
12710 let diff_base = r#"
12711 use some::mod1;
12712 use some::mod2;
12713
12714 const A: u32 = 42;
12715 const B: u32 = 42;
12716 const C: u32 = 42;
12717
12718
12719 fn main() {
12720 println!("hello");
12721
12722 println!("world");
12723 }
12724 "#
12725 .unindent();
12726 executor.run_until_parked();
12727 cx.set_state(
12728 &r#"
12729 use some::mod1;
12730 use some::mod2;
12731
12732 ˇconst B: u32 = 42;
12733 const C: u32 = 42;
12734
12735
12736 fn main() {
12737 println!("hello");
12738
12739 println!("world");
12740 }
12741 "#
12742 .unindent(),
12743 );
12744
12745 cx.set_diff_base(&diff_base);
12746 executor.run_until_parked();
12747
12748 cx.update_editor(|editor, cx| {
12749 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12750 });
12751 executor.run_until_parked();
12752
12753 cx.assert_state_with_diff(
12754 r#"
12755 use some::mod1;
12756 use some::mod2;
12757
12758 - const A: u32 = 42;
12759 ˇconst B: u32 = 42;
12760 const C: u32 = 42;
12761
12762
12763 fn main() {
12764 println!("hello");
12765
12766 println!("world");
12767 }
12768 "#
12769 .unindent(),
12770 );
12771
12772 cx.update_editor(|editor, cx| {
12773 editor.delete_line(&DeleteLine, cx);
12774 });
12775 executor.run_until_parked();
12776 cx.assert_state_with_diff(
12777 r#"
12778 use some::mod1;
12779 use some::mod2;
12780
12781 - const A: u32 = 42;
12782 - const B: u32 = 42;
12783 ˇconst C: u32 = 42;
12784
12785
12786 fn main() {
12787 println!("hello");
12788
12789 println!("world");
12790 }
12791 "#
12792 .unindent(),
12793 );
12794
12795 cx.update_editor(|editor, cx| {
12796 editor.delete_line(&DeleteLine, cx);
12797 });
12798 executor.run_until_parked();
12799 cx.assert_state_with_diff(
12800 r#"
12801 use some::mod1;
12802 use some::mod2;
12803
12804 - const A: u32 = 42;
12805 - const B: u32 = 42;
12806 - const C: u32 = 42;
12807 ˇ
12808
12809 fn main() {
12810 println!("hello");
12811
12812 println!("world");
12813 }
12814 "#
12815 .unindent(),
12816 );
12817
12818 cx.update_editor(|editor, cx| {
12819 editor.handle_input("replacement", cx);
12820 });
12821 executor.run_until_parked();
12822 cx.assert_state_with_diff(
12823 r#"
12824 use some::mod1;
12825 use some::mod2;
12826
12827 - const A: u32 = 42;
12828 - const B: u32 = 42;
12829 - const C: u32 = 42;
12830 -
12831 + replacementˇ
12832
12833 fn main() {
12834 println!("hello");
12835
12836 println!("world");
12837 }
12838 "#
12839 .unindent(),
12840 );
12841}
12842
12843#[gpui::test]
12844async fn test_edit_after_expanded_modification_hunk(
12845 executor: BackgroundExecutor,
12846 cx: &mut gpui::TestAppContext,
12847) {
12848 init_test(cx, |_| {});
12849
12850 let mut cx = EditorTestContext::new(cx).await;
12851
12852 let diff_base = r#"
12853 use some::mod1;
12854 use some::mod2;
12855
12856 const A: u32 = 42;
12857 const B: u32 = 42;
12858 const C: u32 = 42;
12859 const D: u32 = 42;
12860
12861
12862 fn main() {
12863 println!("hello");
12864
12865 println!("world");
12866 }"#
12867 .unindent();
12868
12869 cx.set_state(
12870 &r#"
12871 use some::mod1;
12872 use some::mod2;
12873
12874 const A: u32 = 42;
12875 const B: u32 = 42;
12876 const C: u32 = 43ˇ
12877 const D: u32 = 42;
12878
12879
12880 fn main() {
12881 println!("hello");
12882
12883 println!("world");
12884 }"#
12885 .unindent(),
12886 );
12887
12888 cx.set_diff_base(&diff_base);
12889 executor.run_until_parked();
12890 cx.update_editor(|editor, cx| {
12891 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12892 });
12893 executor.run_until_parked();
12894
12895 cx.assert_state_with_diff(
12896 r#"
12897 use some::mod1;
12898 use some::mod2;
12899
12900 const A: u32 = 42;
12901 const B: u32 = 42;
12902 - const C: u32 = 42;
12903 + const C: u32 = 43ˇ
12904 const D: u32 = 42;
12905
12906
12907 fn main() {
12908 println!("hello");
12909
12910 println!("world");
12911 }"#
12912 .unindent(),
12913 );
12914
12915 cx.update_editor(|editor, cx| {
12916 editor.handle_input("\nnew_line\n", cx);
12917 });
12918 executor.run_until_parked();
12919
12920 cx.assert_state_with_diff(
12921 r#"
12922 use some::mod1;
12923 use some::mod2;
12924
12925 const A: u32 = 42;
12926 const B: u32 = 42;
12927 - const C: u32 = 42;
12928 + const C: u32 = 43
12929 + new_line
12930 + ˇ
12931 const D: u32 = 42;
12932
12933
12934 fn main() {
12935 println!("hello");
12936
12937 println!("world");
12938 }"#
12939 .unindent(),
12940 );
12941}
12942
12943async fn setup_indent_guides_editor(
12944 text: &str,
12945 cx: &mut gpui::TestAppContext,
12946) -> (BufferId, EditorTestContext) {
12947 init_test(cx, |_| {});
12948
12949 let mut cx = EditorTestContext::new(cx).await;
12950
12951 let buffer_id = cx.update_editor(|editor, cx| {
12952 editor.set_text(text, cx);
12953 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12954
12955 buffer_ids[0]
12956 });
12957
12958 (buffer_id, cx)
12959}
12960
12961fn assert_indent_guides(
12962 range: Range<u32>,
12963 expected: Vec<IndentGuide>,
12964 active_indices: Option<Vec<usize>>,
12965 cx: &mut EditorTestContext,
12966) {
12967 let indent_guides = cx.update_editor(|editor, cx| {
12968 let snapshot = editor.snapshot(cx).display_snapshot;
12969 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12970 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12971 true,
12972 &snapshot,
12973 cx,
12974 );
12975
12976 indent_guides.sort_by(|a, b| {
12977 a.depth.cmp(&b.depth).then(
12978 a.start_row
12979 .cmp(&b.start_row)
12980 .then(a.end_row.cmp(&b.end_row)),
12981 )
12982 });
12983 indent_guides
12984 });
12985
12986 if let Some(expected) = active_indices {
12987 let active_indices = cx.update_editor(|editor, cx| {
12988 let snapshot = editor.snapshot(cx).display_snapshot;
12989 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12990 });
12991
12992 assert_eq!(
12993 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12994 expected,
12995 "Active indent guide indices do not match"
12996 );
12997 }
12998
12999 let expected: Vec<_> = expected
13000 .into_iter()
13001 .map(|guide| MultiBufferIndentGuide {
13002 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13003 buffer: guide,
13004 })
13005 .collect();
13006
13007 assert_eq!(indent_guides, expected, "Indent guides do not match");
13008}
13009
13010fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13011 IndentGuide {
13012 buffer_id,
13013 start_row,
13014 end_row,
13015 depth,
13016 tab_size: 4,
13017 settings: IndentGuideSettings {
13018 enabled: true,
13019 line_width: 1,
13020 active_line_width: 1,
13021 ..Default::default()
13022 },
13023 }
13024}
13025
13026#[gpui::test]
13027async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13028 let (buffer_id, mut cx) = setup_indent_guides_editor(
13029 &"
13030 fn main() {
13031 let a = 1;
13032 }"
13033 .unindent(),
13034 cx,
13035 )
13036 .await;
13037
13038 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13039}
13040
13041#[gpui::test]
13042async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13043 let (buffer_id, mut cx) = setup_indent_guides_editor(
13044 &"
13045 fn main() {
13046 let a = 1;
13047 let b = 2;
13048 }"
13049 .unindent(),
13050 cx,
13051 )
13052 .await;
13053
13054 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13055}
13056
13057#[gpui::test]
13058async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13059 let (buffer_id, mut cx) = setup_indent_guides_editor(
13060 &"
13061 fn main() {
13062 let a = 1;
13063 if a == 3 {
13064 let b = 2;
13065 } else {
13066 let c = 3;
13067 }
13068 }"
13069 .unindent(),
13070 cx,
13071 )
13072 .await;
13073
13074 assert_indent_guides(
13075 0..8,
13076 vec![
13077 indent_guide(buffer_id, 1, 6, 0),
13078 indent_guide(buffer_id, 3, 3, 1),
13079 indent_guide(buffer_id, 5, 5, 1),
13080 ],
13081 None,
13082 &mut cx,
13083 );
13084}
13085
13086#[gpui::test]
13087async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13088 let (buffer_id, mut cx) = setup_indent_guides_editor(
13089 &"
13090 fn main() {
13091 let a = 1;
13092 let b = 2;
13093 let c = 3;
13094 }"
13095 .unindent(),
13096 cx,
13097 )
13098 .await;
13099
13100 assert_indent_guides(
13101 0..5,
13102 vec![
13103 indent_guide(buffer_id, 1, 3, 0),
13104 indent_guide(buffer_id, 2, 2, 1),
13105 ],
13106 None,
13107 &mut cx,
13108 );
13109}
13110
13111#[gpui::test]
13112async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13113 let (buffer_id, mut cx) = setup_indent_guides_editor(
13114 &"
13115 fn main() {
13116 let a = 1;
13117
13118 let c = 3;
13119 }"
13120 .unindent(),
13121 cx,
13122 )
13123 .await;
13124
13125 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13126}
13127
13128#[gpui::test]
13129async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13130 let (buffer_id, mut cx) = setup_indent_guides_editor(
13131 &"
13132 fn main() {
13133 let a = 1;
13134
13135 let c = 3;
13136
13137 if a == 3 {
13138 let b = 2;
13139 } else {
13140 let c = 3;
13141 }
13142 }"
13143 .unindent(),
13144 cx,
13145 )
13146 .await;
13147
13148 assert_indent_guides(
13149 0..11,
13150 vec![
13151 indent_guide(buffer_id, 1, 9, 0),
13152 indent_guide(buffer_id, 6, 6, 1),
13153 indent_guide(buffer_id, 8, 8, 1),
13154 ],
13155 None,
13156 &mut cx,
13157 );
13158}
13159
13160#[gpui::test]
13161async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13162 let (buffer_id, mut cx) = setup_indent_guides_editor(
13163 &"
13164 fn main() {
13165 let a = 1;
13166
13167 let c = 3;
13168
13169 if a == 3 {
13170 let b = 2;
13171 } else {
13172 let c = 3;
13173 }
13174 }"
13175 .unindent(),
13176 cx,
13177 )
13178 .await;
13179
13180 assert_indent_guides(
13181 1..11,
13182 vec![
13183 indent_guide(buffer_id, 1, 9, 0),
13184 indent_guide(buffer_id, 6, 6, 1),
13185 indent_guide(buffer_id, 8, 8, 1),
13186 ],
13187 None,
13188 &mut cx,
13189 );
13190}
13191
13192#[gpui::test]
13193async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13194 let (buffer_id, mut cx) = setup_indent_guides_editor(
13195 &"
13196 fn main() {
13197 let a = 1;
13198
13199 let c = 3;
13200
13201 if a == 3 {
13202 let b = 2;
13203 } else {
13204 let c = 3;
13205 }
13206 }"
13207 .unindent(),
13208 cx,
13209 )
13210 .await;
13211
13212 assert_indent_guides(
13213 1..10,
13214 vec![
13215 indent_guide(buffer_id, 1, 9, 0),
13216 indent_guide(buffer_id, 6, 6, 1),
13217 indent_guide(buffer_id, 8, 8, 1),
13218 ],
13219 None,
13220 &mut cx,
13221 );
13222}
13223
13224#[gpui::test]
13225async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13226 let (buffer_id, mut cx) = setup_indent_guides_editor(
13227 &"
13228 block1
13229 block2
13230 block3
13231 block4
13232 block2
13233 block1
13234 block1"
13235 .unindent(),
13236 cx,
13237 )
13238 .await;
13239
13240 assert_indent_guides(
13241 1..10,
13242 vec![
13243 indent_guide(buffer_id, 1, 4, 0),
13244 indent_guide(buffer_id, 2, 3, 1),
13245 indent_guide(buffer_id, 3, 3, 2),
13246 ],
13247 None,
13248 &mut cx,
13249 );
13250}
13251
13252#[gpui::test]
13253async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13254 let (buffer_id, mut cx) = setup_indent_guides_editor(
13255 &"
13256 block1
13257 block2
13258 block3
13259
13260 block1
13261 block1"
13262 .unindent(),
13263 cx,
13264 )
13265 .await;
13266
13267 assert_indent_guides(
13268 0..6,
13269 vec![
13270 indent_guide(buffer_id, 1, 2, 0),
13271 indent_guide(buffer_id, 2, 2, 1),
13272 ],
13273 None,
13274 &mut cx,
13275 );
13276}
13277
13278#[gpui::test]
13279async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13280 let (buffer_id, mut cx) = setup_indent_guides_editor(
13281 &"
13282 block1
13283
13284
13285
13286 block2
13287 "
13288 .unindent(),
13289 cx,
13290 )
13291 .await;
13292
13293 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13294}
13295
13296#[gpui::test]
13297async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13298 let (buffer_id, mut cx) = setup_indent_guides_editor(
13299 &"
13300 def a:
13301 \tb = 3
13302 \tif True:
13303 \t\tc = 4
13304 \t\td = 5
13305 \tprint(b)
13306 "
13307 .unindent(),
13308 cx,
13309 )
13310 .await;
13311
13312 assert_indent_guides(
13313 0..6,
13314 vec![
13315 indent_guide(buffer_id, 1, 6, 0),
13316 indent_guide(buffer_id, 3, 4, 1),
13317 ],
13318 None,
13319 &mut cx,
13320 );
13321}
13322
13323#[gpui::test]
13324async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13325 let (buffer_id, mut cx) = setup_indent_guides_editor(
13326 &"
13327 fn main() {
13328 let a = 1;
13329 }"
13330 .unindent(),
13331 cx,
13332 )
13333 .await;
13334
13335 cx.update_editor(|editor, cx| {
13336 editor.change_selections(None, cx, |s| {
13337 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13338 });
13339 });
13340
13341 assert_indent_guides(
13342 0..3,
13343 vec![indent_guide(buffer_id, 1, 1, 0)],
13344 Some(vec![0]),
13345 &mut cx,
13346 );
13347}
13348
13349#[gpui::test]
13350async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13351 let (buffer_id, mut cx) = setup_indent_guides_editor(
13352 &"
13353 fn main() {
13354 if 1 == 2 {
13355 let a = 1;
13356 }
13357 }"
13358 .unindent(),
13359 cx,
13360 )
13361 .await;
13362
13363 cx.update_editor(|editor, cx| {
13364 editor.change_selections(None, cx, |s| {
13365 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13366 });
13367 });
13368
13369 assert_indent_guides(
13370 0..4,
13371 vec![
13372 indent_guide(buffer_id, 1, 3, 0),
13373 indent_guide(buffer_id, 2, 2, 1),
13374 ],
13375 Some(vec![1]),
13376 &mut cx,
13377 );
13378
13379 cx.update_editor(|editor, cx| {
13380 editor.change_selections(None, cx, |s| {
13381 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13382 });
13383 });
13384
13385 assert_indent_guides(
13386 0..4,
13387 vec![
13388 indent_guide(buffer_id, 1, 3, 0),
13389 indent_guide(buffer_id, 2, 2, 1),
13390 ],
13391 Some(vec![1]),
13392 &mut cx,
13393 );
13394
13395 cx.update_editor(|editor, cx| {
13396 editor.change_selections(None, cx, |s| {
13397 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13398 });
13399 });
13400
13401 assert_indent_guides(
13402 0..4,
13403 vec![
13404 indent_guide(buffer_id, 1, 3, 0),
13405 indent_guide(buffer_id, 2, 2, 1),
13406 ],
13407 Some(vec![0]),
13408 &mut cx,
13409 );
13410}
13411
13412#[gpui::test]
13413async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13414 let (buffer_id, mut cx) = setup_indent_guides_editor(
13415 &"
13416 fn main() {
13417 let a = 1;
13418
13419 let b = 2;
13420 }"
13421 .unindent(),
13422 cx,
13423 )
13424 .await;
13425
13426 cx.update_editor(|editor, cx| {
13427 editor.change_selections(None, cx, |s| {
13428 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13429 });
13430 });
13431
13432 assert_indent_guides(
13433 0..5,
13434 vec![indent_guide(buffer_id, 1, 3, 0)],
13435 Some(vec![0]),
13436 &mut cx,
13437 );
13438}
13439
13440#[gpui::test]
13441async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13442 let (buffer_id, mut cx) = setup_indent_guides_editor(
13443 &"
13444 def m:
13445 a = 1
13446 pass"
13447 .unindent(),
13448 cx,
13449 )
13450 .await;
13451
13452 cx.update_editor(|editor, cx| {
13453 editor.change_selections(None, cx, |s| {
13454 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13455 });
13456 });
13457
13458 assert_indent_guides(
13459 0..3,
13460 vec![indent_guide(buffer_id, 1, 2, 0)],
13461 Some(vec![0]),
13462 &mut cx,
13463 );
13464}
13465
13466#[gpui::test]
13467fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13468 init_test(cx, |_| {});
13469
13470 let editor = cx.add_window(|cx| {
13471 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13472 build_editor(buffer, cx)
13473 });
13474
13475 let render_args = Arc::new(Mutex::new(None));
13476 let snapshot = editor
13477 .update(cx, |editor, cx| {
13478 let snapshot = editor.buffer().read(cx).snapshot(cx);
13479 let range =
13480 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13481
13482 struct RenderArgs {
13483 row: MultiBufferRow,
13484 folded: bool,
13485 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13486 }
13487
13488 let crease = Crease::inline(
13489 range,
13490 FoldPlaceholder::test(),
13491 {
13492 let toggle_callback = render_args.clone();
13493 move |row, folded, callback, _cx| {
13494 *toggle_callback.lock() = Some(RenderArgs {
13495 row,
13496 folded,
13497 callback,
13498 });
13499 div()
13500 }
13501 },
13502 |_row, _folded, _cx| div(),
13503 );
13504
13505 editor.insert_creases(Some(crease), cx);
13506 let snapshot = editor.snapshot(cx);
13507 let _div =
13508 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13509 snapshot
13510 })
13511 .unwrap();
13512
13513 let render_args = render_args.lock().take().unwrap();
13514 assert_eq!(render_args.row, MultiBufferRow(1));
13515 assert!(!render_args.folded);
13516 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13517
13518 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13519 .unwrap();
13520 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13521 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13522
13523 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13524 .unwrap();
13525 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13526 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13527}
13528
13529#[gpui::test]
13530async fn test_input_text(cx: &mut gpui::TestAppContext) {
13531 init_test(cx, |_| {});
13532 let mut cx = EditorTestContext::new(cx).await;
13533
13534 cx.set_state(
13535 &r#"ˇone
13536 two
13537
13538 three
13539 fourˇ
13540 five
13541
13542 siˇx"#
13543 .unindent(),
13544 );
13545
13546 cx.dispatch_action(HandleInput(String::new()));
13547 cx.assert_editor_state(
13548 &r#"ˇone
13549 two
13550
13551 three
13552 fourˇ
13553 five
13554
13555 siˇx"#
13556 .unindent(),
13557 );
13558
13559 cx.dispatch_action(HandleInput("AAAA".to_string()));
13560 cx.assert_editor_state(
13561 &r#"AAAAˇone
13562 two
13563
13564 three
13565 fourAAAAˇ
13566 five
13567
13568 siAAAAˇx"#
13569 .unindent(),
13570 );
13571}
13572
13573#[gpui::test]
13574async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13575 init_test(cx, |_| {});
13576
13577 let mut cx = EditorTestContext::new(cx).await;
13578 cx.set_state(
13579 r#"let foo = 1;
13580let foo = 2;
13581let foo = 3;
13582let fooˇ = 4;
13583let foo = 5;
13584let foo = 6;
13585let foo = 7;
13586let foo = 8;
13587let foo = 9;
13588let foo = 10;
13589let foo = 11;
13590let foo = 12;
13591let foo = 13;
13592let foo = 14;
13593let foo = 15;"#,
13594 );
13595
13596 cx.update_editor(|e, cx| {
13597 assert_eq!(
13598 e.next_scroll_position,
13599 NextScrollCursorCenterTopBottom::Center,
13600 "Default next scroll direction is center",
13601 );
13602
13603 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13604 assert_eq!(
13605 e.next_scroll_position,
13606 NextScrollCursorCenterTopBottom::Top,
13607 "After center, next scroll direction should be top",
13608 );
13609
13610 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13611 assert_eq!(
13612 e.next_scroll_position,
13613 NextScrollCursorCenterTopBottom::Bottom,
13614 "After top, next scroll direction should be bottom",
13615 );
13616
13617 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13618 assert_eq!(
13619 e.next_scroll_position,
13620 NextScrollCursorCenterTopBottom::Center,
13621 "After bottom, scrolling should start over",
13622 );
13623
13624 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13625 assert_eq!(
13626 e.next_scroll_position,
13627 NextScrollCursorCenterTopBottom::Top,
13628 "Scrolling continues if retriggered fast enough"
13629 );
13630 });
13631
13632 cx.executor()
13633 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13634 cx.executor().run_until_parked();
13635 cx.update_editor(|e, _| {
13636 assert_eq!(
13637 e.next_scroll_position,
13638 NextScrollCursorCenterTopBottom::Center,
13639 "If scrolling is not triggered fast enough, it should reset"
13640 );
13641 });
13642}
13643
13644#[gpui::test]
13645async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13646 init_test(cx, |_| {});
13647 let mut cx = EditorLspTestContext::new_rust(
13648 lsp::ServerCapabilities {
13649 definition_provider: Some(lsp::OneOf::Left(true)),
13650 references_provider: Some(lsp::OneOf::Left(true)),
13651 ..lsp::ServerCapabilities::default()
13652 },
13653 cx,
13654 )
13655 .await;
13656
13657 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13658 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13659 move |params, _| async move {
13660 if empty_go_to_definition {
13661 Ok(None)
13662 } else {
13663 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13664 uri: params.text_document_position_params.text_document.uri,
13665 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13666 })))
13667 }
13668 },
13669 );
13670 let references =
13671 cx.lsp
13672 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13673 Ok(Some(vec![lsp::Location {
13674 uri: params.text_document_position.text_document.uri,
13675 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13676 }]))
13677 });
13678 (go_to_definition, references)
13679 };
13680
13681 cx.set_state(
13682 &r#"fn one() {
13683 let mut a = ˇtwo();
13684 }
13685
13686 fn two() {}"#
13687 .unindent(),
13688 );
13689 set_up_lsp_handlers(false, &mut cx);
13690 let navigated = cx
13691 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13692 .await
13693 .expect("Failed to navigate to definition");
13694 assert_eq!(
13695 navigated,
13696 Navigated::Yes,
13697 "Should have navigated to definition from the GetDefinition response"
13698 );
13699 cx.assert_editor_state(
13700 &r#"fn one() {
13701 let mut a = two();
13702 }
13703
13704 fn «twoˇ»() {}"#
13705 .unindent(),
13706 );
13707
13708 let editors = cx.update_workspace(|workspace, cx| {
13709 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13710 });
13711 cx.update_editor(|_, test_editor_cx| {
13712 assert_eq!(
13713 editors.len(),
13714 1,
13715 "Initially, only one, test, editor should be open in the workspace"
13716 );
13717 assert_eq!(
13718 test_editor_cx.view(),
13719 editors.last().expect("Asserted len is 1")
13720 );
13721 });
13722
13723 set_up_lsp_handlers(true, &mut cx);
13724 let navigated = cx
13725 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13726 .await
13727 .expect("Failed to navigate to lookup references");
13728 assert_eq!(
13729 navigated,
13730 Navigated::Yes,
13731 "Should have navigated to references as a fallback after empty GoToDefinition response"
13732 );
13733 // We should not change the selections in the existing file,
13734 // if opening another milti buffer with the references
13735 cx.assert_editor_state(
13736 &r#"fn one() {
13737 let mut a = two();
13738 }
13739
13740 fn «twoˇ»() {}"#
13741 .unindent(),
13742 );
13743 let editors = cx.update_workspace(|workspace, cx| {
13744 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13745 });
13746 cx.update_editor(|_, test_editor_cx| {
13747 assert_eq!(
13748 editors.len(),
13749 2,
13750 "After falling back to references search, we open a new editor with the results"
13751 );
13752 let references_fallback_text = editors
13753 .into_iter()
13754 .find(|new_editor| new_editor != test_editor_cx.view())
13755 .expect("Should have one non-test editor now")
13756 .read(test_editor_cx)
13757 .text(test_editor_cx);
13758 assert_eq!(
13759 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13760 "Should use the range from the references response and not the GoToDefinition one"
13761 );
13762 });
13763}
13764
13765#[gpui::test]
13766async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13767 init_test(cx, |_| {});
13768
13769 let language = Arc::new(Language::new(
13770 LanguageConfig::default(),
13771 Some(tree_sitter_rust::LANGUAGE.into()),
13772 ));
13773
13774 let text = r#"
13775 #[cfg(test)]
13776 mod tests() {
13777 #[test]
13778 fn runnable_1() {
13779 let a = 1;
13780 }
13781
13782 #[test]
13783 fn runnable_2() {
13784 let a = 1;
13785 let b = 2;
13786 }
13787 }
13788 "#
13789 .unindent();
13790
13791 let fs = FakeFs::new(cx.executor());
13792 fs.insert_file("/file.rs", Default::default()).await;
13793
13794 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13795 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13796 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13797 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13798 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13799
13800 let editor = cx.new_view(|cx| {
13801 Editor::new(
13802 EditorMode::Full,
13803 multi_buffer,
13804 Some(project.clone()),
13805 true,
13806 cx,
13807 )
13808 });
13809
13810 editor.update(cx, |editor, cx| {
13811 editor.tasks.insert(
13812 (buffer.read(cx).remote_id(), 3),
13813 RunnableTasks {
13814 templates: vec![],
13815 offset: MultiBufferOffset(43),
13816 column: 0,
13817 extra_variables: HashMap::default(),
13818 context_range: BufferOffset(43)..BufferOffset(85),
13819 },
13820 );
13821 editor.tasks.insert(
13822 (buffer.read(cx).remote_id(), 8),
13823 RunnableTasks {
13824 templates: vec![],
13825 offset: MultiBufferOffset(86),
13826 column: 0,
13827 extra_variables: HashMap::default(),
13828 context_range: BufferOffset(86)..BufferOffset(191),
13829 },
13830 );
13831
13832 // Test finding task when cursor is inside function body
13833 editor.change_selections(None, cx, |s| {
13834 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13835 });
13836 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13837 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13838
13839 // Test finding task when cursor is on function name
13840 editor.change_selections(None, cx, |s| {
13841 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13842 });
13843 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13844 assert_eq!(row, 8, "Should find task when cursor is on function name");
13845 });
13846}
13847
13848fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13849 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13850 point..point
13851}
13852
13853fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13854 let (text, ranges) = marked_text_ranges(marked_text, true);
13855 assert_eq!(view.text(cx), text);
13856 assert_eq!(
13857 view.selections.ranges(cx),
13858 ranges,
13859 "Assert selections are {}",
13860 marked_text
13861 );
13862}
13863
13864pub fn handle_signature_help_request(
13865 cx: &mut EditorLspTestContext,
13866 mocked_response: lsp::SignatureHelp,
13867) -> impl Future<Output = ()> {
13868 let mut request =
13869 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13870 let mocked_response = mocked_response.clone();
13871 async move { Ok(Some(mocked_response)) }
13872 });
13873
13874 async move {
13875 request.next().await;
13876 }
13877}
13878
13879/// Handle completion request passing a marked string specifying where the completion
13880/// should be triggered from using '|' character, what range should be replaced, and what completions
13881/// should be returned using '<' and '>' to delimit the range
13882pub fn handle_completion_request(
13883 cx: &mut EditorLspTestContext,
13884 marked_string: &str,
13885 completions: Vec<&'static str>,
13886 counter: Arc<AtomicUsize>,
13887) -> impl Future<Output = ()> {
13888 let complete_from_marker: TextRangeMarker = '|'.into();
13889 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13890 let (_, mut marked_ranges) = marked_text_ranges_by(
13891 marked_string,
13892 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13893 );
13894
13895 let complete_from_position =
13896 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13897 let replace_range =
13898 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13899
13900 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13901 let completions = completions.clone();
13902 counter.fetch_add(1, atomic::Ordering::Release);
13903 async move {
13904 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13905 assert_eq!(
13906 params.text_document_position.position,
13907 complete_from_position
13908 );
13909 Ok(Some(lsp::CompletionResponse::Array(
13910 completions
13911 .iter()
13912 .map(|completion_text| lsp::CompletionItem {
13913 label: completion_text.to_string(),
13914 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13915 range: replace_range,
13916 new_text: completion_text.to_string(),
13917 })),
13918 ..Default::default()
13919 })
13920 .collect(),
13921 )))
13922 }
13923 });
13924
13925 async move {
13926 request.next().await;
13927 }
13928}
13929
13930fn handle_resolve_completion_request(
13931 cx: &mut EditorLspTestContext,
13932 edits: Option<Vec<(&'static str, &'static str)>>,
13933) -> impl Future<Output = ()> {
13934 let edits = edits.map(|edits| {
13935 edits
13936 .iter()
13937 .map(|(marked_string, new_text)| {
13938 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13939 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13940 lsp::TextEdit::new(replace_range, new_text.to_string())
13941 })
13942 .collect::<Vec<_>>()
13943 });
13944
13945 let mut request =
13946 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13947 let edits = edits.clone();
13948 async move {
13949 Ok(lsp::CompletionItem {
13950 additional_text_edits: edits,
13951 ..Default::default()
13952 })
13953 }
13954 });
13955
13956 async move {
13957 request.next().await;
13958 }
13959}
13960
13961pub(crate) fn update_test_language_settings(
13962 cx: &mut TestAppContext,
13963 f: impl Fn(&mut AllLanguageSettingsContent),
13964) {
13965 cx.update(|cx| {
13966 SettingsStore::update_global(cx, |store, cx| {
13967 store.update_user_settings::<AllLanguageSettings>(cx, f);
13968 });
13969 });
13970}
13971
13972pub(crate) fn update_test_project_settings(
13973 cx: &mut TestAppContext,
13974 f: impl Fn(&mut ProjectSettings),
13975) {
13976 cx.update(|cx| {
13977 SettingsStore::update_global(cx, |store, cx| {
13978 store.update_user_settings::<ProjectSettings>(cx, f);
13979 });
13980 });
13981}
13982
13983pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13984 cx.update(|cx| {
13985 assets::Assets.load_test_fonts(cx);
13986 let store = SettingsStore::test(cx);
13987 cx.set_global(store);
13988 theme::init(theme::LoadThemes::JustBase, cx);
13989 release_channel::init(SemanticVersion::default(), cx);
13990 client::init_settings(cx);
13991 language::init(cx);
13992 Project::init_settings(cx);
13993 workspace::init_settings(cx);
13994 crate::init(cx);
13995 });
13996
13997 update_test_language_settings(cx, f);
13998}
13999
14000#[track_caller]
14001fn assert_hunk_revert(
14002 not_reverted_text_with_selections: &str,
14003 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14004 expected_reverted_text_with_selections: &str,
14005 base_text: &str,
14006 cx: &mut EditorLspTestContext,
14007) {
14008 cx.set_state(not_reverted_text_with_selections);
14009 cx.set_diff_base(base_text);
14010 cx.executor().run_until_parked();
14011
14012 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14013 let snapshot = editor.snapshot(cx);
14014 let reverted_hunk_statuses = snapshot
14015 .diff_map
14016 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14017 .map(|hunk| hunk_status(&hunk))
14018 .collect::<Vec<_>>();
14019
14020 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14021 reverted_hunk_statuses
14022 });
14023 cx.executor().run_until_parked();
14024 cx.assert_editor_state(expected_reverted_text_with_selections);
14025 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14026}