1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
24 LanguageName, Override, ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(
2106 &DeleteToPreviousWordStart {
2107 ignore_newlines: false,
2108 },
2109 cx,
2110 );
2111 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2112 });
2113
2114 _ = view.update(cx, |view, cx| {
2115 view.change_selections(None, cx, |s| {
2116 s.select_display_ranges([
2117 // an empty selection - the following word fragment is deleted
2118 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2119 // characters selected - they are deleted
2120 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2121 ])
2122 });
2123 view.delete_to_next_word_end(
2124 &DeleteToNextWordEnd {
2125 ignore_newlines: false,
2126 },
2127 cx,
2128 );
2129 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2130 });
2131}
2132
2133#[gpui::test]
2134fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2135 init_test(cx, |_| {});
2136
2137 let view = cx.add_window(|cx| {
2138 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2139 build_editor(buffer.clone(), cx)
2140 });
2141 let del_to_prev_word_start = DeleteToPreviousWordStart {
2142 ignore_newlines: false,
2143 };
2144 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2145 ignore_newlines: true,
2146 };
2147
2148 _ = view.update(cx, |view, cx| {
2149 view.change_selections(None, cx, |s| {
2150 s.select_display_ranges([
2151 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2152 ])
2153 });
2154 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2155 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2156 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2157 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2158 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2159 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2160 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2161 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2162 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2163 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2164 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2165 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2166 });
2167}
2168
2169#[gpui::test]
2170fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2171 init_test(cx, |_| {});
2172
2173 let view = cx.add_window(|cx| {
2174 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2175 build_editor(buffer.clone(), cx)
2176 });
2177 let del_to_next_word_end = DeleteToNextWordEnd {
2178 ignore_newlines: false,
2179 };
2180 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2181 ignore_newlines: true,
2182 };
2183
2184 _ = view.update(cx, |view, cx| {
2185 view.change_selections(None, cx, |s| {
2186 s.select_display_ranges([
2187 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2188 ])
2189 });
2190 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2191 assert_eq!(
2192 view.buffer.read(cx).read(cx).text(),
2193 "one\n two\nthree\n four"
2194 );
2195 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2196 assert_eq!(
2197 view.buffer.read(cx).read(cx).text(),
2198 "\n two\nthree\n four"
2199 );
2200 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2201 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2202 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2203 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2204 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2205 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2206 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2207 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2208 });
2209}
2210
2211#[gpui::test]
2212fn test_newline(cx: &mut TestAppContext) {
2213 init_test(cx, |_| {});
2214
2215 let view = cx.add_window(|cx| {
2216 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2217 build_editor(buffer.clone(), cx)
2218 });
2219
2220 _ = view.update(cx, |view, cx| {
2221 view.change_selections(None, cx, |s| {
2222 s.select_display_ranges([
2223 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2224 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2225 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2226 ])
2227 });
2228
2229 view.newline(&Newline, cx);
2230 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2231 });
2232}
2233
2234#[gpui::test]
2235fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2236 init_test(cx, |_| {});
2237
2238 let editor = cx.add_window(|cx| {
2239 let buffer = MultiBuffer::build_simple(
2240 "
2241 a
2242 b(
2243 X
2244 )
2245 c(
2246 X
2247 )
2248 "
2249 .unindent()
2250 .as_str(),
2251 cx,
2252 );
2253 let mut editor = build_editor(buffer.clone(), cx);
2254 editor.change_selections(None, cx, |s| {
2255 s.select_ranges([
2256 Point::new(2, 4)..Point::new(2, 5),
2257 Point::new(5, 4)..Point::new(5, 5),
2258 ])
2259 });
2260 editor
2261 });
2262
2263 _ = editor.update(cx, |editor, cx| {
2264 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2265 editor.buffer.update(cx, |buffer, cx| {
2266 buffer.edit(
2267 [
2268 (Point::new(1, 2)..Point::new(3, 0), ""),
2269 (Point::new(4, 2)..Point::new(6, 0), ""),
2270 ],
2271 None,
2272 cx,
2273 );
2274 assert_eq!(
2275 buffer.read(cx).text(),
2276 "
2277 a
2278 b()
2279 c()
2280 "
2281 .unindent()
2282 );
2283 });
2284 assert_eq!(
2285 editor.selections.ranges(cx),
2286 &[
2287 Point::new(1, 2)..Point::new(1, 2),
2288 Point::new(2, 2)..Point::new(2, 2),
2289 ],
2290 );
2291
2292 editor.newline(&Newline, cx);
2293 assert_eq!(
2294 editor.text(cx),
2295 "
2296 a
2297 b(
2298 )
2299 c(
2300 )
2301 "
2302 .unindent()
2303 );
2304
2305 // The selections are moved after the inserted newlines
2306 assert_eq!(
2307 editor.selections.ranges(cx),
2308 &[
2309 Point::new(2, 0)..Point::new(2, 0),
2310 Point::new(4, 0)..Point::new(4, 0),
2311 ],
2312 );
2313 });
2314}
2315
2316#[gpui::test]
2317async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2318 init_test(cx, |settings| {
2319 settings.defaults.tab_size = NonZeroU32::new(4)
2320 });
2321
2322 let language = Arc::new(
2323 Language::new(
2324 LanguageConfig::default(),
2325 Some(tree_sitter_rust::LANGUAGE.into()),
2326 )
2327 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2328 .unwrap(),
2329 );
2330
2331 let mut cx = EditorTestContext::new(cx).await;
2332 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2333 cx.set_state(indoc! {"
2334 const a: ˇA = (
2335 (ˇ
2336 «const_functionˇ»(ˇ),
2337 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2338 )ˇ
2339 ˇ);ˇ
2340 "});
2341
2342 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2343 cx.assert_editor_state(indoc! {"
2344 ˇ
2345 const a: A = (
2346 ˇ
2347 (
2348 ˇ
2349 ˇ
2350 const_function(),
2351 ˇ
2352 ˇ
2353 ˇ
2354 ˇ
2355 something_else,
2356 ˇ
2357 )
2358 ˇ
2359 ˇ
2360 );
2361 "});
2362}
2363
2364#[gpui::test]
2365async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2366 init_test(cx, |settings| {
2367 settings.defaults.tab_size = NonZeroU32::new(4)
2368 });
2369
2370 let language = Arc::new(
2371 Language::new(
2372 LanguageConfig::default(),
2373 Some(tree_sitter_rust::LANGUAGE.into()),
2374 )
2375 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2376 .unwrap(),
2377 );
2378
2379 let mut cx = EditorTestContext::new(cx).await;
2380 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2381 cx.set_state(indoc! {"
2382 const a: ˇA = (
2383 (ˇ
2384 «const_functionˇ»(ˇ),
2385 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2386 )ˇ
2387 ˇ);ˇ
2388 "});
2389
2390 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2391 cx.assert_editor_state(indoc! {"
2392 const a: A = (
2393 ˇ
2394 (
2395 ˇ
2396 const_function(),
2397 ˇ
2398 ˇ
2399 something_else,
2400 ˇ
2401 ˇ
2402 ˇ
2403 ˇ
2404 )
2405 ˇ
2406 );
2407 ˇ
2408 ˇ
2409 "});
2410}
2411
2412#[gpui::test]
2413async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2414 init_test(cx, |settings| {
2415 settings.defaults.tab_size = NonZeroU32::new(4)
2416 });
2417
2418 let language = Arc::new(Language::new(
2419 LanguageConfig {
2420 line_comments: vec!["//".into()],
2421 ..LanguageConfig::default()
2422 },
2423 None,
2424 ));
2425 {
2426 let mut cx = EditorTestContext::new(cx).await;
2427 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2428 cx.set_state(indoc! {"
2429 // Fooˇ
2430 "});
2431
2432 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2433 cx.assert_editor_state(indoc! {"
2434 // Foo
2435 //ˇ
2436 "});
2437 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2438 cx.set_state(indoc! {"
2439 ˇ// Foo
2440 "});
2441 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2442 cx.assert_editor_state(indoc! {"
2443
2444 ˇ// Foo
2445 "});
2446 }
2447 // Ensure that comment continuations can be disabled.
2448 update_test_language_settings(cx, |settings| {
2449 settings.defaults.extend_comment_on_newline = Some(false);
2450 });
2451 let mut cx = EditorTestContext::new(cx).await;
2452 cx.set_state(indoc! {"
2453 // Fooˇ
2454 "});
2455 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2456 cx.assert_editor_state(indoc! {"
2457 // Foo
2458 ˇ
2459 "});
2460}
2461
2462#[gpui::test]
2463fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2464 init_test(cx, |_| {});
2465
2466 let editor = cx.add_window(|cx| {
2467 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2468 let mut editor = build_editor(buffer.clone(), cx);
2469 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2470 editor
2471 });
2472
2473 _ = editor.update(cx, |editor, cx| {
2474 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2475 editor.buffer.update(cx, |buffer, cx| {
2476 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2477 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2478 });
2479 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2480
2481 editor.insert("Z", cx);
2482 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2483
2484 // The selections are moved after the inserted characters
2485 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2486 });
2487}
2488
2489#[gpui::test]
2490async fn test_tab(cx: &mut gpui::TestAppContext) {
2491 init_test(cx, |settings| {
2492 settings.defaults.tab_size = NonZeroU32::new(3)
2493 });
2494
2495 let mut cx = EditorTestContext::new(cx).await;
2496 cx.set_state(indoc! {"
2497 ˇabˇc
2498 ˇ🏀ˇ🏀ˇefg
2499 dˇ
2500 "});
2501 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2502 cx.assert_editor_state(indoc! {"
2503 ˇab ˇc
2504 ˇ🏀 ˇ🏀 ˇefg
2505 d ˇ
2506 "});
2507
2508 cx.set_state(indoc! {"
2509 a
2510 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2511 "});
2512 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2513 cx.assert_editor_state(indoc! {"
2514 a
2515 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2516 "});
2517}
2518
2519#[gpui::test]
2520async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2521 init_test(cx, |_| {});
2522
2523 let mut cx = EditorTestContext::new(cx).await;
2524 let language = Arc::new(
2525 Language::new(
2526 LanguageConfig::default(),
2527 Some(tree_sitter_rust::LANGUAGE.into()),
2528 )
2529 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2530 .unwrap(),
2531 );
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533
2534 // cursors that are already at the suggested indent level insert
2535 // a soft tab. cursors that are to the left of the suggested indent
2536 // auto-indent their line.
2537 cx.set_state(indoc! {"
2538 ˇ
2539 const a: B = (
2540 c(
2541 d(
2542 ˇ
2543 )
2544 ˇ
2545 ˇ )
2546 );
2547 "});
2548 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2549 cx.assert_editor_state(indoc! {"
2550 ˇ
2551 const a: B = (
2552 c(
2553 d(
2554 ˇ
2555 )
2556 ˇ
2557 ˇ)
2558 );
2559 "});
2560
2561 // handle auto-indent when there are multiple cursors on the same line
2562 cx.set_state(indoc! {"
2563 const a: B = (
2564 c(
2565 ˇ ˇ
2566 ˇ )
2567 );
2568 "});
2569 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2570 cx.assert_editor_state(indoc! {"
2571 const a: B = (
2572 c(
2573 ˇ
2574 ˇ)
2575 );
2576 "});
2577}
2578
2579#[gpui::test]
2580async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2581 init_test(cx, |settings| {
2582 settings.defaults.tab_size = NonZeroU32::new(4)
2583 });
2584
2585 let language = Arc::new(
2586 Language::new(
2587 LanguageConfig::default(),
2588 Some(tree_sitter_rust::LANGUAGE.into()),
2589 )
2590 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2591 .unwrap(),
2592 );
2593
2594 let mut cx = EditorTestContext::new(cx).await;
2595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2596 cx.set_state(indoc! {"
2597 fn a() {
2598 if b {
2599 \t ˇc
2600 }
2601 }
2602 "});
2603
2604 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2605 cx.assert_editor_state(indoc! {"
2606 fn a() {
2607 if b {
2608 ˇc
2609 }
2610 }
2611 "});
2612}
2613
2614#[gpui::test]
2615async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2616 init_test(cx, |settings| {
2617 settings.defaults.tab_size = NonZeroU32::new(4);
2618 });
2619
2620 let mut cx = EditorTestContext::new(cx).await;
2621
2622 cx.set_state(indoc! {"
2623 «oneˇ» «twoˇ»
2624 three
2625 four
2626 "});
2627 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2628 cx.assert_editor_state(indoc! {"
2629 «oneˇ» «twoˇ»
2630 three
2631 four
2632 "});
2633
2634 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2635 cx.assert_editor_state(indoc! {"
2636 «oneˇ» «twoˇ»
2637 three
2638 four
2639 "});
2640
2641 // select across line ending
2642 cx.set_state(indoc! {"
2643 one two
2644 t«hree
2645 ˇ» four
2646 "});
2647 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2648 cx.assert_editor_state(indoc! {"
2649 one two
2650 t«hree
2651 ˇ» four
2652 "});
2653
2654 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2655 cx.assert_editor_state(indoc! {"
2656 one two
2657 t«hree
2658 ˇ» four
2659 "});
2660
2661 // Ensure that indenting/outdenting works when the cursor is at column 0.
2662 cx.set_state(indoc! {"
2663 one two
2664 ˇthree
2665 four
2666 "});
2667 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2668 cx.assert_editor_state(indoc! {"
2669 one two
2670 ˇthree
2671 four
2672 "});
2673
2674 cx.set_state(indoc! {"
2675 one two
2676 ˇ three
2677 four
2678 "});
2679 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2680 cx.assert_editor_state(indoc! {"
2681 one two
2682 ˇthree
2683 four
2684 "});
2685}
2686
2687#[gpui::test]
2688async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2689 init_test(cx, |settings| {
2690 settings.defaults.hard_tabs = Some(true);
2691 });
2692
2693 let mut cx = EditorTestContext::new(cx).await;
2694
2695 // select two ranges on one line
2696 cx.set_state(indoc! {"
2697 «oneˇ» «twoˇ»
2698 three
2699 four
2700 "});
2701 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2702 cx.assert_editor_state(indoc! {"
2703 \t«oneˇ» «twoˇ»
2704 three
2705 four
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 \t\t«oneˇ» «twoˇ»
2710 three
2711 four
2712 "});
2713 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2714 cx.assert_editor_state(indoc! {"
2715 \t«oneˇ» «twoˇ»
2716 three
2717 four
2718 "});
2719 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2720 cx.assert_editor_state(indoc! {"
2721 «oneˇ» «twoˇ»
2722 three
2723 four
2724 "});
2725
2726 // select across a line ending
2727 cx.set_state(indoc! {"
2728 one two
2729 t«hree
2730 ˇ»four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 one two
2735 \tt«hree
2736 ˇ»four
2737 "});
2738 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2739 cx.assert_editor_state(indoc! {"
2740 one two
2741 \t\tt«hree
2742 ˇ»four
2743 "});
2744 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2745 cx.assert_editor_state(indoc! {"
2746 one two
2747 \tt«hree
2748 ˇ»four
2749 "});
2750 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2751 cx.assert_editor_state(indoc! {"
2752 one two
2753 t«hree
2754 ˇ»four
2755 "});
2756
2757 // Ensure that indenting/outdenting works when the cursor is at column 0.
2758 cx.set_state(indoc! {"
2759 one two
2760 ˇthree
2761 four
2762 "});
2763 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2764 cx.assert_editor_state(indoc! {"
2765 one two
2766 ˇthree
2767 four
2768 "});
2769 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2770 cx.assert_editor_state(indoc! {"
2771 one two
2772 \tˇthree
2773 four
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
2783#[gpui::test]
2784fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2785 init_test(cx, |settings| {
2786 settings.languages.extend([
2787 (
2788 "TOML".into(),
2789 LanguageSettingsContent {
2790 tab_size: NonZeroU32::new(2),
2791 ..Default::default()
2792 },
2793 ),
2794 (
2795 "Rust".into(),
2796 LanguageSettingsContent {
2797 tab_size: NonZeroU32::new(4),
2798 ..Default::default()
2799 },
2800 ),
2801 ]);
2802 });
2803
2804 let toml_language = Arc::new(Language::new(
2805 LanguageConfig {
2806 name: "TOML".into(),
2807 ..Default::default()
2808 },
2809 None,
2810 ));
2811 let rust_language = Arc::new(Language::new(
2812 LanguageConfig {
2813 name: "Rust".into(),
2814 ..Default::default()
2815 },
2816 None,
2817 ));
2818
2819 let toml_buffer =
2820 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2821 let rust_buffer = cx.new_model(|cx| {
2822 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2823 });
2824 let multibuffer = cx.new_model(|cx| {
2825 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2826 multibuffer.push_excerpts(
2827 toml_buffer.clone(),
2828 [ExcerptRange {
2829 context: Point::new(0, 0)..Point::new(2, 0),
2830 primary: None,
2831 }],
2832 cx,
2833 );
2834 multibuffer.push_excerpts(
2835 rust_buffer.clone(),
2836 [ExcerptRange {
2837 context: Point::new(0, 0)..Point::new(1, 0),
2838 primary: None,
2839 }],
2840 cx,
2841 );
2842 multibuffer
2843 });
2844
2845 cx.add_window(|cx| {
2846 let mut editor = build_editor(multibuffer, cx);
2847
2848 assert_eq!(
2849 editor.text(cx),
2850 indoc! {"
2851 a = 1
2852 b = 2
2853
2854 const c: usize = 3;
2855 "}
2856 );
2857
2858 select_ranges(
2859 &mut editor,
2860 indoc! {"
2861 «aˇ» = 1
2862 b = 2
2863
2864 «const c:ˇ» usize = 3;
2865 "},
2866 cx,
2867 );
2868
2869 editor.tab(&Tab, cx);
2870 assert_text_with_selections(
2871 &mut editor,
2872 indoc! {"
2873 «aˇ» = 1
2874 b = 2
2875
2876 «const c:ˇ» usize = 3;
2877 "},
2878 cx,
2879 );
2880 editor.tab_prev(&TabPrev, cx);
2881 assert_text_with_selections(
2882 &mut editor,
2883 indoc! {"
2884 «aˇ» = 1
2885 b = 2
2886
2887 «const c:ˇ» usize = 3;
2888 "},
2889 cx,
2890 );
2891
2892 editor
2893 });
2894}
2895
2896#[gpui::test]
2897async fn test_backspace(cx: &mut gpui::TestAppContext) {
2898 init_test(cx, |_| {});
2899
2900 let mut cx = EditorTestContext::new(cx).await;
2901
2902 // Basic backspace
2903 cx.set_state(indoc! {"
2904 onˇe two three
2905 fou«rˇ» five six
2906 seven «ˇeight nine
2907 »ten
2908 "});
2909 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2910 cx.assert_editor_state(indoc! {"
2911 oˇe two three
2912 fouˇ five six
2913 seven ˇten
2914 "});
2915
2916 // Test backspace inside and around indents
2917 cx.set_state(indoc! {"
2918 zero
2919 ˇone
2920 ˇtwo
2921 ˇ ˇ ˇ three
2922 ˇ ˇ four
2923 "});
2924 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2925 cx.assert_editor_state(indoc! {"
2926 zero
2927 ˇone
2928 ˇtwo
2929 ˇ threeˇ four
2930 "});
2931
2932 // Test backspace with line_mode set to true
2933 cx.update_editor(|e, _| e.selections.line_mode = true);
2934 cx.set_state(indoc! {"
2935 The ˇquick ˇbrown
2936 fox jumps over
2937 the lazy dog
2938 ˇThe qu«ick bˇ»rown"});
2939 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2940 cx.assert_editor_state(indoc! {"
2941 ˇfox jumps over
2942 the lazy dogˇ"});
2943}
2944
2945#[gpui::test]
2946async fn test_delete(cx: &mut gpui::TestAppContext) {
2947 init_test(cx, |_| {});
2948
2949 let mut cx = EditorTestContext::new(cx).await;
2950 cx.set_state(indoc! {"
2951 onˇe two three
2952 fou«rˇ» five six
2953 seven «ˇeight nine
2954 »ten
2955 "});
2956 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2957 cx.assert_editor_state(indoc! {"
2958 onˇ two three
2959 fouˇ five six
2960 seven ˇten
2961 "});
2962
2963 // Test backspace with line_mode set to true
2964 cx.update_editor(|e, _| e.selections.line_mode = true);
2965 cx.set_state(indoc! {"
2966 The ˇquick ˇbrown
2967 fox «ˇjum»ps over
2968 the lazy dog
2969 ˇThe qu«ick bˇ»rown"});
2970 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2971 cx.assert_editor_state("ˇthe lazy dogˇ");
2972}
2973
2974#[gpui::test]
2975fn test_delete_line(cx: &mut TestAppContext) {
2976 init_test(cx, |_| {});
2977
2978 let view = cx.add_window(|cx| {
2979 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2980 build_editor(buffer, cx)
2981 });
2982 _ = view.update(cx, |view, cx| {
2983 view.change_selections(None, cx, |s| {
2984 s.select_display_ranges([
2985 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2986 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2987 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2988 ])
2989 });
2990 view.delete_line(&DeleteLine, cx);
2991 assert_eq!(view.display_text(cx), "ghi");
2992 assert_eq!(
2993 view.selections.display_ranges(cx),
2994 vec![
2995 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2996 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2997 ]
2998 );
2999 });
3000
3001 let view = cx.add_window(|cx| {
3002 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3003 build_editor(buffer, cx)
3004 });
3005 _ = view.update(cx, |view, cx| {
3006 view.change_selections(None, cx, |s| {
3007 s.select_display_ranges([
3008 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3009 ])
3010 });
3011 view.delete_line(&DeleteLine, cx);
3012 assert_eq!(view.display_text(cx), "ghi\n");
3013 assert_eq!(
3014 view.selections.display_ranges(cx),
3015 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3016 );
3017 });
3018}
3019
3020#[gpui::test]
3021fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3022 init_test(cx, |_| {});
3023
3024 cx.add_window(|cx| {
3025 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3026 let mut editor = build_editor(buffer.clone(), cx);
3027 let buffer = buffer.read(cx).as_singleton().unwrap();
3028
3029 assert_eq!(
3030 editor.selections.ranges::<Point>(cx),
3031 &[Point::new(0, 0)..Point::new(0, 0)]
3032 );
3033
3034 // When on single line, replace newline at end by space
3035 editor.join_lines(&JoinLines, cx);
3036 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3037 assert_eq!(
3038 editor.selections.ranges::<Point>(cx),
3039 &[Point::new(0, 3)..Point::new(0, 3)]
3040 );
3041
3042 // When multiple lines are selected, remove newlines that are spanned by the selection
3043 editor.change_selections(None, cx, |s| {
3044 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3045 });
3046 editor.join_lines(&JoinLines, cx);
3047 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3048 assert_eq!(
3049 editor.selections.ranges::<Point>(cx),
3050 &[Point::new(0, 11)..Point::new(0, 11)]
3051 );
3052
3053 // Undo should be transactional
3054 editor.undo(&Undo, cx);
3055 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3056 assert_eq!(
3057 editor.selections.ranges::<Point>(cx),
3058 &[Point::new(0, 5)..Point::new(2, 2)]
3059 );
3060
3061 // When joining an empty line don't insert a space
3062 editor.change_selections(None, cx, |s| {
3063 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3064 });
3065 editor.join_lines(&JoinLines, cx);
3066 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3067 assert_eq!(
3068 editor.selections.ranges::<Point>(cx),
3069 [Point::new(2, 3)..Point::new(2, 3)]
3070 );
3071
3072 // We can remove trailing newlines
3073 editor.join_lines(&JoinLines, cx);
3074 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3075 assert_eq!(
3076 editor.selections.ranges::<Point>(cx),
3077 [Point::new(2, 3)..Point::new(2, 3)]
3078 );
3079
3080 // We don't blow up on the last line
3081 editor.join_lines(&JoinLines, cx);
3082 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3083 assert_eq!(
3084 editor.selections.ranges::<Point>(cx),
3085 [Point::new(2, 3)..Point::new(2, 3)]
3086 );
3087
3088 // reset to test indentation
3089 editor.buffer.update(cx, |buffer, cx| {
3090 buffer.edit(
3091 [
3092 (Point::new(1, 0)..Point::new(1, 2), " "),
3093 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3094 ],
3095 None,
3096 cx,
3097 )
3098 });
3099
3100 // We remove any leading spaces
3101 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3102 editor.change_selections(None, cx, |s| {
3103 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3104 });
3105 editor.join_lines(&JoinLines, cx);
3106 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3107
3108 // We don't insert a space for a line containing only spaces
3109 editor.join_lines(&JoinLines, cx);
3110 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3111
3112 // We ignore any leading tabs
3113 editor.join_lines(&JoinLines, cx);
3114 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3115
3116 editor
3117 });
3118}
3119
3120#[gpui::test]
3121fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3122 init_test(cx, |_| {});
3123
3124 cx.add_window(|cx| {
3125 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3126 let mut editor = build_editor(buffer.clone(), cx);
3127 let buffer = buffer.read(cx).as_singleton().unwrap();
3128
3129 editor.change_selections(None, cx, |s| {
3130 s.select_ranges([
3131 Point::new(0, 2)..Point::new(1, 1),
3132 Point::new(1, 2)..Point::new(1, 2),
3133 Point::new(3, 1)..Point::new(3, 2),
3134 ])
3135 });
3136
3137 editor.join_lines(&JoinLines, cx);
3138 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3139
3140 assert_eq!(
3141 editor.selections.ranges::<Point>(cx),
3142 [
3143 Point::new(0, 7)..Point::new(0, 7),
3144 Point::new(1, 3)..Point::new(1, 3)
3145 ]
3146 );
3147 editor
3148 });
3149}
3150
3151#[gpui::test]
3152async fn test_join_lines_with_git_diff_base(
3153 executor: BackgroundExecutor,
3154 cx: &mut gpui::TestAppContext,
3155) {
3156 init_test(cx, |_| {});
3157
3158 let mut cx = EditorTestContext::new(cx).await;
3159
3160 let diff_base = r#"
3161 Line 0
3162 Line 1
3163 Line 2
3164 Line 3
3165 "#
3166 .unindent();
3167
3168 cx.set_state(
3169 &r#"
3170 ˇLine 0
3171 Line 1
3172 Line 2
3173 Line 3
3174 "#
3175 .unindent(),
3176 );
3177
3178 cx.set_diff_base(Some(&diff_base));
3179 executor.run_until_parked();
3180
3181 // Join lines
3182 cx.update_editor(|editor, cx| {
3183 editor.join_lines(&JoinLines, cx);
3184 });
3185 executor.run_until_parked();
3186
3187 cx.assert_editor_state(
3188 &r#"
3189 Line 0ˇ Line 1
3190 Line 2
3191 Line 3
3192 "#
3193 .unindent(),
3194 );
3195 // Join again
3196 cx.update_editor(|editor, cx| {
3197 editor.join_lines(&JoinLines, cx);
3198 });
3199 executor.run_until_parked();
3200
3201 cx.assert_editor_state(
3202 &r#"
3203 Line 0 Line 1ˇ Line 2
3204 Line 3
3205 "#
3206 .unindent(),
3207 );
3208}
3209
3210#[gpui::test]
3211async fn test_custom_newlines_cause_no_false_positive_diffs(
3212 executor: BackgroundExecutor,
3213 cx: &mut gpui::TestAppContext,
3214) {
3215 init_test(cx, |_| {});
3216 let mut cx = EditorTestContext::new(cx).await;
3217 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3218 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3219 executor.run_until_parked();
3220
3221 cx.update_editor(|editor, cx| {
3222 assert_eq!(
3223 editor
3224 .buffer()
3225 .read(cx)
3226 .snapshot(cx)
3227 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3228 .collect::<Vec<_>>(),
3229 Vec::new(),
3230 "Should not have any diffs for files with custom newlines"
3231 );
3232 });
3233}
3234
3235#[gpui::test]
3236async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3237 init_test(cx, |_| {});
3238
3239 let mut cx = EditorTestContext::new(cx).await;
3240
3241 // Test sort_lines_case_insensitive()
3242 cx.set_state(indoc! {"
3243 «z
3244 y
3245 x
3246 Z
3247 Y
3248 Xˇ»
3249 "});
3250 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3251 cx.assert_editor_state(indoc! {"
3252 «x
3253 X
3254 y
3255 Y
3256 z
3257 Zˇ»
3258 "});
3259
3260 // Test reverse_lines()
3261 cx.set_state(indoc! {"
3262 «5
3263 4
3264 3
3265 2
3266 1ˇ»
3267 "});
3268 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3269 cx.assert_editor_state(indoc! {"
3270 «1
3271 2
3272 3
3273 4
3274 5ˇ»
3275 "});
3276
3277 // Skip testing shuffle_line()
3278
3279 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3280 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3281
3282 // Don't manipulate when cursor is on single line, but expand the selection
3283 cx.set_state(indoc! {"
3284 ddˇdd
3285 ccc
3286 bb
3287 a
3288 "});
3289 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3290 cx.assert_editor_state(indoc! {"
3291 «ddddˇ»
3292 ccc
3293 bb
3294 a
3295 "});
3296
3297 // Basic manipulate case
3298 // Start selection moves to column 0
3299 // End of selection shrinks to fit shorter line
3300 cx.set_state(indoc! {"
3301 dd«d
3302 ccc
3303 bb
3304 aaaaaˇ»
3305 "});
3306 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3307 cx.assert_editor_state(indoc! {"
3308 «aaaaa
3309 bb
3310 ccc
3311 dddˇ»
3312 "});
3313
3314 // Manipulate case with newlines
3315 cx.set_state(indoc! {"
3316 dd«d
3317 ccc
3318
3319 bb
3320 aaaaa
3321
3322 ˇ»
3323 "});
3324 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3325 cx.assert_editor_state(indoc! {"
3326 «
3327
3328 aaaaa
3329 bb
3330 ccc
3331 dddˇ»
3332
3333 "});
3334
3335 // Adding new line
3336 cx.set_state(indoc! {"
3337 aa«a
3338 bbˇ»b
3339 "});
3340 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3341 cx.assert_editor_state(indoc! {"
3342 «aaa
3343 bbb
3344 added_lineˇ»
3345 "});
3346
3347 // Removing line
3348 cx.set_state(indoc! {"
3349 aa«a
3350 bbbˇ»
3351 "});
3352 cx.update_editor(|e, cx| {
3353 e.manipulate_lines(cx, |lines| {
3354 lines.pop();
3355 })
3356 });
3357 cx.assert_editor_state(indoc! {"
3358 «aaaˇ»
3359 "});
3360
3361 // Removing all lines
3362 cx.set_state(indoc! {"
3363 aa«a
3364 bbbˇ»
3365 "});
3366 cx.update_editor(|e, cx| {
3367 e.manipulate_lines(cx, |lines| {
3368 lines.drain(..);
3369 })
3370 });
3371 cx.assert_editor_state(indoc! {"
3372 ˇ
3373 "});
3374}
3375
3376#[gpui::test]
3377async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3378 init_test(cx, |_| {});
3379
3380 let mut cx = EditorTestContext::new(cx).await;
3381
3382 // Consider continuous selection as single selection
3383 cx.set_state(indoc! {"
3384 Aaa«aa
3385 cˇ»c«c
3386 bb
3387 aaaˇ»aa
3388 "});
3389 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3390 cx.assert_editor_state(indoc! {"
3391 «Aaaaa
3392 ccc
3393 bb
3394 aaaaaˇ»
3395 "});
3396
3397 cx.set_state(indoc! {"
3398 Aaa«aa
3399 cˇ»c«c
3400 bb
3401 aaaˇ»aa
3402 "});
3403 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3404 cx.assert_editor_state(indoc! {"
3405 «Aaaaa
3406 ccc
3407 bbˇ»
3408 "});
3409
3410 // Consider non continuous selection as distinct dedup operations
3411 cx.set_state(indoc! {"
3412 «aaaaa
3413 bb
3414 aaaaa
3415 aaaaaˇ»
3416
3417 aaa«aaˇ»
3418 "});
3419 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3420 cx.assert_editor_state(indoc! {"
3421 «aaaaa
3422 bbˇ»
3423
3424 «aaaaaˇ»
3425 "});
3426}
3427
3428#[gpui::test]
3429async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3430 init_test(cx, |_| {});
3431
3432 let mut cx = EditorTestContext::new(cx).await;
3433
3434 cx.set_state(indoc! {"
3435 «Aaa
3436 aAa
3437 Aaaˇ»
3438 "});
3439 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3440 cx.assert_editor_state(indoc! {"
3441 «Aaa
3442 aAaˇ»
3443 "});
3444
3445 cx.set_state(indoc! {"
3446 «Aaa
3447 aAa
3448 aaAˇ»
3449 "});
3450 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3451 cx.assert_editor_state(indoc! {"
3452 «Aaaˇ»
3453 "});
3454}
3455
3456#[gpui::test]
3457async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3458 init_test(cx, |_| {});
3459
3460 let mut cx = EditorTestContext::new(cx).await;
3461
3462 // Manipulate with multiple selections on a single line
3463 cx.set_state(indoc! {"
3464 dd«dd
3465 cˇ»c«c
3466 bb
3467 aaaˇ»aa
3468 "});
3469 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3470 cx.assert_editor_state(indoc! {"
3471 «aaaaa
3472 bb
3473 ccc
3474 ddddˇ»
3475 "});
3476
3477 // Manipulate with multiple disjoin selections
3478 cx.set_state(indoc! {"
3479 5«
3480 4
3481 3
3482 2
3483 1ˇ»
3484
3485 dd«dd
3486 ccc
3487 bb
3488 aaaˇ»aa
3489 "});
3490 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3491 cx.assert_editor_state(indoc! {"
3492 «1
3493 2
3494 3
3495 4
3496 5ˇ»
3497
3498 «aaaaa
3499 bb
3500 ccc
3501 ddddˇ»
3502 "});
3503
3504 // Adding lines on each selection
3505 cx.set_state(indoc! {"
3506 2«
3507 1ˇ»
3508
3509 bb«bb
3510 aaaˇ»aa
3511 "});
3512 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3513 cx.assert_editor_state(indoc! {"
3514 «2
3515 1
3516 added lineˇ»
3517
3518 «bbbb
3519 aaaaa
3520 added lineˇ»
3521 "});
3522
3523 // Removing lines on each selection
3524 cx.set_state(indoc! {"
3525 2«
3526 1ˇ»
3527
3528 bb«bb
3529 aaaˇ»aa
3530 "});
3531 cx.update_editor(|e, cx| {
3532 e.manipulate_lines(cx, |lines| {
3533 lines.pop();
3534 })
3535 });
3536 cx.assert_editor_state(indoc! {"
3537 «2ˇ»
3538
3539 «bbbbˇ»
3540 "});
3541}
3542
3543#[gpui::test]
3544async fn test_manipulate_text(cx: &mut TestAppContext) {
3545 init_test(cx, |_| {});
3546
3547 let mut cx = EditorTestContext::new(cx).await;
3548
3549 // Test convert_to_upper_case()
3550 cx.set_state(indoc! {"
3551 «hello worldˇ»
3552 "});
3553 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3554 cx.assert_editor_state(indoc! {"
3555 «HELLO WORLDˇ»
3556 "});
3557
3558 // Test convert_to_lower_case()
3559 cx.set_state(indoc! {"
3560 «HELLO WORLDˇ»
3561 "});
3562 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3563 cx.assert_editor_state(indoc! {"
3564 «hello worldˇ»
3565 "});
3566
3567 // Test multiple line, single selection case
3568 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3569 cx.set_state(indoc! {"
3570 «The quick brown
3571 fox jumps over
3572 the lazy dogˇ»
3573 "});
3574 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «The Quick Brown
3577 Fox Jumps Over
3578 The Lazy Dogˇ»
3579 "});
3580
3581 // Test multiple line, single selection case
3582 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3583 cx.set_state(indoc! {"
3584 «The quick brown
3585 fox jumps over
3586 the lazy dogˇ»
3587 "});
3588 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3589 cx.assert_editor_state(indoc! {"
3590 «TheQuickBrown
3591 FoxJumpsOver
3592 TheLazyDogˇ»
3593 "});
3594
3595 // From here on out, test more complex cases of manipulate_text()
3596
3597 // Test no selection case - should affect words cursors are in
3598 // Cursor at beginning, middle, and end of word
3599 cx.set_state(indoc! {"
3600 ˇhello big beauˇtiful worldˇ
3601 "});
3602 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3603 cx.assert_editor_state(indoc! {"
3604 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3605 "});
3606
3607 // Test multiple selections on a single line and across multiple lines
3608 cx.set_state(indoc! {"
3609 «Theˇ» quick «brown
3610 foxˇ» jumps «overˇ»
3611 the «lazyˇ» dog
3612 "});
3613 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3614 cx.assert_editor_state(indoc! {"
3615 «THEˇ» quick «BROWN
3616 FOXˇ» jumps «OVERˇ»
3617 the «LAZYˇ» dog
3618 "});
3619
3620 // Test case where text length grows
3621 cx.set_state(indoc! {"
3622 «tschüߡ»
3623 "});
3624 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3625 cx.assert_editor_state(indoc! {"
3626 «TSCHÜSSˇ»
3627 "});
3628
3629 // Test to make sure we don't crash when text shrinks
3630 cx.set_state(indoc! {"
3631 aaa_bbbˇ
3632 "});
3633 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3634 cx.assert_editor_state(indoc! {"
3635 «aaaBbbˇ»
3636 "});
3637
3638 // Test to make sure we all aware of the fact that each word can grow and shrink
3639 // Final selections should be aware of this fact
3640 cx.set_state(indoc! {"
3641 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3642 "});
3643 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3644 cx.assert_editor_state(indoc! {"
3645 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3646 "});
3647
3648 cx.set_state(indoc! {"
3649 «hElLo, WoRld!ˇ»
3650 "});
3651 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3652 cx.assert_editor_state(indoc! {"
3653 «HeLlO, wOrLD!ˇ»
3654 "});
3655}
3656
3657#[gpui::test]
3658fn test_duplicate_line(cx: &mut TestAppContext) {
3659 init_test(cx, |_| {});
3660
3661 let view = cx.add_window(|cx| {
3662 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3663 build_editor(buffer, cx)
3664 });
3665 _ = view.update(cx, |view, cx| {
3666 view.change_selections(None, cx, |s| {
3667 s.select_display_ranges([
3668 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3669 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3670 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3671 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3672 ])
3673 });
3674 view.duplicate_line_down(&DuplicateLineDown, cx);
3675 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3676 assert_eq!(
3677 view.selections.display_ranges(cx),
3678 vec![
3679 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3680 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3681 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3682 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3683 ]
3684 );
3685 });
3686
3687 let view = cx.add_window(|cx| {
3688 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3689 build_editor(buffer, cx)
3690 });
3691 _ = view.update(cx, |view, cx| {
3692 view.change_selections(None, cx, |s| {
3693 s.select_display_ranges([
3694 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3695 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3696 ])
3697 });
3698 view.duplicate_line_down(&DuplicateLineDown, cx);
3699 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3700 assert_eq!(
3701 view.selections.display_ranges(cx),
3702 vec![
3703 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3704 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3705 ]
3706 );
3707 });
3708
3709 // With `move_upwards` the selections stay in place, except for
3710 // the lines inserted above them
3711 let view = cx.add_window(|cx| {
3712 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3713 build_editor(buffer, cx)
3714 });
3715 _ = view.update(cx, |view, cx| {
3716 view.change_selections(None, cx, |s| {
3717 s.select_display_ranges([
3718 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3719 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3720 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3721 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3722 ])
3723 });
3724 view.duplicate_line_up(&DuplicateLineUp, cx);
3725 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3726 assert_eq!(
3727 view.selections.display_ranges(cx),
3728 vec![
3729 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3730 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3731 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3732 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3733 ]
3734 );
3735 });
3736
3737 let view = cx.add_window(|cx| {
3738 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3739 build_editor(buffer, cx)
3740 });
3741 _ = view.update(cx, |view, cx| {
3742 view.change_selections(None, cx, |s| {
3743 s.select_display_ranges([
3744 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3745 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3746 ])
3747 });
3748 view.duplicate_line_up(&DuplicateLineUp, cx);
3749 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3750 assert_eq!(
3751 view.selections.display_ranges(cx),
3752 vec![
3753 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3754 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3755 ]
3756 );
3757 });
3758}
3759
3760#[gpui::test]
3761fn test_move_line_up_down(cx: &mut TestAppContext) {
3762 init_test(cx, |_| {});
3763
3764 let view = cx.add_window(|cx| {
3765 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3766 build_editor(buffer, cx)
3767 });
3768 _ = view.update(cx, |view, cx| {
3769 view.fold_ranges(
3770 vec![
3771 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3772 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3773 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3774 ],
3775 true,
3776 cx,
3777 );
3778 view.change_selections(None, cx, |s| {
3779 s.select_display_ranges([
3780 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3781 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3782 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3783 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3784 ])
3785 });
3786 assert_eq!(
3787 view.display_text(cx),
3788 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3789 );
3790
3791 view.move_line_up(&MoveLineUp, cx);
3792 assert_eq!(
3793 view.display_text(cx),
3794 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3795 );
3796 assert_eq!(
3797 view.selections.display_ranges(cx),
3798 vec![
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3800 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3801 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3802 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3803 ]
3804 );
3805 });
3806
3807 _ = view.update(cx, |view, cx| {
3808 view.move_line_down(&MoveLineDown, cx);
3809 assert_eq!(
3810 view.display_text(cx),
3811 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3812 );
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3818 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3819 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3820 ]
3821 );
3822 });
3823
3824 _ = view.update(cx, |view, cx| {
3825 view.move_line_down(&MoveLineDown, cx);
3826 assert_eq!(
3827 view.display_text(cx),
3828 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3829 );
3830 assert_eq!(
3831 view.selections.display_ranges(cx),
3832 vec![
3833 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3834 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3835 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3836 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3837 ]
3838 );
3839 });
3840
3841 _ = view.update(cx, |view, cx| {
3842 view.move_line_up(&MoveLineUp, cx);
3843 assert_eq!(
3844 view.display_text(cx),
3845 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3846 );
3847 assert_eq!(
3848 view.selections.display_ranges(cx),
3849 vec![
3850 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3851 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3852 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3853 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3854 ]
3855 );
3856 });
3857}
3858
3859#[gpui::test]
3860fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3861 init_test(cx, |_| {});
3862
3863 let editor = cx.add_window(|cx| {
3864 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3865 build_editor(buffer, cx)
3866 });
3867 _ = editor.update(cx, |editor, cx| {
3868 let snapshot = editor.buffer.read(cx).snapshot(cx);
3869 editor.insert_blocks(
3870 [BlockProperties {
3871 style: BlockStyle::Fixed,
3872 position: snapshot.anchor_after(Point::new(2, 0)),
3873 disposition: BlockDisposition::Below,
3874 height: 1,
3875 render: Box::new(|_| div().into_any()),
3876 priority: 0,
3877 }],
3878 Some(Autoscroll::fit()),
3879 cx,
3880 );
3881 editor.change_selections(None, cx, |s| {
3882 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3883 });
3884 editor.move_line_down(&MoveLineDown, cx);
3885 });
3886}
3887
3888#[gpui::test]
3889fn test_transpose(cx: &mut TestAppContext) {
3890 init_test(cx, |_| {});
3891
3892 _ = cx.add_window(|cx| {
3893 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3894 editor.set_style(EditorStyle::default(), cx);
3895 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3896 editor.transpose(&Default::default(), cx);
3897 assert_eq!(editor.text(cx), "bac");
3898 assert_eq!(editor.selections.ranges(cx), [2..2]);
3899
3900 editor.transpose(&Default::default(), cx);
3901 assert_eq!(editor.text(cx), "bca");
3902 assert_eq!(editor.selections.ranges(cx), [3..3]);
3903
3904 editor.transpose(&Default::default(), cx);
3905 assert_eq!(editor.text(cx), "bac");
3906 assert_eq!(editor.selections.ranges(cx), [3..3]);
3907
3908 editor
3909 });
3910
3911 _ = cx.add_window(|cx| {
3912 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3913 editor.set_style(EditorStyle::default(), cx);
3914 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3915 editor.transpose(&Default::default(), cx);
3916 assert_eq!(editor.text(cx), "acb\nde");
3917 assert_eq!(editor.selections.ranges(cx), [3..3]);
3918
3919 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3920 editor.transpose(&Default::default(), cx);
3921 assert_eq!(editor.text(cx), "acbd\ne");
3922 assert_eq!(editor.selections.ranges(cx), [5..5]);
3923
3924 editor.transpose(&Default::default(), cx);
3925 assert_eq!(editor.text(cx), "acbde\n");
3926 assert_eq!(editor.selections.ranges(cx), [6..6]);
3927
3928 editor.transpose(&Default::default(), cx);
3929 assert_eq!(editor.text(cx), "acbd\ne");
3930 assert_eq!(editor.selections.ranges(cx), [6..6]);
3931
3932 editor
3933 });
3934
3935 _ = cx.add_window(|cx| {
3936 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3937 editor.set_style(EditorStyle::default(), cx);
3938 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3939 editor.transpose(&Default::default(), cx);
3940 assert_eq!(editor.text(cx), "bacd\ne");
3941 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3942
3943 editor.transpose(&Default::default(), cx);
3944 assert_eq!(editor.text(cx), "bcade\n");
3945 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3946
3947 editor.transpose(&Default::default(), cx);
3948 assert_eq!(editor.text(cx), "bcda\ne");
3949 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3950
3951 editor.transpose(&Default::default(), cx);
3952 assert_eq!(editor.text(cx), "bcade\n");
3953 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3954
3955 editor.transpose(&Default::default(), cx);
3956 assert_eq!(editor.text(cx), "bcaed\n");
3957 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3958
3959 editor
3960 });
3961
3962 _ = cx.add_window(|cx| {
3963 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3964 editor.set_style(EditorStyle::default(), cx);
3965 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3966 editor.transpose(&Default::default(), cx);
3967 assert_eq!(editor.text(cx), "🏀🍐✋");
3968 assert_eq!(editor.selections.ranges(cx), [8..8]);
3969
3970 editor.transpose(&Default::default(), cx);
3971 assert_eq!(editor.text(cx), "🏀✋🍐");
3972 assert_eq!(editor.selections.ranges(cx), [11..11]);
3973
3974 editor.transpose(&Default::default(), cx);
3975 assert_eq!(editor.text(cx), "🏀🍐✋");
3976 assert_eq!(editor.selections.ranges(cx), [11..11]);
3977
3978 editor
3979 });
3980}
3981
3982#[gpui::test]
3983async fn test_rewrap(cx: &mut TestAppContext) {
3984 init_test(cx, |_| {});
3985
3986 let mut cx = EditorTestContext::new(cx).await;
3987
3988 {
3989 let language = Arc::new(Language::new(
3990 LanguageConfig {
3991 line_comments: vec!["// ".into()],
3992 ..LanguageConfig::default()
3993 },
3994 None,
3995 ));
3996 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3997
3998 let unwrapped_text = indoc! {"
3999 // ˇ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.
4000 "};
4001
4002 let wrapped_text = indoc! {"
4003 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4004 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4005 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4006 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4007 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4008 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4009 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4010 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4011 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4012 // porttitor id. Aliquam id accumsan eros.ˇ
4013 "};
4014
4015 cx.set_state(unwrapped_text);
4016 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4017 cx.assert_editor_state(wrapped_text);
4018 }
4019
4020 // Test that cursors that expand to the same region are collapsed.
4021 {
4022 let language = Arc::new(Language::new(
4023 LanguageConfig {
4024 line_comments: vec!["// ".into()],
4025 ..LanguageConfig::default()
4026 },
4027 None,
4028 ));
4029 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4030
4031 let unwrapped_text = indoc! {"
4032 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4033 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4034 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4035 // ˇ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.
4036 "};
4037
4038 let wrapped_text = indoc! {"
4039 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4040 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4041 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4042 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4043 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4044 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4045 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4046 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4047 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4048 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4049 "};
4050
4051 cx.set_state(unwrapped_text);
4052 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4053 cx.assert_editor_state(wrapped_text);
4054 }
4055
4056 // Test that non-contiguous selections are treated separately.
4057 {
4058 let language = Arc::new(Language::new(
4059 LanguageConfig {
4060 line_comments: vec!["// ".into()],
4061 ..LanguageConfig::default()
4062 },
4063 None,
4064 ));
4065 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4066
4067 let unwrapped_text = indoc! {"
4068 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4069 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4070 //
4071 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4072 // ˇ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.
4073 "};
4074
4075 let wrapped_text = indoc! {"
4076 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4077 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4078 // auctor, eu lacinia sapien scelerisque.ˇˇ
4079 //
4080 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4081 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4082 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4083 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4084 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4085 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4086 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4087 "};
4088
4089 cx.set_state(unwrapped_text);
4090 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4091 cx.assert_editor_state(wrapped_text);
4092 }
4093
4094 // Test that different comment prefixes are supported.
4095 {
4096 let language = Arc::new(Language::new(
4097 LanguageConfig {
4098 line_comments: vec!["# ".into()],
4099 ..LanguageConfig::default()
4100 },
4101 None,
4102 ));
4103 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4104
4105 let unwrapped_text = indoc! {"
4106 # ˇ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.
4107 "};
4108
4109 let wrapped_text = indoc! {"
4110 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4111 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4112 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4113 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4114 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4115 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4116 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4117 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4118 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4119 # accumsan eros.ˇ
4120 "};
4121
4122 cx.set_state(unwrapped_text);
4123 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4124 cx.assert_editor_state(wrapped_text);
4125 }
4126
4127 // Test that rewrapping is ignored outside of comments in most languages.
4128 {
4129 let language = Arc::new(Language::new(
4130 LanguageConfig {
4131 line_comments: vec!["// ".into(), "/// ".into()],
4132 ..LanguageConfig::default()
4133 },
4134 Some(tree_sitter_rust::LANGUAGE.into()),
4135 ));
4136 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4137
4138 let unwrapped_text = indoc! {"
4139 /// Adds two numbers.
4140 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4141 fn add(a: u32, b: u32) -> u32 {
4142 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ˇ
4143 }
4144 "};
4145
4146 let wrapped_text = indoc! {"
4147 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4148 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4149 fn add(a: u32, b: u32) -> u32 {
4150 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ˇ
4151 }
4152 "};
4153
4154 cx.set_state(unwrapped_text);
4155 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4156 cx.assert_editor_state(wrapped_text);
4157 }
4158
4159 // Test that rewrapping works in Markdown and Plain Text languages.
4160 {
4161 let markdown_language = Arc::new(Language::new(
4162 LanguageConfig {
4163 name: "Markdown".into(),
4164 ..LanguageConfig::default()
4165 },
4166 None,
4167 ));
4168 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4169
4170 let unwrapped_text = indoc! {"
4171 # Hello
4172
4173 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.
4174 "};
4175
4176 let wrapped_text = indoc! {"
4177 # Hello
4178
4179 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4180 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4181 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4182 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4183 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4184 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4185 Integer sit amet scelerisque nisi.ˇ
4186 "};
4187
4188 cx.set_state(unwrapped_text);
4189 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4190 cx.assert_editor_state(wrapped_text);
4191
4192 let plaintext_language = Arc::new(Language::new(
4193 LanguageConfig {
4194 name: "Plain Text".into(),
4195 ..LanguageConfig::default()
4196 },
4197 None,
4198 ));
4199 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4200
4201 let unwrapped_text = indoc! {"
4202 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.
4203 "};
4204
4205 let wrapped_text = indoc! {"
4206 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4207 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4208 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4209 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4210 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4211 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4212 Integer sit amet scelerisque nisi.ˇ
4213 "};
4214
4215 cx.set_state(unwrapped_text);
4216 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4217 cx.assert_editor_state(wrapped_text);
4218 }
4219}
4220
4221#[gpui::test]
4222async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4223 init_test(cx, |_| {});
4224
4225 let mut cx = EditorTestContext::new(cx).await;
4226
4227 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4228 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4229 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4230
4231 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4232 cx.set_state("two ˇfour ˇsix ˇ");
4233 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4234 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4235
4236 // Paste again but with only two cursors. Since the number of cursors doesn't
4237 // match the number of slices in the clipboard, the entire clipboard text
4238 // is pasted at each cursor.
4239 cx.set_state("ˇtwo one✅ four three six five ˇ");
4240 cx.update_editor(|e, cx| {
4241 e.handle_input("( ", cx);
4242 e.paste(&Paste, cx);
4243 e.handle_input(") ", cx);
4244 });
4245 cx.assert_editor_state(
4246 &([
4247 "( one✅ ",
4248 "three ",
4249 "five ) ˇtwo one✅ four three six five ( one✅ ",
4250 "three ",
4251 "five ) ˇ",
4252 ]
4253 .join("\n")),
4254 );
4255
4256 // Cut with three selections, one of which is full-line.
4257 cx.set_state(indoc! {"
4258 1«2ˇ»3
4259 4ˇ567
4260 «8ˇ»9"});
4261 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4262 cx.assert_editor_state(indoc! {"
4263 1ˇ3
4264 ˇ9"});
4265
4266 // Paste with three selections, noticing how the copied selection that was full-line
4267 // gets inserted before the second cursor.
4268 cx.set_state(indoc! {"
4269 1ˇ3
4270 9ˇ
4271 «oˇ»ne"});
4272 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4273 cx.assert_editor_state(indoc! {"
4274 12ˇ3
4275 4567
4276 9ˇ
4277 8ˇne"});
4278
4279 // Copy with a single cursor only, which writes the whole line into the clipboard.
4280 cx.set_state(indoc! {"
4281 The quick brown
4282 fox juˇmps over
4283 the lazy dog"});
4284 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4285 assert_eq!(
4286 cx.read_from_clipboard()
4287 .and_then(|item| item.text().as_deref().map(str::to_string)),
4288 Some("fox jumps over\n".to_string())
4289 );
4290
4291 // Paste with three selections, noticing how the copied full-line selection is inserted
4292 // before the empty selections but replaces the selection that is non-empty.
4293 cx.set_state(indoc! {"
4294 Tˇhe quick brown
4295 «foˇ»x jumps over
4296 tˇhe lazy dog"});
4297 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4298 cx.assert_editor_state(indoc! {"
4299 fox jumps over
4300 Tˇhe quick brown
4301 fox jumps over
4302 ˇx jumps over
4303 fox jumps over
4304 tˇhe lazy dog"});
4305}
4306
4307#[gpui::test]
4308async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4309 init_test(cx, |_| {});
4310
4311 let mut cx = EditorTestContext::new(cx).await;
4312 let language = Arc::new(Language::new(
4313 LanguageConfig::default(),
4314 Some(tree_sitter_rust::LANGUAGE.into()),
4315 ));
4316 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4317
4318 // Cut an indented block, without the leading whitespace.
4319 cx.set_state(indoc! {"
4320 const a: B = (
4321 c(),
4322 «d(
4323 e,
4324 f
4325 )ˇ»
4326 );
4327 "});
4328 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4329 cx.assert_editor_state(indoc! {"
4330 const a: B = (
4331 c(),
4332 ˇ
4333 );
4334 "});
4335
4336 // Paste it at the same position.
4337 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4338 cx.assert_editor_state(indoc! {"
4339 const a: B = (
4340 c(),
4341 d(
4342 e,
4343 f
4344 )ˇ
4345 );
4346 "});
4347
4348 // Paste it at a line with a lower indent level.
4349 cx.set_state(indoc! {"
4350 ˇ
4351 const a: B = (
4352 c(),
4353 );
4354 "});
4355 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4356 cx.assert_editor_state(indoc! {"
4357 d(
4358 e,
4359 f
4360 )ˇ
4361 const a: B = (
4362 c(),
4363 );
4364 "});
4365
4366 // Cut an indented block, with the leading whitespace.
4367 cx.set_state(indoc! {"
4368 const a: B = (
4369 c(),
4370 « d(
4371 e,
4372 f
4373 )
4374 ˇ»);
4375 "});
4376 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4377 cx.assert_editor_state(indoc! {"
4378 const a: B = (
4379 c(),
4380 ˇ);
4381 "});
4382
4383 // Paste it at the same position.
4384 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4385 cx.assert_editor_state(indoc! {"
4386 const a: B = (
4387 c(),
4388 d(
4389 e,
4390 f
4391 )
4392 ˇ);
4393 "});
4394
4395 // Paste it at a line with a higher indent level.
4396 cx.set_state(indoc! {"
4397 const a: B = (
4398 c(),
4399 d(
4400 e,
4401 fˇ
4402 )
4403 );
4404 "});
4405 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4406 cx.assert_editor_state(indoc! {"
4407 const a: B = (
4408 c(),
4409 d(
4410 e,
4411 f d(
4412 e,
4413 f
4414 )
4415 ˇ
4416 )
4417 );
4418 "});
4419}
4420
4421#[gpui::test]
4422fn test_select_all(cx: &mut TestAppContext) {
4423 init_test(cx, |_| {});
4424
4425 let view = cx.add_window(|cx| {
4426 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4427 build_editor(buffer, cx)
4428 });
4429 _ = view.update(cx, |view, cx| {
4430 view.select_all(&SelectAll, cx);
4431 assert_eq!(
4432 view.selections.display_ranges(cx),
4433 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4434 );
4435 });
4436}
4437
4438#[gpui::test]
4439fn test_select_line(cx: &mut TestAppContext) {
4440 init_test(cx, |_| {});
4441
4442 let view = cx.add_window(|cx| {
4443 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4444 build_editor(buffer, cx)
4445 });
4446 _ = view.update(cx, |view, cx| {
4447 view.change_selections(None, cx, |s| {
4448 s.select_display_ranges([
4449 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4450 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4451 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4452 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4453 ])
4454 });
4455 view.select_line(&SelectLine, cx);
4456 assert_eq!(
4457 view.selections.display_ranges(cx),
4458 vec![
4459 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4460 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4461 ]
4462 );
4463 });
4464
4465 _ = view.update(cx, |view, cx| {
4466 view.select_line(&SelectLine, cx);
4467 assert_eq!(
4468 view.selections.display_ranges(cx),
4469 vec![
4470 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4471 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4472 ]
4473 );
4474 });
4475
4476 _ = view.update(cx, |view, cx| {
4477 view.select_line(&SelectLine, cx);
4478 assert_eq!(
4479 view.selections.display_ranges(cx),
4480 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4481 );
4482 });
4483}
4484
4485#[gpui::test]
4486fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4487 init_test(cx, |_| {});
4488
4489 let view = cx.add_window(|cx| {
4490 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4491 build_editor(buffer, cx)
4492 });
4493 _ = view.update(cx, |view, cx| {
4494 view.fold_ranges(
4495 vec![
4496 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4497 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4498 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4499 ],
4500 true,
4501 cx,
4502 );
4503 view.change_selections(None, cx, |s| {
4504 s.select_display_ranges([
4505 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4506 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4507 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4508 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4509 ])
4510 });
4511 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4512 });
4513
4514 _ = view.update(cx, |view, cx| {
4515 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4516 assert_eq!(
4517 view.display_text(cx),
4518 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4519 );
4520 assert_eq!(
4521 view.selections.display_ranges(cx),
4522 [
4523 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4524 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4525 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4526 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4527 ]
4528 );
4529 });
4530
4531 _ = view.update(cx, |view, cx| {
4532 view.change_selections(None, cx, |s| {
4533 s.select_display_ranges([
4534 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4535 ])
4536 });
4537 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4538 assert_eq!(
4539 view.display_text(cx),
4540 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4541 );
4542 assert_eq!(
4543 view.selections.display_ranges(cx),
4544 [
4545 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4546 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4547 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4548 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4549 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4550 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4551 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4552 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4553 ]
4554 );
4555 });
4556}
4557
4558#[gpui::test]
4559async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4560 init_test(cx, |_| {});
4561
4562 let mut cx = EditorTestContext::new(cx).await;
4563
4564 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4565 cx.set_state(indoc!(
4566 r#"abc
4567 defˇghi
4568
4569 jk
4570 nlmo
4571 "#
4572 ));
4573
4574 cx.update_editor(|editor, cx| {
4575 editor.add_selection_above(&Default::default(), cx);
4576 });
4577
4578 cx.assert_editor_state(indoc!(
4579 r#"abcˇ
4580 defˇghi
4581
4582 jk
4583 nlmo
4584 "#
4585 ));
4586
4587 cx.update_editor(|editor, cx| {
4588 editor.add_selection_above(&Default::default(), cx);
4589 });
4590
4591 cx.assert_editor_state(indoc!(
4592 r#"abcˇ
4593 defˇghi
4594
4595 jk
4596 nlmo
4597 "#
4598 ));
4599
4600 cx.update_editor(|view, cx| {
4601 view.add_selection_below(&Default::default(), cx);
4602 });
4603
4604 cx.assert_editor_state(indoc!(
4605 r#"abc
4606 defˇghi
4607
4608 jk
4609 nlmo
4610 "#
4611 ));
4612
4613 cx.update_editor(|view, cx| {
4614 view.undo_selection(&Default::default(), cx);
4615 });
4616
4617 cx.assert_editor_state(indoc!(
4618 r#"abcˇ
4619 defˇghi
4620
4621 jk
4622 nlmo
4623 "#
4624 ));
4625
4626 cx.update_editor(|view, cx| {
4627 view.redo_selection(&Default::default(), cx);
4628 });
4629
4630 cx.assert_editor_state(indoc!(
4631 r#"abc
4632 defˇghi
4633
4634 jk
4635 nlmo
4636 "#
4637 ));
4638
4639 cx.update_editor(|view, cx| {
4640 view.add_selection_below(&Default::default(), cx);
4641 });
4642
4643 cx.assert_editor_state(indoc!(
4644 r#"abc
4645 defˇghi
4646
4647 jk
4648 nlmˇo
4649 "#
4650 ));
4651
4652 cx.update_editor(|view, cx| {
4653 view.add_selection_below(&Default::default(), cx);
4654 });
4655
4656 cx.assert_editor_state(indoc!(
4657 r#"abc
4658 defˇghi
4659
4660 jk
4661 nlmˇo
4662 "#
4663 ));
4664
4665 // change selections
4666 cx.set_state(indoc!(
4667 r#"abc
4668 def«ˇg»hi
4669
4670 jk
4671 nlmo
4672 "#
4673 ));
4674
4675 cx.update_editor(|view, cx| {
4676 view.add_selection_below(&Default::default(), cx);
4677 });
4678
4679 cx.assert_editor_state(indoc!(
4680 r#"abc
4681 def«ˇg»hi
4682
4683 jk
4684 nlm«ˇo»
4685 "#
4686 ));
4687
4688 cx.update_editor(|view, cx| {
4689 view.add_selection_below(&Default::default(), cx);
4690 });
4691
4692 cx.assert_editor_state(indoc!(
4693 r#"abc
4694 def«ˇg»hi
4695
4696 jk
4697 nlm«ˇo»
4698 "#
4699 ));
4700
4701 cx.update_editor(|view, cx| {
4702 view.add_selection_above(&Default::default(), cx);
4703 });
4704
4705 cx.assert_editor_state(indoc!(
4706 r#"abc
4707 def«ˇg»hi
4708
4709 jk
4710 nlmo
4711 "#
4712 ));
4713
4714 cx.update_editor(|view, cx| {
4715 view.add_selection_above(&Default::default(), cx);
4716 });
4717
4718 cx.assert_editor_state(indoc!(
4719 r#"abc
4720 def«ˇg»hi
4721
4722 jk
4723 nlmo
4724 "#
4725 ));
4726
4727 // Change selections again
4728 cx.set_state(indoc!(
4729 r#"a«bc
4730 defgˇ»hi
4731
4732 jk
4733 nlmo
4734 "#
4735 ));
4736
4737 cx.update_editor(|view, cx| {
4738 view.add_selection_below(&Default::default(), cx);
4739 });
4740
4741 cx.assert_editor_state(indoc!(
4742 r#"a«bcˇ»
4743 d«efgˇ»hi
4744
4745 j«kˇ»
4746 nlmo
4747 "#
4748 ));
4749
4750 cx.update_editor(|view, cx| {
4751 view.add_selection_below(&Default::default(), cx);
4752 });
4753 cx.assert_editor_state(indoc!(
4754 r#"a«bcˇ»
4755 d«efgˇ»hi
4756
4757 j«kˇ»
4758 n«lmoˇ»
4759 "#
4760 ));
4761 cx.update_editor(|view, cx| {
4762 view.add_selection_above(&Default::default(), cx);
4763 });
4764
4765 cx.assert_editor_state(indoc!(
4766 r#"a«bcˇ»
4767 d«efgˇ»hi
4768
4769 j«kˇ»
4770 nlmo
4771 "#
4772 ));
4773
4774 // Change selections again
4775 cx.set_state(indoc!(
4776 r#"abc
4777 d«ˇefghi
4778
4779 jk
4780 nlm»o
4781 "#
4782 ));
4783
4784 cx.update_editor(|view, cx| {
4785 view.add_selection_above(&Default::default(), cx);
4786 });
4787
4788 cx.assert_editor_state(indoc!(
4789 r#"a«ˇbc»
4790 d«ˇef»ghi
4791
4792 j«ˇk»
4793 n«ˇlm»o
4794 "#
4795 ));
4796
4797 cx.update_editor(|view, cx| {
4798 view.add_selection_below(&Default::default(), cx);
4799 });
4800
4801 cx.assert_editor_state(indoc!(
4802 r#"abc
4803 d«ˇef»ghi
4804
4805 j«ˇk»
4806 n«ˇlm»o
4807 "#
4808 ));
4809}
4810
4811#[gpui::test]
4812async fn test_select_next(cx: &mut gpui::TestAppContext) {
4813 init_test(cx, |_| {});
4814
4815 let mut cx = EditorTestContext::new(cx).await;
4816 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4817
4818 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4819 .unwrap();
4820 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4821
4822 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4823 .unwrap();
4824 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4825
4826 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4827 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4828
4829 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4830 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4831
4832 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4833 .unwrap();
4834 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4835
4836 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4837 .unwrap();
4838 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4839}
4840
4841#[gpui::test]
4842async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4843 init_test(cx, |_| {});
4844
4845 let mut cx = EditorTestContext::new(cx).await;
4846 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4847
4848 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4849 .unwrap();
4850 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4851}
4852
4853#[gpui::test]
4854async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4855 init_test(cx, |_| {});
4856
4857 let mut cx = EditorTestContext::new(cx).await;
4858 cx.set_state(
4859 r#"let foo = 2;
4860lˇet foo = 2;
4861let fooˇ = 2;
4862let foo = 2;
4863let foo = ˇ2;"#,
4864 );
4865
4866 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4867 .unwrap();
4868 cx.assert_editor_state(
4869 r#"let foo = 2;
4870«letˇ» foo = 2;
4871let «fooˇ» = 2;
4872let foo = 2;
4873let foo = «2ˇ»;"#,
4874 );
4875
4876 // noop for multiple selections with different contents
4877 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4878 .unwrap();
4879 cx.assert_editor_state(
4880 r#"let foo = 2;
4881«letˇ» foo = 2;
4882let «fooˇ» = 2;
4883let foo = 2;
4884let foo = «2ˇ»;"#,
4885 );
4886}
4887
4888#[gpui::test]
4889async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4890 init_test(cx, |_| {});
4891
4892 let mut cx =
4893 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
4894
4895 cx.assert_editor_state(indoc! {"
4896 ˇbbb
4897 ccc
4898
4899 bbb
4900 ccc
4901 "});
4902 cx.dispatch_action(SelectPrevious::default());
4903 cx.assert_editor_state(indoc! {"
4904 «bbbˇ»
4905 ccc
4906
4907 bbb
4908 ccc
4909 "});
4910 cx.dispatch_action(SelectPrevious::default());
4911 cx.assert_editor_state(indoc! {"
4912 «bbbˇ»
4913 ccc
4914
4915 «bbbˇ»
4916 ccc
4917 "});
4918}
4919
4920#[gpui::test]
4921async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4922 init_test(cx, |_| {});
4923
4924 let mut cx = EditorTestContext::new(cx).await;
4925 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4926
4927 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4928 .unwrap();
4929 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4930
4931 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4932 .unwrap();
4933 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4934
4935 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4936 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4937
4938 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4939 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4940
4941 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4942 .unwrap();
4943 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4944
4945 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4946 .unwrap();
4947 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4948
4949 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4950 .unwrap();
4951 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4952}
4953
4954#[gpui::test]
4955async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4956 init_test(cx, |_| {});
4957
4958 let mut cx = EditorTestContext::new(cx).await;
4959 cx.set_state(
4960 r#"let foo = 2;
4961lˇet foo = 2;
4962let fooˇ = 2;
4963let foo = 2;
4964let foo = ˇ2;"#,
4965 );
4966
4967 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4968 .unwrap();
4969 cx.assert_editor_state(
4970 r#"let foo = 2;
4971«letˇ» foo = 2;
4972let «fooˇ» = 2;
4973let foo = 2;
4974let foo = «2ˇ»;"#,
4975 );
4976
4977 // noop for multiple selections with different contents
4978 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4979 .unwrap();
4980 cx.assert_editor_state(
4981 r#"let foo = 2;
4982«letˇ» foo = 2;
4983let «fooˇ» = 2;
4984let foo = 2;
4985let foo = «2ˇ»;"#,
4986 );
4987}
4988
4989#[gpui::test]
4990async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4991 init_test(cx, |_| {});
4992
4993 let mut cx = EditorTestContext::new(cx).await;
4994 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4995
4996 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4997 .unwrap();
4998 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4999
5000 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5001 .unwrap();
5002 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5003
5004 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5005 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5006
5007 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5008 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5009
5010 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5011 .unwrap();
5012 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5013
5014 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5015 .unwrap();
5016 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5017}
5018
5019#[gpui::test]
5020async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5021 init_test(cx, |_| {});
5022
5023 let language = Arc::new(Language::new(
5024 LanguageConfig::default(),
5025 Some(tree_sitter_rust::LANGUAGE.into()),
5026 ));
5027
5028 let text = r#"
5029 use mod1::mod2::{mod3, mod4};
5030
5031 fn fn_1(param1: bool, param2: &str) {
5032 let var1 = "text";
5033 }
5034 "#
5035 .unindent();
5036
5037 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5038 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5039 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5040
5041 editor
5042 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5043 .await;
5044
5045 editor.update(cx, |view, cx| {
5046 view.change_selections(None, cx, |s| {
5047 s.select_display_ranges([
5048 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5049 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5050 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5051 ]);
5052 });
5053 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5054 });
5055 editor.update(cx, |editor, cx| {
5056 assert_text_with_selections(
5057 editor,
5058 indoc! {r#"
5059 use mod1::mod2::{mod3, «mod4ˇ»};
5060
5061 fn fn_1«ˇ(param1: bool, param2: &str)» {
5062 let var1 = "«textˇ»";
5063 }
5064 "#},
5065 cx,
5066 );
5067 });
5068
5069 editor.update(cx, |view, cx| {
5070 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5071 });
5072 editor.update(cx, |editor, cx| {
5073 assert_text_with_selections(
5074 editor,
5075 indoc! {r#"
5076 use mod1::mod2::«{mod3, mod4}ˇ»;
5077
5078 «ˇfn fn_1(param1: bool, param2: &str) {
5079 let var1 = "text";
5080 }»
5081 "#},
5082 cx,
5083 );
5084 });
5085
5086 editor.update(cx, |view, cx| {
5087 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5088 });
5089 assert_eq!(
5090 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5091 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5092 );
5093
5094 // Trying to expand the selected syntax node one more time has no effect.
5095 editor.update(cx, |view, cx| {
5096 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5097 });
5098 assert_eq!(
5099 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5100 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5101 );
5102
5103 editor.update(cx, |view, cx| {
5104 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5105 });
5106 editor.update(cx, |editor, cx| {
5107 assert_text_with_selections(
5108 editor,
5109 indoc! {r#"
5110 use mod1::mod2::«{mod3, mod4}ˇ»;
5111
5112 «ˇfn fn_1(param1: bool, param2: &str) {
5113 let var1 = "text";
5114 }»
5115 "#},
5116 cx,
5117 );
5118 });
5119
5120 editor.update(cx, |view, cx| {
5121 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5122 });
5123 editor.update(cx, |editor, cx| {
5124 assert_text_with_selections(
5125 editor,
5126 indoc! {r#"
5127 use mod1::mod2::{mod3, «mod4ˇ»};
5128
5129 fn fn_1«ˇ(param1: bool, param2: &str)» {
5130 let var1 = "«textˇ»";
5131 }
5132 "#},
5133 cx,
5134 );
5135 });
5136
5137 editor.update(cx, |view, cx| {
5138 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5139 });
5140 editor.update(cx, |editor, cx| {
5141 assert_text_with_selections(
5142 editor,
5143 indoc! {r#"
5144 use mod1::mod2::{mod3, mo«ˇ»d4};
5145
5146 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5147 let var1 = "te«ˇ»xt";
5148 }
5149 "#},
5150 cx,
5151 );
5152 });
5153
5154 // Trying to shrink the selected syntax node one more time has no effect.
5155 editor.update(cx, |view, cx| {
5156 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5157 });
5158 editor.update(cx, |editor, cx| {
5159 assert_text_with_selections(
5160 editor,
5161 indoc! {r#"
5162 use mod1::mod2::{mod3, mo«ˇ»d4};
5163
5164 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5165 let var1 = "te«ˇ»xt";
5166 }
5167 "#},
5168 cx,
5169 );
5170 });
5171
5172 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5173 // a fold.
5174 editor.update(cx, |view, cx| {
5175 view.fold_ranges(
5176 vec![
5177 (
5178 Point::new(0, 21)..Point::new(0, 24),
5179 FoldPlaceholder::test(),
5180 ),
5181 (
5182 Point::new(3, 20)..Point::new(3, 22),
5183 FoldPlaceholder::test(),
5184 ),
5185 ],
5186 true,
5187 cx,
5188 );
5189 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5190 });
5191 editor.update(cx, |editor, cx| {
5192 assert_text_with_selections(
5193 editor,
5194 indoc! {r#"
5195 use mod1::mod2::«{mod3, mod4}ˇ»;
5196
5197 fn fn_1«ˇ(param1: bool, param2: &str)» {
5198 «let var1 = "text";ˇ»
5199 }
5200 "#},
5201 cx,
5202 );
5203 });
5204}
5205
5206#[gpui::test]
5207async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5208 init_test(cx, |_| {});
5209
5210 let language = Arc::new(
5211 Language::new(
5212 LanguageConfig {
5213 brackets: BracketPairConfig {
5214 pairs: vec![
5215 BracketPair {
5216 start: "{".to_string(),
5217 end: "}".to_string(),
5218 close: false,
5219 surround: false,
5220 newline: true,
5221 },
5222 BracketPair {
5223 start: "(".to_string(),
5224 end: ")".to_string(),
5225 close: false,
5226 surround: false,
5227 newline: true,
5228 },
5229 ],
5230 ..Default::default()
5231 },
5232 ..Default::default()
5233 },
5234 Some(tree_sitter_rust::LANGUAGE.into()),
5235 )
5236 .with_indents_query(
5237 r#"
5238 (_ "(" ")" @end) @indent
5239 (_ "{" "}" @end) @indent
5240 "#,
5241 )
5242 .unwrap(),
5243 );
5244
5245 let text = "fn a() {}";
5246
5247 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5248 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5249 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5250 editor
5251 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5252 .await;
5253
5254 editor.update(cx, |editor, cx| {
5255 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5256 editor.newline(&Newline, cx);
5257 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5258 assert_eq!(
5259 editor.selections.ranges(cx),
5260 &[
5261 Point::new(1, 4)..Point::new(1, 4),
5262 Point::new(3, 4)..Point::new(3, 4),
5263 Point::new(5, 0)..Point::new(5, 0)
5264 ]
5265 );
5266 });
5267}
5268
5269#[gpui::test]
5270async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5271 init_test(cx, |_| {});
5272
5273 let mut cx = EditorTestContext::new(cx).await;
5274
5275 let language = Arc::new(Language::new(
5276 LanguageConfig {
5277 brackets: BracketPairConfig {
5278 pairs: vec![
5279 BracketPair {
5280 start: "{".to_string(),
5281 end: "}".to_string(),
5282 close: true,
5283 surround: true,
5284 newline: true,
5285 },
5286 BracketPair {
5287 start: "(".to_string(),
5288 end: ")".to_string(),
5289 close: true,
5290 surround: true,
5291 newline: true,
5292 },
5293 BracketPair {
5294 start: "/*".to_string(),
5295 end: " */".to_string(),
5296 close: true,
5297 surround: true,
5298 newline: true,
5299 },
5300 BracketPair {
5301 start: "[".to_string(),
5302 end: "]".to_string(),
5303 close: false,
5304 surround: false,
5305 newline: true,
5306 },
5307 BracketPair {
5308 start: "\"".to_string(),
5309 end: "\"".to_string(),
5310 close: true,
5311 surround: true,
5312 newline: false,
5313 },
5314 BracketPair {
5315 start: "<".to_string(),
5316 end: ">".to_string(),
5317 close: false,
5318 surround: true,
5319 newline: true,
5320 },
5321 ],
5322 ..Default::default()
5323 },
5324 autoclose_before: "})]".to_string(),
5325 ..Default::default()
5326 },
5327 Some(tree_sitter_rust::LANGUAGE.into()),
5328 ));
5329
5330 cx.language_registry().add(language.clone());
5331 cx.update_buffer(|buffer, cx| {
5332 buffer.set_language(Some(language), cx);
5333 });
5334
5335 cx.set_state(
5336 &r#"
5337 🏀ˇ
5338 εˇ
5339 ❤️ˇ
5340 "#
5341 .unindent(),
5342 );
5343
5344 // autoclose multiple nested brackets at multiple cursors
5345 cx.update_editor(|view, cx| {
5346 view.handle_input("{", cx);
5347 view.handle_input("{", cx);
5348 view.handle_input("{", cx);
5349 });
5350 cx.assert_editor_state(
5351 &"
5352 🏀{{{ˇ}}}
5353 ε{{{ˇ}}}
5354 ❤️{{{ˇ}}}
5355 "
5356 .unindent(),
5357 );
5358
5359 // insert a different closing bracket
5360 cx.update_editor(|view, cx| {
5361 view.handle_input(")", cx);
5362 });
5363 cx.assert_editor_state(
5364 &"
5365 🏀{{{)ˇ}}}
5366 ε{{{)ˇ}}}
5367 ❤️{{{)ˇ}}}
5368 "
5369 .unindent(),
5370 );
5371
5372 // skip over the auto-closed brackets when typing a closing bracket
5373 cx.update_editor(|view, cx| {
5374 view.move_right(&MoveRight, cx);
5375 view.handle_input("}", cx);
5376 view.handle_input("}", cx);
5377 view.handle_input("}", cx);
5378 });
5379 cx.assert_editor_state(
5380 &"
5381 🏀{{{)}}}}ˇ
5382 ε{{{)}}}}ˇ
5383 ❤️{{{)}}}}ˇ
5384 "
5385 .unindent(),
5386 );
5387
5388 // autoclose multi-character pairs
5389 cx.set_state(
5390 &"
5391 ˇ
5392 ˇ
5393 "
5394 .unindent(),
5395 );
5396 cx.update_editor(|view, cx| {
5397 view.handle_input("/", cx);
5398 view.handle_input("*", cx);
5399 });
5400 cx.assert_editor_state(
5401 &"
5402 /*ˇ */
5403 /*ˇ */
5404 "
5405 .unindent(),
5406 );
5407
5408 // one cursor autocloses a multi-character pair, one cursor
5409 // does not autoclose.
5410 cx.set_state(
5411 &"
5412 /ˇ
5413 ˇ
5414 "
5415 .unindent(),
5416 );
5417 cx.update_editor(|view, cx| view.handle_input("*", cx));
5418 cx.assert_editor_state(
5419 &"
5420 /*ˇ */
5421 *ˇ
5422 "
5423 .unindent(),
5424 );
5425
5426 // Don't autoclose if the next character isn't whitespace and isn't
5427 // listed in the language's "autoclose_before" section.
5428 cx.set_state("ˇa b");
5429 cx.update_editor(|view, cx| view.handle_input("{", cx));
5430 cx.assert_editor_state("{ˇa b");
5431
5432 // Don't autoclose if `close` is false for the bracket pair
5433 cx.set_state("ˇ");
5434 cx.update_editor(|view, cx| view.handle_input("[", cx));
5435 cx.assert_editor_state("[ˇ");
5436
5437 // Surround with brackets if text is selected
5438 cx.set_state("«aˇ» b");
5439 cx.update_editor(|view, cx| view.handle_input("{", cx));
5440 cx.assert_editor_state("{«aˇ»} b");
5441
5442 // Autclose pair where the start and end characters are the same
5443 cx.set_state("aˇ");
5444 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5445 cx.assert_editor_state("a\"ˇ\"");
5446 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5447 cx.assert_editor_state("a\"\"ˇ");
5448
5449 // Don't autoclose pair if autoclose is disabled
5450 cx.set_state("ˇ");
5451 cx.update_editor(|view, cx| view.handle_input("<", cx));
5452 cx.assert_editor_state("<ˇ");
5453
5454 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5455 cx.set_state("«aˇ» b");
5456 cx.update_editor(|view, cx| view.handle_input("<", cx));
5457 cx.assert_editor_state("<«aˇ»> b");
5458}
5459
5460#[gpui::test]
5461async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5462 init_test(cx, |settings| {
5463 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5464 });
5465
5466 let mut cx = EditorTestContext::new(cx).await;
5467
5468 let language = Arc::new(Language::new(
5469 LanguageConfig {
5470 brackets: BracketPairConfig {
5471 pairs: vec![
5472 BracketPair {
5473 start: "{".to_string(),
5474 end: "}".to_string(),
5475 close: true,
5476 surround: true,
5477 newline: true,
5478 },
5479 BracketPair {
5480 start: "(".to_string(),
5481 end: ")".to_string(),
5482 close: true,
5483 surround: true,
5484 newline: true,
5485 },
5486 BracketPair {
5487 start: "[".to_string(),
5488 end: "]".to_string(),
5489 close: false,
5490 surround: false,
5491 newline: true,
5492 },
5493 ],
5494 ..Default::default()
5495 },
5496 autoclose_before: "})]".to_string(),
5497 ..Default::default()
5498 },
5499 Some(tree_sitter_rust::LANGUAGE.into()),
5500 ));
5501
5502 cx.language_registry().add(language.clone());
5503 cx.update_buffer(|buffer, cx| {
5504 buffer.set_language(Some(language), cx);
5505 });
5506
5507 cx.set_state(
5508 &"
5509 ˇ
5510 ˇ
5511 ˇ
5512 "
5513 .unindent(),
5514 );
5515
5516 // ensure only matching closing brackets are skipped over
5517 cx.update_editor(|view, cx| {
5518 view.handle_input("}", cx);
5519 view.move_left(&MoveLeft, cx);
5520 view.handle_input(")", cx);
5521 view.move_left(&MoveLeft, cx);
5522 });
5523 cx.assert_editor_state(
5524 &"
5525 ˇ)}
5526 ˇ)}
5527 ˇ)}
5528 "
5529 .unindent(),
5530 );
5531
5532 // skip-over closing brackets at multiple cursors
5533 cx.update_editor(|view, cx| {
5534 view.handle_input(")", cx);
5535 view.handle_input("}", cx);
5536 });
5537 cx.assert_editor_state(
5538 &"
5539 )}ˇ
5540 )}ˇ
5541 )}ˇ
5542 "
5543 .unindent(),
5544 );
5545
5546 // ignore non-close brackets
5547 cx.update_editor(|view, cx| {
5548 view.handle_input("]", cx);
5549 view.move_left(&MoveLeft, cx);
5550 view.handle_input("]", cx);
5551 });
5552 cx.assert_editor_state(
5553 &"
5554 )}]ˇ]
5555 )}]ˇ]
5556 )}]ˇ]
5557 "
5558 .unindent(),
5559 );
5560}
5561
5562#[gpui::test]
5563async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5564 init_test(cx, |_| {});
5565
5566 let mut cx = EditorTestContext::new(cx).await;
5567
5568 let html_language = Arc::new(
5569 Language::new(
5570 LanguageConfig {
5571 name: "HTML".into(),
5572 brackets: BracketPairConfig {
5573 pairs: vec![
5574 BracketPair {
5575 start: "<".into(),
5576 end: ">".into(),
5577 close: true,
5578 ..Default::default()
5579 },
5580 BracketPair {
5581 start: "{".into(),
5582 end: "}".into(),
5583 close: true,
5584 ..Default::default()
5585 },
5586 BracketPair {
5587 start: "(".into(),
5588 end: ")".into(),
5589 close: true,
5590 ..Default::default()
5591 },
5592 ],
5593 ..Default::default()
5594 },
5595 autoclose_before: "})]>".into(),
5596 ..Default::default()
5597 },
5598 Some(tree_sitter_html::language()),
5599 )
5600 .with_injection_query(
5601 r#"
5602 (script_element
5603 (raw_text) @content
5604 (#set! "language" "javascript"))
5605 "#,
5606 )
5607 .unwrap(),
5608 );
5609
5610 let javascript_language = Arc::new(Language::new(
5611 LanguageConfig {
5612 name: "JavaScript".into(),
5613 brackets: BracketPairConfig {
5614 pairs: vec![
5615 BracketPair {
5616 start: "/*".into(),
5617 end: " */".into(),
5618 close: true,
5619 ..Default::default()
5620 },
5621 BracketPair {
5622 start: "{".into(),
5623 end: "}".into(),
5624 close: true,
5625 ..Default::default()
5626 },
5627 BracketPair {
5628 start: "(".into(),
5629 end: ")".into(),
5630 close: true,
5631 ..Default::default()
5632 },
5633 ],
5634 ..Default::default()
5635 },
5636 autoclose_before: "})]>".into(),
5637 ..Default::default()
5638 },
5639 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5640 ));
5641
5642 cx.language_registry().add(html_language.clone());
5643 cx.language_registry().add(javascript_language.clone());
5644
5645 cx.update_buffer(|buffer, cx| {
5646 buffer.set_language(Some(html_language), cx);
5647 });
5648
5649 cx.set_state(
5650 &r#"
5651 <body>ˇ
5652 <script>
5653 var x = 1;ˇ
5654 </script>
5655 </body>ˇ
5656 "#
5657 .unindent(),
5658 );
5659
5660 // Precondition: different languages are active at different locations.
5661 cx.update_editor(|editor, cx| {
5662 let snapshot = editor.snapshot(cx);
5663 let cursors = editor.selections.ranges::<usize>(cx);
5664 let languages = cursors
5665 .iter()
5666 .map(|c| snapshot.language_at(c.start).unwrap().name())
5667 .collect::<Vec<_>>();
5668 assert_eq!(
5669 languages,
5670 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5671 );
5672 });
5673
5674 // Angle brackets autoclose in HTML, but not JavaScript.
5675 cx.update_editor(|editor, cx| {
5676 editor.handle_input("<", cx);
5677 editor.handle_input("a", cx);
5678 });
5679 cx.assert_editor_state(
5680 &r#"
5681 <body><aˇ>
5682 <script>
5683 var x = 1;<aˇ
5684 </script>
5685 </body><aˇ>
5686 "#
5687 .unindent(),
5688 );
5689
5690 // Curly braces and parens autoclose in both HTML and JavaScript.
5691 cx.update_editor(|editor, cx| {
5692 editor.handle_input(" b=", cx);
5693 editor.handle_input("{", cx);
5694 editor.handle_input("c", cx);
5695 editor.handle_input("(", cx);
5696 });
5697 cx.assert_editor_state(
5698 &r#"
5699 <body><a b={c(ˇ)}>
5700 <script>
5701 var x = 1;<a b={c(ˇ)}
5702 </script>
5703 </body><a b={c(ˇ)}>
5704 "#
5705 .unindent(),
5706 );
5707
5708 // Brackets that were already autoclosed are skipped.
5709 cx.update_editor(|editor, cx| {
5710 editor.handle_input(")", cx);
5711 editor.handle_input("d", cx);
5712 editor.handle_input("}", cx);
5713 });
5714 cx.assert_editor_state(
5715 &r#"
5716 <body><a b={c()d}ˇ>
5717 <script>
5718 var x = 1;<a b={c()d}ˇ
5719 </script>
5720 </body><a b={c()d}ˇ>
5721 "#
5722 .unindent(),
5723 );
5724 cx.update_editor(|editor, cx| {
5725 editor.handle_input(">", cx);
5726 });
5727 cx.assert_editor_state(
5728 &r#"
5729 <body><a b={c()d}>ˇ
5730 <script>
5731 var x = 1;<a b={c()d}>ˇ
5732 </script>
5733 </body><a b={c()d}>ˇ
5734 "#
5735 .unindent(),
5736 );
5737
5738 // Reset
5739 cx.set_state(
5740 &r#"
5741 <body>ˇ
5742 <script>
5743 var x = 1;ˇ
5744 </script>
5745 </body>ˇ
5746 "#
5747 .unindent(),
5748 );
5749
5750 cx.update_editor(|editor, cx| {
5751 editor.handle_input("<", cx);
5752 });
5753 cx.assert_editor_state(
5754 &r#"
5755 <body><ˇ>
5756 <script>
5757 var x = 1;<ˇ
5758 </script>
5759 </body><ˇ>
5760 "#
5761 .unindent(),
5762 );
5763
5764 // When backspacing, the closing angle brackets are removed.
5765 cx.update_editor(|editor, cx| {
5766 editor.backspace(&Backspace, cx);
5767 });
5768 cx.assert_editor_state(
5769 &r#"
5770 <body>ˇ
5771 <script>
5772 var x = 1;ˇ
5773 </script>
5774 </body>ˇ
5775 "#
5776 .unindent(),
5777 );
5778
5779 // Block comments autoclose in JavaScript, but not HTML.
5780 cx.update_editor(|editor, cx| {
5781 editor.handle_input("/", cx);
5782 editor.handle_input("*", cx);
5783 });
5784 cx.assert_editor_state(
5785 &r#"
5786 <body>/*ˇ
5787 <script>
5788 var x = 1;/*ˇ */
5789 </script>
5790 </body>/*ˇ
5791 "#
5792 .unindent(),
5793 );
5794}
5795
5796#[gpui::test]
5797async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5798 init_test(cx, |_| {});
5799
5800 let mut cx = EditorTestContext::new(cx).await;
5801
5802 let rust_language = Arc::new(
5803 Language::new(
5804 LanguageConfig {
5805 name: "Rust".into(),
5806 brackets: serde_json::from_value(json!([
5807 { "start": "{", "end": "}", "close": true, "newline": true },
5808 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5809 ]))
5810 .unwrap(),
5811 autoclose_before: "})]>".into(),
5812 ..Default::default()
5813 },
5814 Some(tree_sitter_rust::LANGUAGE.into()),
5815 )
5816 .with_override_query("(string_literal) @string")
5817 .unwrap(),
5818 );
5819
5820 cx.language_registry().add(rust_language.clone());
5821 cx.update_buffer(|buffer, cx| {
5822 buffer.set_language(Some(rust_language), cx);
5823 });
5824
5825 cx.set_state(
5826 &r#"
5827 let x = ˇ
5828 "#
5829 .unindent(),
5830 );
5831
5832 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5833 cx.update_editor(|editor, cx| {
5834 editor.handle_input("\"", cx);
5835 });
5836 cx.assert_editor_state(
5837 &r#"
5838 let x = "ˇ"
5839 "#
5840 .unindent(),
5841 );
5842
5843 // Inserting another quotation mark. The cursor moves across the existing
5844 // automatically-inserted quotation mark.
5845 cx.update_editor(|editor, cx| {
5846 editor.handle_input("\"", cx);
5847 });
5848 cx.assert_editor_state(
5849 &r#"
5850 let x = ""ˇ
5851 "#
5852 .unindent(),
5853 );
5854
5855 // Reset
5856 cx.set_state(
5857 &r#"
5858 let x = ˇ
5859 "#
5860 .unindent(),
5861 );
5862
5863 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5864 cx.update_editor(|editor, cx| {
5865 editor.handle_input("\"", cx);
5866 editor.handle_input(" ", cx);
5867 editor.move_left(&Default::default(), cx);
5868 editor.handle_input("\\", cx);
5869 editor.handle_input("\"", cx);
5870 });
5871 cx.assert_editor_state(
5872 &r#"
5873 let x = "\"ˇ "
5874 "#
5875 .unindent(),
5876 );
5877
5878 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5879 // mark. Nothing is inserted.
5880 cx.update_editor(|editor, cx| {
5881 editor.move_right(&Default::default(), cx);
5882 editor.handle_input("\"", cx);
5883 });
5884 cx.assert_editor_state(
5885 &r#"
5886 let x = "\" "ˇ
5887 "#
5888 .unindent(),
5889 );
5890}
5891
5892#[gpui::test]
5893async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5894 init_test(cx, |_| {});
5895
5896 let language = Arc::new(Language::new(
5897 LanguageConfig {
5898 brackets: BracketPairConfig {
5899 pairs: vec![
5900 BracketPair {
5901 start: "{".to_string(),
5902 end: "}".to_string(),
5903 close: true,
5904 surround: true,
5905 newline: true,
5906 },
5907 BracketPair {
5908 start: "/* ".to_string(),
5909 end: "*/".to_string(),
5910 close: true,
5911 surround: true,
5912 ..Default::default()
5913 },
5914 ],
5915 ..Default::default()
5916 },
5917 ..Default::default()
5918 },
5919 Some(tree_sitter_rust::LANGUAGE.into()),
5920 ));
5921
5922 let text = r#"
5923 a
5924 b
5925 c
5926 "#
5927 .unindent();
5928
5929 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5930 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5931 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5932 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5933 .await;
5934
5935 view.update(cx, |view, cx| {
5936 view.change_selections(None, cx, |s| {
5937 s.select_display_ranges([
5938 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5939 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5940 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5941 ])
5942 });
5943
5944 view.handle_input("{", cx);
5945 view.handle_input("{", cx);
5946 view.handle_input("{", cx);
5947 assert_eq!(
5948 view.text(cx),
5949 "
5950 {{{a}}}
5951 {{{b}}}
5952 {{{c}}}
5953 "
5954 .unindent()
5955 );
5956 assert_eq!(
5957 view.selections.display_ranges(cx),
5958 [
5959 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5960 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5961 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5962 ]
5963 );
5964
5965 view.undo(&Undo, cx);
5966 view.undo(&Undo, cx);
5967 view.undo(&Undo, cx);
5968 assert_eq!(
5969 view.text(cx),
5970 "
5971 a
5972 b
5973 c
5974 "
5975 .unindent()
5976 );
5977 assert_eq!(
5978 view.selections.display_ranges(cx),
5979 [
5980 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5981 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5982 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5983 ]
5984 );
5985
5986 // Ensure inserting the first character of a multi-byte bracket pair
5987 // doesn't surround the selections with the bracket.
5988 view.handle_input("/", cx);
5989 assert_eq!(
5990 view.text(cx),
5991 "
5992 /
5993 /
5994 /
5995 "
5996 .unindent()
5997 );
5998 assert_eq!(
5999 view.selections.display_ranges(cx),
6000 [
6001 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6002 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6003 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6004 ]
6005 );
6006
6007 view.undo(&Undo, cx);
6008 assert_eq!(
6009 view.text(cx),
6010 "
6011 a
6012 b
6013 c
6014 "
6015 .unindent()
6016 );
6017 assert_eq!(
6018 view.selections.display_ranges(cx),
6019 [
6020 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6021 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6022 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6023 ]
6024 );
6025
6026 // Ensure inserting the last character of a multi-byte bracket pair
6027 // doesn't surround the selections with the bracket.
6028 view.handle_input("*", cx);
6029 assert_eq!(
6030 view.text(cx),
6031 "
6032 *
6033 *
6034 *
6035 "
6036 .unindent()
6037 );
6038 assert_eq!(
6039 view.selections.display_ranges(cx),
6040 [
6041 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6042 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6043 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6044 ]
6045 );
6046 });
6047}
6048
6049#[gpui::test]
6050async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6051 init_test(cx, |_| {});
6052
6053 let language = Arc::new(Language::new(
6054 LanguageConfig {
6055 brackets: BracketPairConfig {
6056 pairs: vec![BracketPair {
6057 start: "{".to_string(),
6058 end: "}".to_string(),
6059 close: true,
6060 surround: true,
6061 newline: true,
6062 }],
6063 ..Default::default()
6064 },
6065 autoclose_before: "}".to_string(),
6066 ..Default::default()
6067 },
6068 Some(tree_sitter_rust::LANGUAGE.into()),
6069 ));
6070
6071 let text = r#"
6072 a
6073 b
6074 c
6075 "#
6076 .unindent();
6077
6078 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6079 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6080 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6081 editor
6082 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6083 .await;
6084
6085 editor.update(cx, |editor, cx| {
6086 editor.change_selections(None, cx, |s| {
6087 s.select_ranges([
6088 Point::new(0, 1)..Point::new(0, 1),
6089 Point::new(1, 1)..Point::new(1, 1),
6090 Point::new(2, 1)..Point::new(2, 1),
6091 ])
6092 });
6093
6094 editor.handle_input("{", cx);
6095 editor.handle_input("{", cx);
6096 editor.handle_input("_", cx);
6097 assert_eq!(
6098 editor.text(cx),
6099 "
6100 a{{_}}
6101 b{{_}}
6102 c{{_}}
6103 "
6104 .unindent()
6105 );
6106 assert_eq!(
6107 editor.selections.ranges::<Point>(cx),
6108 [
6109 Point::new(0, 4)..Point::new(0, 4),
6110 Point::new(1, 4)..Point::new(1, 4),
6111 Point::new(2, 4)..Point::new(2, 4)
6112 ]
6113 );
6114
6115 editor.backspace(&Default::default(), cx);
6116 editor.backspace(&Default::default(), cx);
6117 assert_eq!(
6118 editor.text(cx),
6119 "
6120 a{}
6121 b{}
6122 c{}
6123 "
6124 .unindent()
6125 );
6126 assert_eq!(
6127 editor.selections.ranges::<Point>(cx),
6128 [
6129 Point::new(0, 2)..Point::new(0, 2),
6130 Point::new(1, 2)..Point::new(1, 2),
6131 Point::new(2, 2)..Point::new(2, 2)
6132 ]
6133 );
6134
6135 editor.delete_to_previous_word_start(&Default::default(), cx);
6136 assert_eq!(
6137 editor.text(cx),
6138 "
6139 a
6140 b
6141 c
6142 "
6143 .unindent()
6144 );
6145 assert_eq!(
6146 editor.selections.ranges::<Point>(cx),
6147 [
6148 Point::new(0, 1)..Point::new(0, 1),
6149 Point::new(1, 1)..Point::new(1, 1),
6150 Point::new(2, 1)..Point::new(2, 1)
6151 ]
6152 );
6153 });
6154}
6155
6156#[gpui::test]
6157async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6158 init_test(cx, |settings| {
6159 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6160 });
6161
6162 let mut cx = EditorTestContext::new(cx).await;
6163
6164 let language = Arc::new(Language::new(
6165 LanguageConfig {
6166 brackets: BracketPairConfig {
6167 pairs: vec![
6168 BracketPair {
6169 start: "{".to_string(),
6170 end: "}".to_string(),
6171 close: true,
6172 surround: true,
6173 newline: true,
6174 },
6175 BracketPair {
6176 start: "(".to_string(),
6177 end: ")".to_string(),
6178 close: true,
6179 surround: true,
6180 newline: true,
6181 },
6182 BracketPair {
6183 start: "[".to_string(),
6184 end: "]".to_string(),
6185 close: false,
6186 surround: true,
6187 newline: true,
6188 },
6189 ],
6190 ..Default::default()
6191 },
6192 autoclose_before: "})]".to_string(),
6193 ..Default::default()
6194 },
6195 Some(tree_sitter_rust::LANGUAGE.into()),
6196 ));
6197
6198 cx.language_registry().add(language.clone());
6199 cx.update_buffer(|buffer, cx| {
6200 buffer.set_language(Some(language), cx);
6201 });
6202
6203 cx.set_state(
6204 &"
6205 {(ˇ)}
6206 [[ˇ]]
6207 {(ˇ)}
6208 "
6209 .unindent(),
6210 );
6211
6212 cx.update_editor(|view, cx| {
6213 view.backspace(&Default::default(), cx);
6214 view.backspace(&Default::default(), cx);
6215 });
6216
6217 cx.assert_editor_state(
6218 &"
6219 ˇ
6220 ˇ]]
6221 ˇ
6222 "
6223 .unindent(),
6224 );
6225
6226 cx.update_editor(|view, cx| {
6227 view.handle_input("{", cx);
6228 view.handle_input("{", cx);
6229 view.move_right(&MoveRight, cx);
6230 view.move_right(&MoveRight, cx);
6231 view.move_left(&MoveLeft, cx);
6232 view.move_left(&MoveLeft, cx);
6233 view.backspace(&Default::default(), cx);
6234 });
6235
6236 cx.assert_editor_state(
6237 &"
6238 {ˇ}
6239 {ˇ}]]
6240 {ˇ}
6241 "
6242 .unindent(),
6243 );
6244
6245 cx.update_editor(|view, cx| {
6246 view.backspace(&Default::default(), cx);
6247 });
6248
6249 cx.assert_editor_state(
6250 &"
6251 ˇ
6252 ˇ]]
6253 ˇ
6254 "
6255 .unindent(),
6256 );
6257}
6258
6259#[gpui::test]
6260async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6261 init_test(cx, |_| {});
6262
6263 let language = Arc::new(Language::new(
6264 LanguageConfig::default(),
6265 Some(tree_sitter_rust::LANGUAGE.into()),
6266 ));
6267
6268 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6269 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6270 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6271 editor
6272 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6273 .await;
6274
6275 editor.update(cx, |editor, cx| {
6276 editor.set_auto_replace_emoji_shortcode(true);
6277
6278 editor.handle_input("Hello ", cx);
6279 editor.handle_input(":wave", cx);
6280 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6281
6282 editor.handle_input(":", cx);
6283 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6284
6285 editor.handle_input(" :smile", cx);
6286 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6287
6288 editor.handle_input(":", cx);
6289 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6290
6291 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6292 editor.handle_input(":wave", cx);
6293 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6294
6295 editor.handle_input(":", cx);
6296 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6297
6298 editor.handle_input(":1", cx);
6299 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6300
6301 editor.handle_input(":", cx);
6302 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6303
6304 // Ensure shortcode does not get replaced when it is part of a word
6305 editor.handle_input(" Test:wave", cx);
6306 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6307
6308 editor.handle_input(":", cx);
6309 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6310
6311 editor.set_auto_replace_emoji_shortcode(false);
6312
6313 // Ensure shortcode does not get replaced when auto replace is off
6314 editor.handle_input(" :wave", cx);
6315 assert_eq!(
6316 editor.text(cx),
6317 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6318 );
6319
6320 editor.handle_input(":", cx);
6321 assert_eq!(
6322 editor.text(cx),
6323 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6324 );
6325 });
6326}
6327
6328#[gpui::test]
6329async fn test_snippets(cx: &mut gpui::TestAppContext) {
6330 init_test(cx, |_| {});
6331
6332 let (text, insertion_ranges) = marked_text_ranges(
6333 indoc! {"
6334 a.ˇ b
6335 a.ˇ b
6336 a.ˇ b
6337 "},
6338 false,
6339 );
6340
6341 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6342 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6343
6344 editor.update(cx, |editor, cx| {
6345 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6346
6347 editor
6348 .insert_snippet(&insertion_ranges, snippet, cx)
6349 .unwrap();
6350
6351 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6352 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6353 assert_eq!(editor.text(cx), expected_text);
6354 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6355 }
6356
6357 assert(
6358 editor,
6359 cx,
6360 indoc! {"
6361 a.f(«one», two, «three») b
6362 a.f(«one», two, «three») b
6363 a.f(«one», two, «three») b
6364 "},
6365 );
6366
6367 // Can't move earlier than the first tab stop
6368 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6369 assert(
6370 editor,
6371 cx,
6372 indoc! {"
6373 a.f(«one», two, «three») b
6374 a.f(«one», two, «three») b
6375 a.f(«one», two, «three») b
6376 "},
6377 );
6378
6379 assert!(editor.move_to_next_snippet_tabstop(cx));
6380 assert(
6381 editor,
6382 cx,
6383 indoc! {"
6384 a.f(one, «two», three) b
6385 a.f(one, «two», three) b
6386 a.f(one, «two», three) b
6387 "},
6388 );
6389
6390 editor.move_to_prev_snippet_tabstop(cx);
6391 assert(
6392 editor,
6393 cx,
6394 indoc! {"
6395 a.f(«one», two, «three») b
6396 a.f(«one», two, «three») b
6397 a.f(«one», two, «three») b
6398 "},
6399 );
6400
6401 assert!(editor.move_to_next_snippet_tabstop(cx));
6402 assert(
6403 editor,
6404 cx,
6405 indoc! {"
6406 a.f(one, «two», three) b
6407 a.f(one, «two», three) b
6408 a.f(one, «two», three) b
6409 "},
6410 );
6411 assert!(editor.move_to_next_snippet_tabstop(cx));
6412 assert(
6413 editor,
6414 cx,
6415 indoc! {"
6416 a.f(one, two, three)ˇ b
6417 a.f(one, two, three)ˇ b
6418 a.f(one, two, three)ˇ b
6419 "},
6420 );
6421
6422 // As soon as the last tab stop is reached, snippet state is gone
6423 editor.move_to_prev_snippet_tabstop(cx);
6424 assert(
6425 editor,
6426 cx,
6427 indoc! {"
6428 a.f(one, two, three)ˇ b
6429 a.f(one, two, three)ˇ b
6430 a.f(one, two, three)ˇ b
6431 "},
6432 );
6433 });
6434}
6435
6436#[gpui::test]
6437async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6438 init_test(cx, |_| {});
6439
6440 let fs = FakeFs::new(cx.executor());
6441 fs.insert_file("/file.rs", Default::default()).await;
6442
6443 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6444
6445 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6446 language_registry.add(rust_lang());
6447 let mut fake_servers = language_registry.register_fake_lsp(
6448 "Rust",
6449 FakeLspAdapter {
6450 capabilities: lsp::ServerCapabilities {
6451 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6452 ..Default::default()
6453 },
6454 ..Default::default()
6455 },
6456 );
6457
6458 let buffer = project
6459 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6460 .await
6461 .unwrap();
6462
6463 cx.executor().start_waiting();
6464 let fake_server = fake_servers.next().await.unwrap();
6465
6466 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6467 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6468 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6469 assert!(cx.read(|cx| editor.is_dirty(cx)));
6470
6471 let save = editor
6472 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6473 .unwrap();
6474 fake_server
6475 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6476 assert_eq!(
6477 params.text_document.uri,
6478 lsp::Url::from_file_path("/file.rs").unwrap()
6479 );
6480 assert_eq!(params.options.tab_size, 4);
6481 Ok(Some(vec![lsp::TextEdit::new(
6482 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6483 ", ".to_string(),
6484 )]))
6485 })
6486 .next()
6487 .await;
6488 cx.executor().start_waiting();
6489 save.await;
6490
6491 assert_eq!(
6492 editor.update(cx, |editor, cx| editor.text(cx)),
6493 "one, two\nthree\n"
6494 );
6495 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6496
6497 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6498 assert!(cx.read(|cx| editor.is_dirty(cx)));
6499
6500 // Ensure we can still save even if formatting hangs.
6501 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6502 assert_eq!(
6503 params.text_document.uri,
6504 lsp::Url::from_file_path("/file.rs").unwrap()
6505 );
6506 futures::future::pending::<()>().await;
6507 unreachable!()
6508 });
6509 let save = editor
6510 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6511 .unwrap();
6512 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6513 cx.executor().start_waiting();
6514 save.await;
6515 assert_eq!(
6516 editor.update(cx, |editor, cx| editor.text(cx)),
6517 "one\ntwo\nthree\n"
6518 );
6519 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6520
6521 // For non-dirty buffer, no formatting request should be sent
6522 let save = editor
6523 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6524 .unwrap();
6525 let _pending_format_request = fake_server
6526 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6527 panic!("Should not be invoked on non-dirty buffer");
6528 })
6529 .next();
6530 cx.executor().start_waiting();
6531 save.await;
6532
6533 // Set rust language override and assert overridden tabsize is sent to language server
6534 update_test_language_settings(cx, |settings| {
6535 settings.languages.insert(
6536 "Rust".into(),
6537 LanguageSettingsContent {
6538 tab_size: NonZeroU32::new(8),
6539 ..Default::default()
6540 },
6541 );
6542 });
6543
6544 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6545 assert!(cx.read(|cx| editor.is_dirty(cx)));
6546 let save = editor
6547 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6548 .unwrap();
6549 fake_server
6550 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6551 assert_eq!(
6552 params.text_document.uri,
6553 lsp::Url::from_file_path("/file.rs").unwrap()
6554 );
6555 assert_eq!(params.options.tab_size, 8);
6556 Ok(Some(vec![]))
6557 })
6558 .next()
6559 .await;
6560 cx.executor().start_waiting();
6561 save.await;
6562}
6563
6564#[gpui::test]
6565async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6566 init_test(cx, |_| {});
6567
6568 let cols = 4;
6569 let rows = 10;
6570 let sample_text_1 = sample_text(rows, cols, 'a');
6571 assert_eq!(
6572 sample_text_1,
6573 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6574 );
6575 let sample_text_2 = sample_text(rows, cols, 'l');
6576 assert_eq!(
6577 sample_text_2,
6578 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6579 );
6580 let sample_text_3 = sample_text(rows, cols, 'v');
6581 assert_eq!(
6582 sample_text_3,
6583 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6584 );
6585
6586 let fs = FakeFs::new(cx.executor());
6587 fs.insert_tree(
6588 "/a",
6589 json!({
6590 "main.rs": sample_text_1,
6591 "other.rs": sample_text_2,
6592 "lib.rs": sample_text_3,
6593 }),
6594 )
6595 .await;
6596
6597 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6598 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6599 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6600
6601 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6602 language_registry.add(rust_lang());
6603 let mut fake_servers = language_registry.register_fake_lsp(
6604 "Rust",
6605 FakeLspAdapter {
6606 capabilities: lsp::ServerCapabilities {
6607 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6608 ..Default::default()
6609 },
6610 ..Default::default()
6611 },
6612 );
6613
6614 let worktree = project.update(cx, |project, cx| {
6615 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6616 assert_eq!(worktrees.len(), 1);
6617 worktrees.pop().unwrap()
6618 });
6619 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6620
6621 let buffer_1 = project
6622 .update(cx, |project, cx| {
6623 project.open_buffer((worktree_id, "main.rs"), cx)
6624 })
6625 .await
6626 .unwrap();
6627 let buffer_2 = project
6628 .update(cx, |project, cx| {
6629 project.open_buffer((worktree_id, "other.rs"), cx)
6630 })
6631 .await
6632 .unwrap();
6633 let buffer_3 = project
6634 .update(cx, |project, cx| {
6635 project.open_buffer((worktree_id, "lib.rs"), cx)
6636 })
6637 .await
6638 .unwrap();
6639
6640 let multi_buffer = cx.new_model(|cx| {
6641 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6642 multi_buffer.push_excerpts(
6643 buffer_1.clone(),
6644 [
6645 ExcerptRange {
6646 context: Point::new(0, 0)..Point::new(3, 0),
6647 primary: None,
6648 },
6649 ExcerptRange {
6650 context: Point::new(5, 0)..Point::new(7, 0),
6651 primary: None,
6652 },
6653 ExcerptRange {
6654 context: Point::new(9, 0)..Point::new(10, 4),
6655 primary: None,
6656 },
6657 ],
6658 cx,
6659 );
6660 multi_buffer.push_excerpts(
6661 buffer_2.clone(),
6662 [
6663 ExcerptRange {
6664 context: Point::new(0, 0)..Point::new(3, 0),
6665 primary: None,
6666 },
6667 ExcerptRange {
6668 context: Point::new(5, 0)..Point::new(7, 0),
6669 primary: None,
6670 },
6671 ExcerptRange {
6672 context: Point::new(9, 0)..Point::new(10, 4),
6673 primary: None,
6674 },
6675 ],
6676 cx,
6677 );
6678 multi_buffer.push_excerpts(
6679 buffer_3.clone(),
6680 [
6681 ExcerptRange {
6682 context: Point::new(0, 0)..Point::new(3, 0),
6683 primary: None,
6684 },
6685 ExcerptRange {
6686 context: Point::new(5, 0)..Point::new(7, 0),
6687 primary: None,
6688 },
6689 ExcerptRange {
6690 context: Point::new(9, 0)..Point::new(10, 4),
6691 primary: None,
6692 },
6693 ],
6694 cx,
6695 );
6696 multi_buffer
6697 });
6698 let multi_buffer_editor = cx.new_view(|cx| {
6699 Editor::new(
6700 EditorMode::Full,
6701 multi_buffer,
6702 Some(project.clone()),
6703 true,
6704 cx,
6705 )
6706 });
6707
6708 multi_buffer_editor.update(cx, |editor, cx| {
6709 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6710 editor.insert("|one|two|three|", cx);
6711 });
6712 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6713 multi_buffer_editor.update(cx, |editor, cx| {
6714 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6715 s.select_ranges(Some(60..70))
6716 });
6717 editor.insert("|four|five|six|", cx);
6718 });
6719 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6720
6721 // First two buffers should be edited, but not the third one.
6722 assert_eq!(
6723 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6724 "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}",
6725 );
6726 buffer_1.update(cx, |buffer, _| {
6727 assert!(buffer.is_dirty());
6728 assert_eq!(
6729 buffer.text(),
6730 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6731 )
6732 });
6733 buffer_2.update(cx, |buffer, _| {
6734 assert!(buffer.is_dirty());
6735 assert_eq!(
6736 buffer.text(),
6737 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6738 )
6739 });
6740 buffer_3.update(cx, |buffer, _| {
6741 assert!(!buffer.is_dirty());
6742 assert_eq!(buffer.text(), sample_text_3,)
6743 });
6744
6745 cx.executor().start_waiting();
6746 let save = multi_buffer_editor
6747 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6748 .unwrap();
6749
6750 let fake_server = fake_servers.next().await.unwrap();
6751 fake_server
6752 .server
6753 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6754 Ok(Some(vec![lsp::TextEdit::new(
6755 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6756 format!("[{} formatted]", params.text_document.uri),
6757 )]))
6758 })
6759 .detach();
6760 save.await;
6761
6762 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6763 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6764 assert_eq!(
6765 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6766 "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}",
6767 );
6768 buffer_1.update(cx, |buffer, _| {
6769 assert!(!buffer.is_dirty());
6770 assert_eq!(
6771 buffer.text(),
6772 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6773 )
6774 });
6775 buffer_2.update(cx, |buffer, _| {
6776 assert!(!buffer.is_dirty());
6777 assert_eq!(
6778 buffer.text(),
6779 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6780 )
6781 });
6782 buffer_3.update(cx, |buffer, _| {
6783 assert!(!buffer.is_dirty());
6784 assert_eq!(buffer.text(), sample_text_3,)
6785 });
6786}
6787
6788#[gpui::test]
6789async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6790 init_test(cx, |_| {});
6791
6792 let fs = FakeFs::new(cx.executor());
6793 fs.insert_file("/file.rs", Default::default()).await;
6794
6795 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6796
6797 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6798 language_registry.add(rust_lang());
6799 let mut fake_servers = language_registry.register_fake_lsp(
6800 "Rust",
6801 FakeLspAdapter {
6802 capabilities: lsp::ServerCapabilities {
6803 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6804 ..Default::default()
6805 },
6806 ..Default::default()
6807 },
6808 );
6809
6810 let buffer = project
6811 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6812 .await
6813 .unwrap();
6814
6815 cx.executor().start_waiting();
6816 let fake_server = fake_servers.next().await.unwrap();
6817
6818 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6819 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6820 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6821 assert!(cx.read(|cx| editor.is_dirty(cx)));
6822
6823 let save = editor
6824 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6825 .unwrap();
6826 fake_server
6827 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6828 assert_eq!(
6829 params.text_document.uri,
6830 lsp::Url::from_file_path("/file.rs").unwrap()
6831 );
6832 assert_eq!(params.options.tab_size, 4);
6833 Ok(Some(vec![lsp::TextEdit::new(
6834 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6835 ", ".to_string(),
6836 )]))
6837 })
6838 .next()
6839 .await;
6840 cx.executor().start_waiting();
6841 save.await;
6842 assert_eq!(
6843 editor.update(cx, |editor, cx| editor.text(cx)),
6844 "one, two\nthree\n"
6845 );
6846 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6847
6848 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6849 assert!(cx.read(|cx| editor.is_dirty(cx)));
6850
6851 // Ensure we can still save even if formatting hangs.
6852 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6853 move |params, _| async move {
6854 assert_eq!(
6855 params.text_document.uri,
6856 lsp::Url::from_file_path("/file.rs").unwrap()
6857 );
6858 futures::future::pending::<()>().await;
6859 unreachable!()
6860 },
6861 );
6862 let save = editor
6863 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6864 .unwrap();
6865 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6866 cx.executor().start_waiting();
6867 save.await;
6868 assert_eq!(
6869 editor.update(cx, |editor, cx| editor.text(cx)),
6870 "one\ntwo\nthree\n"
6871 );
6872 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6873
6874 // For non-dirty buffer, no formatting request should be sent
6875 let save = editor
6876 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6877 .unwrap();
6878 let _pending_format_request = fake_server
6879 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6880 panic!("Should not be invoked on non-dirty buffer");
6881 })
6882 .next();
6883 cx.executor().start_waiting();
6884 save.await;
6885
6886 // Set Rust language override and assert overridden tabsize is sent to language server
6887 update_test_language_settings(cx, |settings| {
6888 settings.languages.insert(
6889 "Rust".into(),
6890 LanguageSettingsContent {
6891 tab_size: NonZeroU32::new(8),
6892 ..Default::default()
6893 },
6894 );
6895 });
6896
6897 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6898 assert!(cx.read(|cx| editor.is_dirty(cx)));
6899 let save = editor
6900 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6901 .unwrap();
6902 fake_server
6903 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6904 assert_eq!(
6905 params.text_document.uri,
6906 lsp::Url::from_file_path("/file.rs").unwrap()
6907 );
6908 assert_eq!(params.options.tab_size, 8);
6909 Ok(Some(vec![]))
6910 })
6911 .next()
6912 .await;
6913 cx.executor().start_waiting();
6914 save.await;
6915}
6916
6917#[gpui::test]
6918async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6919 init_test(cx, |settings| {
6920 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6921 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6922 ))
6923 });
6924
6925 let fs = FakeFs::new(cx.executor());
6926 fs.insert_file("/file.rs", Default::default()).await;
6927
6928 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6929
6930 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6931 language_registry.add(Arc::new(Language::new(
6932 LanguageConfig {
6933 name: "Rust".into(),
6934 matcher: LanguageMatcher {
6935 path_suffixes: vec!["rs".to_string()],
6936 ..Default::default()
6937 },
6938 ..LanguageConfig::default()
6939 },
6940 Some(tree_sitter_rust::LANGUAGE.into()),
6941 )));
6942 update_test_language_settings(cx, |settings| {
6943 // Enable Prettier formatting for the same buffer, and ensure
6944 // LSP is called instead of Prettier.
6945 settings.defaults.prettier = Some(PrettierSettings {
6946 allowed: true,
6947 ..PrettierSettings::default()
6948 });
6949 });
6950 let mut fake_servers = language_registry.register_fake_lsp(
6951 "Rust",
6952 FakeLspAdapter {
6953 capabilities: lsp::ServerCapabilities {
6954 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6955 ..Default::default()
6956 },
6957 ..Default::default()
6958 },
6959 );
6960
6961 let buffer = project
6962 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6963 .await
6964 .unwrap();
6965
6966 cx.executor().start_waiting();
6967 let fake_server = fake_servers.next().await.unwrap();
6968
6969 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6970 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6971 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6972
6973 let format = editor
6974 .update(cx, |editor, cx| {
6975 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6976 })
6977 .unwrap();
6978 fake_server
6979 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6980 assert_eq!(
6981 params.text_document.uri,
6982 lsp::Url::from_file_path("/file.rs").unwrap()
6983 );
6984 assert_eq!(params.options.tab_size, 4);
6985 Ok(Some(vec![lsp::TextEdit::new(
6986 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6987 ", ".to_string(),
6988 )]))
6989 })
6990 .next()
6991 .await;
6992 cx.executor().start_waiting();
6993 format.await;
6994 assert_eq!(
6995 editor.update(cx, |editor, cx| editor.text(cx)),
6996 "one, two\nthree\n"
6997 );
6998
6999 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7000 // Ensure we don't lock if formatting hangs.
7001 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7002 assert_eq!(
7003 params.text_document.uri,
7004 lsp::Url::from_file_path("/file.rs").unwrap()
7005 );
7006 futures::future::pending::<()>().await;
7007 unreachable!()
7008 });
7009 let format = editor
7010 .update(cx, |editor, cx| {
7011 editor.perform_format(project, FormatTrigger::Manual, cx)
7012 })
7013 .unwrap();
7014 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7015 cx.executor().start_waiting();
7016 format.await;
7017 assert_eq!(
7018 editor.update(cx, |editor, cx| editor.text(cx)),
7019 "one\ntwo\nthree\n"
7020 );
7021}
7022
7023#[gpui::test]
7024async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7025 init_test(cx, |_| {});
7026
7027 let mut cx = EditorLspTestContext::new_rust(
7028 lsp::ServerCapabilities {
7029 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7030 ..Default::default()
7031 },
7032 cx,
7033 )
7034 .await;
7035
7036 cx.set_state(indoc! {"
7037 one.twoˇ
7038 "});
7039
7040 // The format request takes a long time. When it completes, it inserts
7041 // a newline and an indent before the `.`
7042 cx.lsp
7043 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7044 let executor = cx.background_executor().clone();
7045 async move {
7046 executor.timer(Duration::from_millis(100)).await;
7047 Ok(Some(vec![lsp::TextEdit {
7048 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7049 new_text: "\n ".into(),
7050 }]))
7051 }
7052 });
7053
7054 // Submit a format request.
7055 let format_1 = cx
7056 .update_editor(|editor, cx| editor.format(&Format, cx))
7057 .unwrap();
7058 cx.executor().run_until_parked();
7059
7060 // Submit a second format request.
7061 let format_2 = cx
7062 .update_editor(|editor, cx| editor.format(&Format, cx))
7063 .unwrap();
7064 cx.executor().run_until_parked();
7065
7066 // Wait for both format requests to complete
7067 cx.executor().advance_clock(Duration::from_millis(200));
7068 cx.executor().start_waiting();
7069 format_1.await.unwrap();
7070 cx.executor().start_waiting();
7071 format_2.await.unwrap();
7072
7073 // The formatting edits only happens once.
7074 cx.assert_editor_state(indoc! {"
7075 one
7076 .twoˇ
7077 "});
7078}
7079
7080#[gpui::test]
7081async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7082 init_test(cx, |settings| {
7083 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7084 });
7085
7086 let mut cx = EditorLspTestContext::new_rust(
7087 lsp::ServerCapabilities {
7088 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7089 ..Default::default()
7090 },
7091 cx,
7092 )
7093 .await;
7094
7095 // Set up a buffer white some trailing whitespace and no trailing newline.
7096 cx.set_state(
7097 &[
7098 "one ", //
7099 "twoˇ", //
7100 "three ", //
7101 "four", //
7102 ]
7103 .join("\n"),
7104 );
7105
7106 // Submit a format request.
7107 let format = cx
7108 .update_editor(|editor, cx| editor.format(&Format, cx))
7109 .unwrap();
7110
7111 // Record which buffer changes have been sent to the language server
7112 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7113 cx.lsp
7114 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7115 let buffer_changes = buffer_changes.clone();
7116 move |params, _| {
7117 buffer_changes.lock().extend(
7118 params
7119 .content_changes
7120 .into_iter()
7121 .map(|e| (e.range.unwrap(), e.text)),
7122 );
7123 }
7124 });
7125
7126 // Handle formatting requests to the language server.
7127 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7128 let buffer_changes = buffer_changes.clone();
7129 move |_, _| {
7130 // When formatting is requested, trailing whitespace has already been stripped,
7131 // and the trailing newline has already been added.
7132 assert_eq!(
7133 &buffer_changes.lock()[1..],
7134 &[
7135 (
7136 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7137 "".into()
7138 ),
7139 (
7140 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7141 "".into()
7142 ),
7143 (
7144 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7145 "\n".into()
7146 ),
7147 ]
7148 );
7149
7150 // Insert blank lines between each line of the buffer.
7151 async move {
7152 Ok(Some(vec![
7153 lsp::TextEdit {
7154 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7155 new_text: "\n".into(),
7156 },
7157 lsp::TextEdit {
7158 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7159 new_text: "\n".into(),
7160 },
7161 ]))
7162 }
7163 }
7164 });
7165
7166 // After formatting the buffer, the trailing whitespace is stripped,
7167 // a newline is appended, and the edits provided by the language server
7168 // have been applied.
7169 format.await.unwrap();
7170 cx.assert_editor_state(
7171 &[
7172 "one", //
7173 "", //
7174 "twoˇ", //
7175 "", //
7176 "three", //
7177 "four", //
7178 "", //
7179 ]
7180 .join("\n"),
7181 );
7182
7183 // Undoing the formatting undoes the trailing whitespace removal, the
7184 // trailing newline, and the LSP edits.
7185 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7186 cx.assert_editor_state(
7187 &[
7188 "one ", //
7189 "twoˇ", //
7190 "three ", //
7191 "four", //
7192 ]
7193 .join("\n"),
7194 );
7195}
7196
7197#[gpui::test]
7198async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7199 cx: &mut gpui::TestAppContext,
7200) {
7201 init_test(cx, |_| {});
7202
7203 cx.update(|cx| {
7204 cx.update_global::<SettingsStore, _>(|settings, cx| {
7205 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7206 settings.auto_signature_help = Some(true);
7207 });
7208 });
7209 });
7210
7211 let mut cx = EditorLspTestContext::new_rust(
7212 lsp::ServerCapabilities {
7213 signature_help_provider: Some(lsp::SignatureHelpOptions {
7214 ..Default::default()
7215 }),
7216 ..Default::default()
7217 },
7218 cx,
7219 )
7220 .await;
7221
7222 let language = Language::new(
7223 LanguageConfig {
7224 name: "Rust".into(),
7225 brackets: BracketPairConfig {
7226 pairs: vec![
7227 BracketPair {
7228 start: "{".to_string(),
7229 end: "}".to_string(),
7230 close: true,
7231 surround: true,
7232 newline: true,
7233 },
7234 BracketPair {
7235 start: "(".to_string(),
7236 end: ")".to_string(),
7237 close: true,
7238 surround: true,
7239 newline: true,
7240 },
7241 BracketPair {
7242 start: "/*".to_string(),
7243 end: " */".to_string(),
7244 close: true,
7245 surround: true,
7246 newline: true,
7247 },
7248 BracketPair {
7249 start: "[".to_string(),
7250 end: "]".to_string(),
7251 close: false,
7252 surround: false,
7253 newline: true,
7254 },
7255 BracketPair {
7256 start: "\"".to_string(),
7257 end: "\"".to_string(),
7258 close: true,
7259 surround: true,
7260 newline: false,
7261 },
7262 BracketPair {
7263 start: "<".to_string(),
7264 end: ">".to_string(),
7265 close: false,
7266 surround: true,
7267 newline: true,
7268 },
7269 ],
7270 ..Default::default()
7271 },
7272 autoclose_before: "})]".to_string(),
7273 ..Default::default()
7274 },
7275 Some(tree_sitter_rust::LANGUAGE.into()),
7276 );
7277 let language = Arc::new(language);
7278
7279 cx.language_registry().add(language.clone());
7280 cx.update_buffer(|buffer, cx| {
7281 buffer.set_language(Some(language), cx);
7282 });
7283
7284 cx.set_state(
7285 &r#"
7286 fn main() {
7287 sampleˇ
7288 }
7289 "#
7290 .unindent(),
7291 );
7292
7293 cx.update_editor(|view, cx| {
7294 view.handle_input("(", cx);
7295 });
7296 cx.assert_editor_state(
7297 &"
7298 fn main() {
7299 sample(ˇ)
7300 }
7301 "
7302 .unindent(),
7303 );
7304
7305 let mocked_response = lsp::SignatureHelp {
7306 signatures: vec![lsp::SignatureInformation {
7307 label: "fn sample(param1: u8, param2: u8)".to_string(),
7308 documentation: None,
7309 parameters: Some(vec![
7310 lsp::ParameterInformation {
7311 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7312 documentation: None,
7313 },
7314 lsp::ParameterInformation {
7315 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7316 documentation: None,
7317 },
7318 ]),
7319 active_parameter: None,
7320 }],
7321 active_signature: Some(0),
7322 active_parameter: Some(0),
7323 };
7324 handle_signature_help_request(&mut cx, mocked_response).await;
7325
7326 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7327 .await;
7328
7329 cx.editor(|editor, _| {
7330 let signature_help_state = editor.signature_help_state.popover().cloned();
7331 assert!(signature_help_state.is_some());
7332 let ParsedMarkdown {
7333 text, highlights, ..
7334 } = signature_help_state.unwrap().parsed_content;
7335 assert_eq!(text, "param1: u8, param2: u8");
7336 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7337 });
7338}
7339
7340#[gpui::test]
7341async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7342 init_test(cx, |_| {});
7343
7344 cx.update(|cx| {
7345 cx.update_global::<SettingsStore, _>(|settings, cx| {
7346 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7347 settings.auto_signature_help = Some(false);
7348 settings.show_signature_help_after_edits = Some(false);
7349 });
7350 });
7351 });
7352
7353 let mut cx = EditorLspTestContext::new_rust(
7354 lsp::ServerCapabilities {
7355 signature_help_provider: Some(lsp::SignatureHelpOptions {
7356 ..Default::default()
7357 }),
7358 ..Default::default()
7359 },
7360 cx,
7361 )
7362 .await;
7363
7364 let language = Language::new(
7365 LanguageConfig {
7366 name: "Rust".into(),
7367 brackets: BracketPairConfig {
7368 pairs: vec![
7369 BracketPair {
7370 start: "{".to_string(),
7371 end: "}".to_string(),
7372 close: true,
7373 surround: true,
7374 newline: true,
7375 },
7376 BracketPair {
7377 start: "(".to_string(),
7378 end: ")".to_string(),
7379 close: true,
7380 surround: true,
7381 newline: true,
7382 },
7383 BracketPair {
7384 start: "/*".to_string(),
7385 end: " */".to_string(),
7386 close: true,
7387 surround: true,
7388 newline: true,
7389 },
7390 BracketPair {
7391 start: "[".to_string(),
7392 end: "]".to_string(),
7393 close: false,
7394 surround: false,
7395 newline: true,
7396 },
7397 BracketPair {
7398 start: "\"".to_string(),
7399 end: "\"".to_string(),
7400 close: true,
7401 surround: true,
7402 newline: false,
7403 },
7404 BracketPair {
7405 start: "<".to_string(),
7406 end: ">".to_string(),
7407 close: false,
7408 surround: true,
7409 newline: true,
7410 },
7411 ],
7412 ..Default::default()
7413 },
7414 autoclose_before: "})]".to_string(),
7415 ..Default::default()
7416 },
7417 Some(tree_sitter_rust::LANGUAGE.into()),
7418 );
7419 let language = Arc::new(language);
7420
7421 cx.language_registry().add(language.clone());
7422 cx.update_buffer(|buffer, cx| {
7423 buffer.set_language(Some(language), cx);
7424 });
7425
7426 // Ensure that signature_help is not called when no signature help is enabled.
7427 cx.set_state(
7428 &r#"
7429 fn main() {
7430 sampleˇ
7431 }
7432 "#
7433 .unindent(),
7434 );
7435 cx.update_editor(|view, cx| {
7436 view.handle_input("(", cx);
7437 });
7438 cx.assert_editor_state(
7439 &"
7440 fn main() {
7441 sample(ˇ)
7442 }
7443 "
7444 .unindent(),
7445 );
7446 cx.editor(|editor, _| {
7447 assert!(editor.signature_help_state.task().is_none());
7448 });
7449
7450 let mocked_response = lsp::SignatureHelp {
7451 signatures: vec![lsp::SignatureInformation {
7452 label: "fn sample(param1: u8, param2: u8)".to_string(),
7453 documentation: None,
7454 parameters: Some(vec![
7455 lsp::ParameterInformation {
7456 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7457 documentation: None,
7458 },
7459 lsp::ParameterInformation {
7460 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7461 documentation: None,
7462 },
7463 ]),
7464 active_parameter: None,
7465 }],
7466 active_signature: Some(0),
7467 active_parameter: Some(0),
7468 };
7469
7470 // Ensure that signature_help is called when enabled afte edits
7471 cx.update(|cx| {
7472 cx.update_global::<SettingsStore, _>(|settings, cx| {
7473 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7474 settings.auto_signature_help = Some(false);
7475 settings.show_signature_help_after_edits = Some(true);
7476 });
7477 });
7478 });
7479 cx.set_state(
7480 &r#"
7481 fn main() {
7482 sampleˇ
7483 }
7484 "#
7485 .unindent(),
7486 );
7487 cx.update_editor(|view, cx| {
7488 view.handle_input("(", cx);
7489 });
7490 cx.assert_editor_state(
7491 &"
7492 fn main() {
7493 sample(ˇ)
7494 }
7495 "
7496 .unindent(),
7497 );
7498 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7499 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7500 .await;
7501 cx.update_editor(|editor, _| {
7502 let signature_help_state = editor.signature_help_state.popover().cloned();
7503 assert!(signature_help_state.is_some());
7504 let ParsedMarkdown {
7505 text, highlights, ..
7506 } = signature_help_state.unwrap().parsed_content;
7507 assert_eq!(text, "param1: u8, param2: u8");
7508 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7509 editor.signature_help_state = SignatureHelpState::default();
7510 });
7511
7512 // Ensure that signature_help is called when auto signature help override is enabled
7513 cx.update(|cx| {
7514 cx.update_global::<SettingsStore, _>(|settings, cx| {
7515 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7516 settings.auto_signature_help = Some(true);
7517 settings.show_signature_help_after_edits = Some(false);
7518 });
7519 });
7520 });
7521 cx.set_state(
7522 &r#"
7523 fn main() {
7524 sampleˇ
7525 }
7526 "#
7527 .unindent(),
7528 );
7529 cx.update_editor(|view, cx| {
7530 view.handle_input("(", cx);
7531 });
7532 cx.assert_editor_state(
7533 &"
7534 fn main() {
7535 sample(ˇ)
7536 }
7537 "
7538 .unindent(),
7539 );
7540 handle_signature_help_request(&mut cx, mocked_response).await;
7541 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7542 .await;
7543 cx.editor(|editor, _| {
7544 let signature_help_state = editor.signature_help_state.popover().cloned();
7545 assert!(signature_help_state.is_some());
7546 let ParsedMarkdown {
7547 text, highlights, ..
7548 } = signature_help_state.unwrap().parsed_content;
7549 assert_eq!(text, "param1: u8, param2: u8");
7550 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7551 });
7552}
7553
7554#[gpui::test]
7555async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7556 init_test(cx, |_| {});
7557 cx.update(|cx| {
7558 cx.update_global::<SettingsStore, _>(|settings, cx| {
7559 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7560 settings.auto_signature_help = Some(true);
7561 });
7562 });
7563 });
7564
7565 let mut cx = EditorLspTestContext::new_rust(
7566 lsp::ServerCapabilities {
7567 signature_help_provider: Some(lsp::SignatureHelpOptions {
7568 ..Default::default()
7569 }),
7570 ..Default::default()
7571 },
7572 cx,
7573 )
7574 .await;
7575
7576 // A test that directly calls `show_signature_help`
7577 cx.update_editor(|editor, cx| {
7578 editor.show_signature_help(&ShowSignatureHelp, cx);
7579 });
7580
7581 let mocked_response = lsp::SignatureHelp {
7582 signatures: vec![lsp::SignatureInformation {
7583 label: "fn sample(param1: u8, param2: u8)".to_string(),
7584 documentation: None,
7585 parameters: Some(vec![
7586 lsp::ParameterInformation {
7587 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7588 documentation: None,
7589 },
7590 lsp::ParameterInformation {
7591 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7592 documentation: None,
7593 },
7594 ]),
7595 active_parameter: None,
7596 }],
7597 active_signature: Some(0),
7598 active_parameter: Some(0),
7599 };
7600 handle_signature_help_request(&mut cx, mocked_response).await;
7601
7602 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7603 .await;
7604
7605 cx.editor(|editor, _| {
7606 let signature_help_state = editor.signature_help_state.popover().cloned();
7607 assert!(signature_help_state.is_some());
7608 let ParsedMarkdown {
7609 text, highlights, ..
7610 } = signature_help_state.unwrap().parsed_content;
7611 assert_eq!(text, "param1: u8, param2: u8");
7612 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7613 });
7614
7615 // When exiting outside from inside the brackets, `signature_help` is closed.
7616 cx.set_state(indoc! {"
7617 fn main() {
7618 sample(ˇ);
7619 }
7620
7621 fn sample(param1: u8, param2: u8) {}
7622 "});
7623
7624 cx.update_editor(|editor, cx| {
7625 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7626 });
7627
7628 let mocked_response = lsp::SignatureHelp {
7629 signatures: Vec::new(),
7630 active_signature: None,
7631 active_parameter: None,
7632 };
7633 handle_signature_help_request(&mut cx, mocked_response).await;
7634
7635 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7636 .await;
7637
7638 cx.editor(|editor, _| {
7639 assert!(!editor.signature_help_state.is_shown());
7640 });
7641
7642 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7643 cx.set_state(indoc! {"
7644 fn main() {
7645 sample(ˇ);
7646 }
7647
7648 fn sample(param1: u8, param2: u8) {}
7649 "});
7650
7651 let mocked_response = lsp::SignatureHelp {
7652 signatures: vec![lsp::SignatureInformation {
7653 label: "fn sample(param1: u8, param2: u8)".to_string(),
7654 documentation: None,
7655 parameters: Some(vec![
7656 lsp::ParameterInformation {
7657 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7658 documentation: None,
7659 },
7660 lsp::ParameterInformation {
7661 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7662 documentation: None,
7663 },
7664 ]),
7665 active_parameter: None,
7666 }],
7667 active_signature: Some(0),
7668 active_parameter: Some(0),
7669 };
7670 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7671 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7672 .await;
7673 cx.editor(|editor, _| {
7674 assert!(editor.signature_help_state.is_shown());
7675 });
7676
7677 // Restore the popover with more parameter input
7678 cx.set_state(indoc! {"
7679 fn main() {
7680 sample(param1, param2ˇ);
7681 }
7682
7683 fn sample(param1: u8, param2: u8) {}
7684 "});
7685
7686 let mocked_response = lsp::SignatureHelp {
7687 signatures: vec![lsp::SignatureInformation {
7688 label: "fn sample(param1: u8, param2: u8)".to_string(),
7689 documentation: None,
7690 parameters: Some(vec![
7691 lsp::ParameterInformation {
7692 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7693 documentation: None,
7694 },
7695 lsp::ParameterInformation {
7696 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7697 documentation: None,
7698 },
7699 ]),
7700 active_parameter: None,
7701 }],
7702 active_signature: Some(0),
7703 active_parameter: Some(1),
7704 };
7705 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7706 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7707 .await;
7708
7709 // When selecting a range, the popover is gone.
7710 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7711 cx.update_editor(|editor, cx| {
7712 editor.change_selections(None, cx, |s| {
7713 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7714 })
7715 });
7716 cx.assert_editor_state(indoc! {"
7717 fn main() {
7718 sample(param1, «ˇparam2»);
7719 }
7720
7721 fn sample(param1: u8, param2: u8) {}
7722 "});
7723 cx.editor(|editor, _| {
7724 assert!(!editor.signature_help_state.is_shown());
7725 });
7726
7727 // When unselecting again, the popover is back if within the brackets.
7728 cx.update_editor(|editor, cx| {
7729 editor.change_selections(None, cx, |s| {
7730 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7731 })
7732 });
7733 cx.assert_editor_state(indoc! {"
7734 fn main() {
7735 sample(param1, ˇparam2);
7736 }
7737
7738 fn sample(param1: u8, param2: u8) {}
7739 "});
7740 handle_signature_help_request(&mut cx, mocked_response).await;
7741 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7742 .await;
7743 cx.editor(|editor, _| {
7744 assert!(editor.signature_help_state.is_shown());
7745 });
7746
7747 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7748 cx.update_editor(|editor, cx| {
7749 editor.change_selections(None, cx, |s| {
7750 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7751 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7752 })
7753 });
7754 cx.assert_editor_state(indoc! {"
7755 fn main() {
7756 sample(param1, ˇparam2);
7757 }
7758
7759 fn sample(param1: u8, param2: u8) {}
7760 "});
7761
7762 let mocked_response = lsp::SignatureHelp {
7763 signatures: vec![lsp::SignatureInformation {
7764 label: "fn sample(param1: u8, param2: u8)".to_string(),
7765 documentation: None,
7766 parameters: Some(vec![
7767 lsp::ParameterInformation {
7768 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7769 documentation: None,
7770 },
7771 lsp::ParameterInformation {
7772 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7773 documentation: None,
7774 },
7775 ]),
7776 active_parameter: None,
7777 }],
7778 active_signature: Some(0),
7779 active_parameter: Some(1),
7780 };
7781 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7782 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7783 .await;
7784 cx.update_editor(|editor, cx| {
7785 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7786 });
7787 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7788 .await;
7789 cx.update_editor(|editor, cx| {
7790 editor.change_selections(None, cx, |s| {
7791 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7792 })
7793 });
7794 cx.assert_editor_state(indoc! {"
7795 fn main() {
7796 sample(param1, «ˇparam2»);
7797 }
7798
7799 fn sample(param1: u8, param2: u8) {}
7800 "});
7801 cx.update_editor(|editor, cx| {
7802 editor.change_selections(None, cx, |s| {
7803 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7804 })
7805 });
7806 cx.assert_editor_state(indoc! {"
7807 fn main() {
7808 sample(param1, ˇparam2);
7809 }
7810
7811 fn sample(param1: u8, param2: u8) {}
7812 "});
7813 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7814 .await;
7815}
7816
7817#[gpui::test]
7818async fn test_completion(cx: &mut gpui::TestAppContext) {
7819 init_test(cx, |_| {});
7820
7821 let mut cx = EditorLspTestContext::new_rust(
7822 lsp::ServerCapabilities {
7823 completion_provider: Some(lsp::CompletionOptions {
7824 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7825 resolve_provider: Some(true),
7826 ..Default::default()
7827 }),
7828 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7829 ..Default::default()
7830 },
7831 cx,
7832 )
7833 .await;
7834 let counter = Arc::new(AtomicUsize::new(0));
7835
7836 cx.set_state(indoc! {"
7837 oneˇ
7838 two
7839 three
7840 "});
7841 cx.simulate_keystroke(".");
7842 handle_completion_request(
7843 &mut cx,
7844 indoc! {"
7845 one.|<>
7846 two
7847 three
7848 "},
7849 vec!["first_completion", "second_completion"],
7850 counter.clone(),
7851 )
7852 .await;
7853 cx.condition(|editor, _| editor.context_menu_visible())
7854 .await;
7855 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7856
7857 let _handler = handle_signature_help_request(
7858 &mut cx,
7859 lsp::SignatureHelp {
7860 signatures: vec![lsp::SignatureInformation {
7861 label: "test signature".to_string(),
7862 documentation: None,
7863 parameters: Some(vec![lsp::ParameterInformation {
7864 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7865 documentation: None,
7866 }]),
7867 active_parameter: None,
7868 }],
7869 active_signature: None,
7870 active_parameter: None,
7871 },
7872 );
7873 cx.update_editor(|editor, cx| {
7874 assert!(
7875 !editor.signature_help_state.is_shown(),
7876 "No signature help was called for"
7877 );
7878 editor.show_signature_help(&ShowSignatureHelp, cx);
7879 });
7880 cx.run_until_parked();
7881 cx.update_editor(|editor, _| {
7882 assert!(
7883 !editor.signature_help_state.is_shown(),
7884 "No signature help should be shown when completions menu is open"
7885 );
7886 });
7887
7888 let apply_additional_edits = cx.update_editor(|editor, cx| {
7889 editor.context_menu_next(&Default::default(), cx);
7890 editor
7891 .confirm_completion(&ConfirmCompletion::default(), cx)
7892 .unwrap()
7893 });
7894 cx.assert_editor_state(indoc! {"
7895 one.second_completionˇ
7896 two
7897 three
7898 "});
7899
7900 handle_resolve_completion_request(
7901 &mut cx,
7902 Some(vec![
7903 (
7904 //This overlaps with the primary completion edit which is
7905 //misbehavior from the LSP spec, test that we filter it out
7906 indoc! {"
7907 one.second_ˇcompletion
7908 two
7909 threeˇ
7910 "},
7911 "overlapping additional edit",
7912 ),
7913 (
7914 indoc! {"
7915 one.second_completion
7916 two
7917 threeˇ
7918 "},
7919 "\nadditional edit",
7920 ),
7921 ]),
7922 )
7923 .await;
7924 apply_additional_edits.await.unwrap();
7925 cx.assert_editor_state(indoc! {"
7926 one.second_completionˇ
7927 two
7928 three
7929 additional edit
7930 "});
7931
7932 cx.set_state(indoc! {"
7933 one.second_completion
7934 twoˇ
7935 threeˇ
7936 additional edit
7937 "});
7938 cx.simulate_keystroke(" ");
7939 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7940 cx.simulate_keystroke("s");
7941 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7942
7943 cx.assert_editor_state(indoc! {"
7944 one.second_completion
7945 two sˇ
7946 three sˇ
7947 additional edit
7948 "});
7949 handle_completion_request(
7950 &mut cx,
7951 indoc! {"
7952 one.second_completion
7953 two s
7954 three <s|>
7955 additional edit
7956 "},
7957 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7958 counter.clone(),
7959 )
7960 .await;
7961 cx.condition(|editor, _| editor.context_menu_visible())
7962 .await;
7963 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7964
7965 cx.simulate_keystroke("i");
7966
7967 handle_completion_request(
7968 &mut cx,
7969 indoc! {"
7970 one.second_completion
7971 two si
7972 three <si|>
7973 additional edit
7974 "},
7975 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7976 counter.clone(),
7977 )
7978 .await;
7979 cx.condition(|editor, _| editor.context_menu_visible())
7980 .await;
7981 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7982
7983 let apply_additional_edits = cx.update_editor(|editor, cx| {
7984 editor
7985 .confirm_completion(&ConfirmCompletion::default(), cx)
7986 .unwrap()
7987 });
7988 cx.assert_editor_state(indoc! {"
7989 one.second_completion
7990 two sixth_completionˇ
7991 three sixth_completionˇ
7992 additional edit
7993 "});
7994
7995 handle_resolve_completion_request(&mut cx, None).await;
7996 apply_additional_edits.await.unwrap();
7997
7998 cx.update(|cx| {
7999 cx.update_global::<SettingsStore, _>(|settings, cx| {
8000 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8001 settings.show_completions_on_input = Some(false);
8002 });
8003 })
8004 });
8005 cx.set_state("editorˇ");
8006 cx.simulate_keystroke(".");
8007 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8008 cx.simulate_keystroke("c");
8009 cx.simulate_keystroke("l");
8010 cx.simulate_keystroke("o");
8011 cx.assert_editor_state("editor.cloˇ");
8012 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8013 cx.update_editor(|editor, cx| {
8014 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8015 });
8016 handle_completion_request(
8017 &mut cx,
8018 "editor.<clo|>",
8019 vec!["close", "clobber"],
8020 counter.clone(),
8021 )
8022 .await;
8023 cx.condition(|editor, _| editor.context_menu_visible())
8024 .await;
8025 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8026
8027 let apply_additional_edits = cx.update_editor(|editor, cx| {
8028 editor
8029 .confirm_completion(&ConfirmCompletion::default(), cx)
8030 .unwrap()
8031 });
8032 cx.assert_editor_state("editor.closeˇ");
8033 handle_resolve_completion_request(&mut cx, None).await;
8034 apply_additional_edits.await.unwrap();
8035}
8036
8037#[gpui::test]
8038async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8039 init_test(cx, |_| {});
8040 let mut cx = EditorLspTestContext::new_rust(
8041 lsp::ServerCapabilities {
8042 completion_provider: Some(lsp::CompletionOptions {
8043 trigger_characters: Some(vec![".".to_string()]),
8044 ..Default::default()
8045 }),
8046 ..Default::default()
8047 },
8048 cx,
8049 )
8050 .await;
8051 cx.lsp
8052 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8053 Ok(Some(lsp::CompletionResponse::Array(vec![
8054 lsp::CompletionItem {
8055 label: "first".into(),
8056 ..Default::default()
8057 },
8058 lsp::CompletionItem {
8059 label: "last".into(),
8060 ..Default::default()
8061 },
8062 ])))
8063 });
8064 cx.set_state("variableˇ");
8065 cx.simulate_keystroke(".");
8066 cx.executor().run_until_parked();
8067
8068 cx.update_editor(|editor, _| {
8069 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8070 assert_eq!(
8071 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8072 &["first", "last"]
8073 );
8074 } else {
8075 panic!("expected completion menu to be open");
8076 }
8077 });
8078
8079 cx.update_editor(|editor, cx| {
8080 editor.move_page_down(&MovePageDown::default(), cx);
8081 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8082 assert!(
8083 menu.selected_item == 1,
8084 "expected PageDown to select the last item from the context menu"
8085 );
8086 } else {
8087 panic!("expected completion menu to stay open after PageDown");
8088 }
8089 });
8090
8091 cx.update_editor(|editor, cx| {
8092 editor.move_page_up(&MovePageUp::default(), cx);
8093 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8094 assert!(
8095 menu.selected_item == 0,
8096 "expected PageUp to select the first item from the context menu"
8097 );
8098 } else {
8099 panic!("expected completion menu to stay open after PageUp");
8100 }
8101 });
8102}
8103
8104#[gpui::test]
8105async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8106 init_test(cx, |_| {});
8107
8108 let mut cx = EditorLspTestContext::new_rust(
8109 lsp::ServerCapabilities {
8110 completion_provider: Some(lsp::CompletionOptions {
8111 trigger_characters: Some(vec![".".to_string()]),
8112 resolve_provider: Some(true),
8113 ..Default::default()
8114 }),
8115 ..Default::default()
8116 },
8117 cx,
8118 )
8119 .await;
8120
8121 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8122 cx.simulate_keystroke(".");
8123 let completion_item = lsp::CompletionItem {
8124 label: "Some".into(),
8125 kind: Some(lsp::CompletionItemKind::SNIPPET),
8126 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8127 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8128 kind: lsp::MarkupKind::Markdown,
8129 value: "```rust\nSome(2)\n```".to_string(),
8130 })),
8131 deprecated: Some(false),
8132 sort_text: Some("Some".to_string()),
8133 filter_text: Some("Some".to_string()),
8134 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8135 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8136 range: lsp::Range {
8137 start: lsp::Position {
8138 line: 0,
8139 character: 22,
8140 },
8141 end: lsp::Position {
8142 line: 0,
8143 character: 22,
8144 },
8145 },
8146 new_text: "Some(2)".to_string(),
8147 })),
8148 additional_text_edits: Some(vec![lsp::TextEdit {
8149 range: lsp::Range {
8150 start: lsp::Position {
8151 line: 0,
8152 character: 20,
8153 },
8154 end: lsp::Position {
8155 line: 0,
8156 character: 22,
8157 },
8158 },
8159 new_text: "".to_string(),
8160 }]),
8161 ..Default::default()
8162 };
8163
8164 let closure_completion_item = completion_item.clone();
8165 let counter = Arc::new(AtomicUsize::new(0));
8166 let counter_clone = counter.clone();
8167 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8168 let task_completion_item = closure_completion_item.clone();
8169 counter_clone.fetch_add(1, atomic::Ordering::Release);
8170 async move {
8171 Ok(Some(lsp::CompletionResponse::Array(vec![
8172 task_completion_item,
8173 ])))
8174 }
8175 });
8176
8177 cx.condition(|editor, _| editor.context_menu_visible())
8178 .await;
8179 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8180 assert!(request.next().await.is_some());
8181 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8182
8183 cx.simulate_keystroke("S");
8184 cx.simulate_keystroke("o");
8185 cx.simulate_keystroke("m");
8186 cx.condition(|editor, _| editor.context_menu_visible())
8187 .await;
8188 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8189 assert!(request.next().await.is_some());
8190 assert!(request.next().await.is_some());
8191 assert!(request.next().await.is_some());
8192 request.close();
8193 assert!(request.next().await.is_none());
8194 assert_eq!(
8195 counter.load(atomic::Ordering::Acquire),
8196 4,
8197 "With the completions menu open, only one LSP request should happen per input"
8198 );
8199}
8200
8201#[gpui::test]
8202async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8203 init_test(cx, |_| {});
8204 let mut cx = EditorTestContext::new(cx).await;
8205 let language = Arc::new(Language::new(
8206 LanguageConfig {
8207 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8208 ..Default::default()
8209 },
8210 Some(tree_sitter_rust::LANGUAGE.into()),
8211 ));
8212 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8213
8214 // If multiple selections intersect a line, the line is only toggled once.
8215 cx.set_state(indoc! {"
8216 fn a() {
8217 «//b();
8218 ˇ»// «c();
8219 //ˇ» d();
8220 }
8221 "});
8222
8223 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8224
8225 cx.assert_editor_state(indoc! {"
8226 fn a() {
8227 «b();
8228 c();
8229 ˇ» d();
8230 }
8231 "});
8232
8233 // The comment prefix is inserted at the same column for every line in a
8234 // selection.
8235 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8236
8237 cx.assert_editor_state(indoc! {"
8238 fn a() {
8239 // «b();
8240 // c();
8241 ˇ»// d();
8242 }
8243 "});
8244
8245 // If a selection ends at the beginning of a line, that line is not toggled.
8246 cx.set_selections_state(indoc! {"
8247 fn a() {
8248 // b();
8249 «// c();
8250 ˇ» // d();
8251 }
8252 "});
8253
8254 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8255
8256 cx.assert_editor_state(indoc! {"
8257 fn a() {
8258 // b();
8259 «c();
8260 ˇ» // d();
8261 }
8262 "});
8263
8264 // If a selection span a single line and is empty, the line is toggled.
8265 cx.set_state(indoc! {"
8266 fn a() {
8267 a();
8268 b();
8269 ˇ
8270 }
8271 "});
8272
8273 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8274
8275 cx.assert_editor_state(indoc! {"
8276 fn a() {
8277 a();
8278 b();
8279 //•ˇ
8280 }
8281 "});
8282
8283 // If a selection span multiple lines, empty lines are not toggled.
8284 cx.set_state(indoc! {"
8285 fn a() {
8286 «a();
8287
8288 c();ˇ»
8289 }
8290 "});
8291
8292 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8293
8294 cx.assert_editor_state(indoc! {"
8295 fn a() {
8296 // «a();
8297
8298 // c();ˇ»
8299 }
8300 "});
8301
8302 // If a selection includes multiple comment prefixes, all lines are uncommented.
8303 cx.set_state(indoc! {"
8304 fn a() {
8305 «// a();
8306 /// b();
8307 //! c();ˇ»
8308 }
8309 "});
8310
8311 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8312
8313 cx.assert_editor_state(indoc! {"
8314 fn a() {
8315 «a();
8316 b();
8317 c();ˇ»
8318 }
8319 "});
8320}
8321
8322#[gpui::test]
8323async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8324 init_test(cx, |_| {});
8325
8326 let language = Arc::new(Language::new(
8327 LanguageConfig {
8328 line_comments: vec!["// ".into()],
8329 ..Default::default()
8330 },
8331 Some(tree_sitter_rust::LANGUAGE.into()),
8332 ));
8333
8334 let mut cx = EditorTestContext::new(cx).await;
8335
8336 cx.language_registry().add(language.clone());
8337 cx.update_buffer(|buffer, cx| {
8338 buffer.set_language(Some(language), cx);
8339 });
8340
8341 let toggle_comments = &ToggleComments {
8342 advance_downwards: true,
8343 };
8344
8345 // Single cursor on one line -> advance
8346 // Cursor moves horizontally 3 characters as well on non-blank line
8347 cx.set_state(indoc!(
8348 "fn a() {
8349 ˇdog();
8350 cat();
8351 }"
8352 ));
8353 cx.update_editor(|editor, cx| {
8354 editor.toggle_comments(toggle_comments, cx);
8355 });
8356 cx.assert_editor_state(indoc!(
8357 "fn a() {
8358 // dog();
8359 catˇ();
8360 }"
8361 ));
8362
8363 // Single selection on one line -> don't advance
8364 cx.set_state(indoc!(
8365 "fn a() {
8366 «dog()ˇ»;
8367 cat();
8368 }"
8369 ));
8370 cx.update_editor(|editor, cx| {
8371 editor.toggle_comments(toggle_comments, cx);
8372 });
8373 cx.assert_editor_state(indoc!(
8374 "fn a() {
8375 // «dog()ˇ»;
8376 cat();
8377 }"
8378 ));
8379
8380 // Multiple cursors on one line -> advance
8381 cx.set_state(indoc!(
8382 "fn a() {
8383 ˇdˇog();
8384 cat();
8385 }"
8386 ));
8387 cx.update_editor(|editor, cx| {
8388 editor.toggle_comments(toggle_comments, cx);
8389 });
8390 cx.assert_editor_state(indoc!(
8391 "fn a() {
8392 // dog();
8393 catˇ(ˇ);
8394 }"
8395 ));
8396
8397 // Multiple cursors on one line, with selection -> don't advance
8398 cx.set_state(indoc!(
8399 "fn a() {
8400 ˇdˇog«()ˇ»;
8401 cat();
8402 }"
8403 ));
8404 cx.update_editor(|editor, cx| {
8405 editor.toggle_comments(toggle_comments, cx);
8406 });
8407 cx.assert_editor_state(indoc!(
8408 "fn a() {
8409 // ˇdˇog«()ˇ»;
8410 cat();
8411 }"
8412 ));
8413
8414 // Single cursor on one line -> advance
8415 // Cursor moves to column 0 on blank line
8416 cx.set_state(indoc!(
8417 "fn a() {
8418 ˇdog();
8419
8420 cat();
8421 }"
8422 ));
8423 cx.update_editor(|editor, cx| {
8424 editor.toggle_comments(toggle_comments, cx);
8425 });
8426 cx.assert_editor_state(indoc!(
8427 "fn a() {
8428 // dog();
8429 ˇ
8430 cat();
8431 }"
8432 ));
8433
8434 // Single cursor on one line -> advance
8435 // Cursor starts and ends at column 0
8436 cx.set_state(indoc!(
8437 "fn a() {
8438 ˇ dog();
8439 cat();
8440 }"
8441 ));
8442 cx.update_editor(|editor, cx| {
8443 editor.toggle_comments(toggle_comments, cx);
8444 });
8445 cx.assert_editor_state(indoc!(
8446 "fn a() {
8447 // dog();
8448 ˇ cat();
8449 }"
8450 ));
8451}
8452
8453#[gpui::test]
8454async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8455 init_test(cx, |_| {});
8456
8457 let mut cx = EditorTestContext::new(cx).await;
8458
8459 let html_language = Arc::new(
8460 Language::new(
8461 LanguageConfig {
8462 name: "HTML".into(),
8463 block_comment: Some(("<!-- ".into(), " -->".into())),
8464 ..Default::default()
8465 },
8466 Some(tree_sitter_html::language()),
8467 )
8468 .with_injection_query(
8469 r#"
8470 (script_element
8471 (raw_text) @content
8472 (#set! "language" "javascript"))
8473 "#,
8474 )
8475 .unwrap(),
8476 );
8477
8478 let javascript_language = Arc::new(Language::new(
8479 LanguageConfig {
8480 name: "JavaScript".into(),
8481 line_comments: vec!["// ".into()],
8482 ..Default::default()
8483 },
8484 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8485 ));
8486
8487 cx.language_registry().add(html_language.clone());
8488 cx.language_registry().add(javascript_language.clone());
8489 cx.update_buffer(|buffer, cx| {
8490 buffer.set_language(Some(html_language), cx);
8491 });
8492
8493 // Toggle comments for empty selections
8494 cx.set_state(
8495 &r#"
8496 <p>A</p>ˇ
8497 <p>B</p>ˇ
8498 <p>C</p>ˇ
8499 "#
8500 .unindent(),
8501 );
8502 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8503 cx.assert_editor_state(
8504 &r#"
8505 <!-- <p>A</p>ˇ -->
8506 <!-- <p>B</p>ˇ -->
8507 <!-- <p>C</p>ˇ -->
8508 "#
8509 .unindent(),
8510 );
8511 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8512 cx.assert_editor_state(
8513 &r#"
8514 <p>A</p>ˇ
8515 <p>B</p>ˇ
8516 <p>C</p>ˇ
8517 "#
8518 .unindent(),
8519 );
8520
8521 // Toggle comments for mixture of empty and non-empty selections, where
8522 // multiple selections occupy a given line.
8523 cx.set_state(
8524 &r#"
8525 <p>A«</p>
8526 <p>ˇ»B</p>ˇ
8527 <p>C«</p>
8528 <p>ˇ»D</p>ˇ
8529 "#
8530 .unindent(),
8531 );
8532
8533 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8534 cx.assert_editor_state(
8535 &r#"
8536 <!-- <p>A«</p>
8537 <p>ˇ»B</p>ˇ -->
8538 <!-- <p>C«</p>
8539 <p>ˇ»D</p>ˇ -->
8540 "#
8541 .unindent(),
8542 );
8543 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8544 cx.assert_editor_state(
8545 &r#"
8546 <p>A«</p>
8547 <p>ˇ»B</p>ˇ
8548 <p>C«</p>
8549 <p>ˇ»D</p>ˇ
8550 "#
8551 .unindent(),
8552 );
8553
8554 // Toggle comments when different languages are active for different
8555 // selections.
8556 cx.set_state(
8557 &r#"
8558 ˇ<script>
8559 ˇvar x = new Y();
8560 ˇ</script>
8561 "#
8562 .unindent(),
8563 );
8564 cx.executor().run_until_parked();
8565 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8566 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8567 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8568 cx.assert_editor_state(
8569 &r#"
8570 <!-- ˇ<script> -->
8571 // ˇvar x = new Y();
8572 // ˇ</script>
8573 "#
8574 .unindent(),
8575 );
8576}
8577
8578#[gpui::test]
8579fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8580 init_test(cx, |_| {});
8581
8582 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8583 let multibuffer = cx.new_model(|cx| {
8584 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8585 multibuffer.push_excerpts(
8586 buffer.clone(),
8587 [
8588 ExcerptRange {
8589 context: Point::new(0, 0)..Point::new(0, 4),
8590 primary: None,
8591 },
8592 ExcerptRange {
8593 context: Point::new(1, 0)..Point::new(1, 4),
8594 primary: None,
8595 },
8596 ],
8597 cx,
8598 );
8599 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8600 multibuffer
8601 });
8602
8603 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8604 view.update(cx, |view, cx| {
8605 assert_eq!(view.text(cx), "aaaa\nbbbb");
8606 view.change_selections(None, cx, |s| {
8607 s.select_ranges([
8608 Point::new(0, 0)..Point::new(0, 0),
8609 Point::new(1, 0)..Point::new(1, 0),
8610 ])
8611 });
8612
8613 view.handle_input("X", cx);
8614 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8615 assert_eq!(
8616 view.selections.ranges(cx),
8617 [
8618 Point::new(0, 1)..Point::new(0, 1),
8619 Point::new(1, 1)..Point::new(1, 1),
8620 ]
8621 );
8622
8623 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8624 view.change_selections(None, cx, |s| {
8625 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8626 });
8627 view.backspace(&Default::default(), cx);
8628 assert_eq!(view.text(cx), "Xa\nbbb");
8629 assert_eq!(
8630 view.selections.ranges(cx),
8631 [Point::new(1, 0)..Point::new(1, 0)]
8632 );
8633
8634 view.change_selections(None, cx, |s| {
8635 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8636 });
8637 view.backspace(&Default::default(), cx);
8638 assert_eq!(view.text(cx), "X\nbb");
8639 assert_eq!(
8640 view.selections.ranges(cx),
8641 [Point::new(0, 1)..Point::new(0, 1)]
8642 );
8643 });
8644}
8645
8646#[gpui::test]
8647fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8648 init_test(cx, |_| {});
8649
8650 let markers = vec![('[', ']').into(), ('(', ')').into()];
8651 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8652 indoc! {"
8653 [aaaa
8654 (bbbb]
8655 cccc)",
8656 },
8657 markers.clone(),
8658 );
8659 let excerpt_ranges = markers.into_iter().map(|marker| {
8660 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8661 ExcerptRange {
8662 context,
8663 primary: None,
8664 }
8665 });
8666 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8667 let multibuffer = cx.new_model(|cx| {
8668 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8669 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8670 multibuffer
8671 });
8672
8673 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8674 view.update(cx, |view, cx| {
8675 let (expected_text, selection_ranges) = marked_text_ranges(
8676 indoc! {"
8677 aaaa
8678 bˇbbb
8679 bˇbbˇb
8680 cccc"
8681 },
8682 true,
8683 );
8684 assert_eq!(view.text(cx), expected_text);
8685 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8686
8687 view.handle_input("X", cx);
8688
8689 let (expected_text, expected_selections) = marked_text_ranges(
8690 indoc! {"
8691 aaaa
8692 bXˇbbXb
8693 bXˇbbXˇb
8694 cccc"
8695 },
8696 false,
8697 );
8698 assert_eq!(view.text(cx), expected_text);
8699 assert_eq!(view.selections.ranges(cx), expected_selections);
8700
8701 view.newline(&Newline, cx);
8702 let (expected_text, expected_selections) = marked_text_ranges(
8703 indoc! {"
8704 aaaa
8705 bX
8706 ˇbbX
8707 b
8708 bX
8709 ˇbbX
8710 ˇb
8711 cccc"
8712 },
8713 false,
8714 );
8715 assert_eq!(view.text(cx), expected_text);
8716 assert_eq!(view.selections.ranges(cx), expected_selections);
8717 });
8718}
8719
8720#[gpui::test]
8721fn test_refresh_selections(cx: &mut TestAppContext) {
8722 init_test(cx, |_| {});
8723
8724 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8725 let mut excerpt1_id = None;
8726 let multibuffer = cx.new_model(|cx| {
8727 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8728 excerpt1_id = multibuffer
8729 .push_excerpts(
8730 buffer.clone(),
8731 [
8732 ExcerptRange {
8733 context: Point::new(0, 0)..Point::new(1, 4),
8734 primary: None,
8735 },
8736 ExcerptRange {
8737 context: Point::new(1, 0)..Point::new(2, 4),
8738 primary: None,
8739 },
8740 ],
8741 cx,
8742 )
8743 .into_iter()
8744 .next();
8745 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8746 multibuffer
8747 });
8748
8749 let editor = cx.add_window(|cx| {
8750 let mut editor = build_editor(multibuffer.clone(), cx);
8751 let snapshot = editor.snapshot(cx);
8752 editor.change_selections(None, cx, |s| {
8753 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8754 });
8755 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8756 assert_eq!(
8757 editor.selections.ranges(cx),
8758 [
8759 Point::new(1, 3)..Point::new(1, 3),
8760 Point::new(2, 1)..Point::new(2, 1),
8761 ]
8762 );
8763 editor
8764 });
8765
8766 // Refreshing selections is a no-op when excerpts haven't changed.
8767 _ = editor.update(cx, |editor, cx| {
8768 editor.change_selections(None, cx, |s| s.refresh());
8769 assert_eq!(
8770 editor.selections.ranges(cx),
8771 [
8772 Point::new(1, 3)..Point::new(1, 3),
8773 Point::new(2, 1)..Point::new(2, 1),
8774 ]
8775 );
8776 });
8777
8778 multibuffer.update(cx, |multibuffer, cx| {
8779 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8780 });
8781 _ = editor.update(cx, |editor, cx| {
8782 // Removing an excerpt causes the first selection to become degenerate.
8783 assert_eq!(
8784 editor.selections.ranges(cx),
8785 [
8786 Point::new(0, 0)..Point::new(0, 0),
8787 Point::new(0, 1)..Point::new(0, 1)
8788 ]
8789 );
8790
8791 // Refreshing selections will relocate the first selection to the original buffer
8792 // location.
8793 editor.change_selections(None, cx, |s| s.refresh());
8794 assert_eq!(
8795 editor.selections.ranges(cx),
8796 [
8797 Point::new(0, 1)..Point::new(0, 1),
8798 Point::new(0, 3)..Point::new(0, 3)
8799 ]
8800 );
8801 assert!(editor.selections.pending_anchor().is_some());
8802 });
8803}
8804
8805#[gpui::test]
8806fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8807 init_test(cx, |_| {});
8808
8809 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8810 let mut excerpt1_id = None;
8811 let multibuffer = cx.new_model(|cx| {
8812 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8813 excerpt1_id = multibuffer
8814 .push_excerpts(
8815 buffer.clone(),
8816 [
8817 ExcerptRange {
8818 context: Point::new(0, 0)..Point::new(1, 4),
8819 primary: None,
8820 },
8821 ExcerptRange {
8822 context: Point::new(1, 0)..Point::new(2, 4),
8823 primary: None,
8824 },
8825 ],
8826 cx,
8827 )
8828 .into_iter()
8829 .next();
8830 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8831 multibuffer
8832 });
8833
8834 let editor = cx.add_window(|cx| {
8835 let mut editor = build_editor(multibuffer.clone(), cx);
8836 let snapshot = editor.snapshot(cx);
8837 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8838 assert_eq!(
8839 editor.selections.ranges(cx),
8840 [Point::new(1, 3)..Point::new(1, 3)]
8841 );
8842 editor
8843 });
8844
8845 multibuffer.update(cx, |multibuffer, cx| {
8846 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8847 });
8848 _ = editor.update(cx, |editor, cx| {
8849 assert_eq!(
8850 editor.selections.ranges(cx),
8851 [Point::new(0, 0)..Point::new(0, 0)]
8852 );
8853
8854 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8855 editor.change_selections(None, cx, |s| s.refresh());
8856 assert_eq!(
8857 editor.selections.ranges(cx),
8858 [Point::new(0, 3)..Point::new(0, 3)]
8859 );
8860 assert!(editor.selections.pending_anchor().is_some());
8861 });
8862}
8863
8864#[gpui::test]
8865async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8866 init_test(cx, |_| {});
8867
8868 let language = Arc::new(
8869 Language::new(
8870 LanguageConfig {
8871 brackets: BracketPairConfig {
8872 pairs: vec![
8873 BracketPair {
8874 start: "{".to_string(),
8875 end: "}".to_string(),
8876 close: true,
8877 surround: true,
8878 newline: true,
8879 },
8880 BracketPair {
8881 start: "/* ".to_string(),
8882 end: " */".to_string(),
8883 close: true,
8884 surround: true,
8885 newline: true,
8886 },
8887 ],
8888 ..Default::default()
8889 },
8890 ..Default::default()
8891 },
8892 Some(tree_sitter_rust::LANGUAGE.into()),
8893 )
8894 .with_indents_query("")
8895 .unwrap(),
8896 );
8897
8898 let text = concat!(
8899 "{ }\n", //
8900 " x\n", //
8901 " /* */\n", //
8902 "x\n", //
8903 "{{} }\n", //
8904 );
8905
8906 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8907 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8908 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8909 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8910 .await;
8911
8912 view.update(cx, |view, cx| {
8913 view.change_selections(None, cx, |s| {
8914 s.select_display_ranges([
8915 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8916 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8917 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8918 ])
8919 });
8920 view.newline(&Newline, cx);
8921
8922 assert_eq!(
8923 view.buffer().read(cx).read(cx).text(),
8924 concat!(
8925 "{ \n", // Suppress rustfmt
8926 "\n", //
8927 "}\n", //
8928 " x\n", //
8929 " /* \n", //
8930 " \n", //
8931 " */\n", //
8932 "x\n", //
8933 "{{} \n", //
8934 "}\n", //
8935 )
8936 );
8937 });
8938}
8939
8940#[gpui::test]
8941fn test_highlighted_ranges(cx: &mut TestAppContext) {
8942 init_test(cx, |_| {});
8943
8944 let editor = cx.add_window(|cx| {
8945 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8946 build_editor(buffer.clone(), cx)
8947 });
8948
8949 _ = editor.update(cx, |editor, cx| {
8950 struct Type1;
8951 struct Type2;
8952
8953 let buffer = editor.buffer.read(cx).snapshot(cx);
8954
8955 let anchor_range =
8956 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8957
8958 editor.highlight_background::<Type1>(
8959 &[
8960 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8961 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8962 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8963 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8964 ],
8965 |_| Hsla::red(),
8966 cx,
8967 );
8968 editor.highlight_background::<Type2>(
8969 &[
8970 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8971 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8972 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8973 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8974 ],
8975 |_| Hsla::green(),
8976 cx,
8977 );
8978
8979 let snapshot = editor.snapshot(cx);
8980 let mut highlighted_ranges = editor.background_highlights_in_range(
8981 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8982 &snapshot,
8983 cx.theme().colors(),
8984 );
8985 // Enforce a consistent ordering based on color without relying on the ordering of the
8986 // highlight's `TypeId` which is non-executor.
8987 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8988 assert_eq!(
8989 highlighted_ranges,
8990 &[
8991 (
8992 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8993 Hsla::red(),
8994 ),
8995 (
8996 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8997 Hsla::red(),
8998 ),
8999 (
9000 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9001 Hsla::green(),
9002 ),
9003 (
9004 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9005 Hsla::green(),
9006 ),
9007 ]
9008 );
9009 assert_eq!(
9010 editor.background_highlights_in_range(
9011 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9012 &snapshot,
9013 cx.theme().colors(),
9014 ),
9015 &[(
9016 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9017 Hsla::red(),
9018 )]
9019 );
9020 });
9021}
9022
9023#[gpui::test]
9024async fn test_following(cx: &mut gpui::TestAppContext) {
9025 init_test(cx, |_| {});
9026
9027 let fs = FakeFs::new(cx.executor());
9028 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9029
9030 let buffer = project.update(cx, |project, cx| {
9031 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9032 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9033 });
9034 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9035 let follower = cx.update(|cx| {
9036 cx.open_window(
9037 WindowOptions {
9038 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9039 gpui::Point::new(px(0.), px(0.)),
9040 gpui::Point::new(px(10.), px(80.)),
9041 ))),
9042 ..Default::default()
9043 },
9044 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9045 )
9046 .unwrap()
9047 });
9048
9049 let is_still_following = Rc::new(RefCell::new(true));
9050 let follower_edit_event_count = Rc::new(RefCell::new(0));
9051 let pending_update = Rc::new(RefCell::new(None));
9052 _ = follower.update(cx, {
9053 let update = pending_update.clone();
9054 let is_still_following = is_still_following.clone();
9055 let follower_edit_event_count = follower_edit_event_count.clone();
9056 |_, cx| {
9057 cx.subscribe(
9058 &leader.root_view(cx).unwrap(),
9059 move |_, leader, event, cx| {
9060 leader
9061 .read(cx)
9062 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9063 },
9064 )
9065 .detach();
9066
9067 cx.subscribe(
9068 &follower.root_view(cx).unwrap(),
9069 move |_, _, event: &EditorEvent, _cx| {
9070 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9071 *is_still_following.borrow_mut() = false;
9072 }
9073
9074 if let EditorEvent::BufferEdited = event {
9075 *follower_edit_event_count.borrow_mut() += 1;
9076 }
9077 },
9078 )
9079 .detach();
9080 }
9081 });
9082
9083 // Update the selections only
9084 _ = leader.update(cx, |leader, cx| {
9085 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9086 });
9087 follower
9088 .update(cx, |follower, cx| {
9089 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9090 })
9091 .unwrap()
9092 .await
9093 .unwrap();
9094 _ = follower.update(cx, |follower, cx| {
9095 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9096 });
9097 assert!(*is_still_following.borrow());
9098 assert_eq!(*follower_edit_event_count.borrow(), 0);
9099
9100 // Update the scroll position only
9101 _ = leader.update(cx, |leader, cx| {
9102 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9103 });
9104 follower
9105 .update(cx, |follower, cx| {
9106 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9107 })
9108 .unwrap()
9109 .await
9110 .unwrap();
9111 assert_eq!(
9112 follower
9113 .update(cx, |follower, cx| follower.scroll_position(cx))
9114 .unwrap(),
9115 gpui::Point::new(1.5, 3.5)
9116 );
9117 assert!(*is_still_following.borrow());
9118 assert_eq!(*follower_edit_event_count.borrow(), 0);
9119
9120 // Update the selections and scroll position. The follower's scroll position is updated
9121 // via autoscroll, not via the leader's exact scroll position.
9122 _ = leader.update(cx, |leader, cx| {
9123 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9124 leader.request_autoscroll(Autoscroll::newest(), cx);
9125 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9126 });
9127 follower
9128 .update(cx, |follower, cx| {
9129 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9130 })
9131 .unwrap()
9132 .await
9133 .unwrap();
9134 _ = follower.update(cx, |follower, cx| {
9135 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9136 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9137 });
9138 assert!(*is_still_following.borrow());
9139
9140 // Creating a pending selection that precedes another selection
9141 _ = leader.update(cx, |leader, cx| {
9142 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9143 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9144 });
9145 follower
9146 .update(cx, |follower, cx| {
9147 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9148 })
9149 .unwrap()
9150 .await
9151 .unwrap();
9152 _ = follower.update(cx, |follower, cx| {
9153 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9154 });
9155 assert!(*is_still_following.borrow());
9156
9157 // Extend the pending selection so that it surrounds another selection
9158 _ = leader.update(cx, |leader, cx| {
9159 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9160 });
9161 follower
9162 .update(cx, |follower, cx| {
9163 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9164 })
9165 .unwrap()
9166 .await
9167 .unwrap();
9168 _ = follower.update(cx, |follower, cx| {
9169 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9170 });
9171
9172 // Scrolling locally breaks the follow
9173 _ = follower.update(cx, |follower, cx| {
9174 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9175 follower.set_scroll_anchor(
9176 ScrollAnchor {
9177 anchor: top_anchor,
9178 offset: gpui::Point::new(0.0, 0.5),
9179 },
9180 cx,
9181 );
9182 });
9183 assert!(!(*is_still_following.borrow()));
9184}
9185
9186#[gpui::test]
9187async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9188 init_test(cx, |_| {});
9189
9190 let fs = FakeFs::new(cx.executor());
9191 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9192 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9193 let pane = workspace
9194 .update(cx, |workspace, _| workspace.active_pane().clone())
9195 .unwrap();
9196
9197 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9198
9199 let leader = pane.update(cx, |_, cx| {
9200 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
9201 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9202 });
9203
9204 // Start following the editor when it has no excerpts.
9205 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9206 let follower_1 = cx
9207 .update_window(*workspace.deref(), |_, cx| {
9208 Editor::from_state_proto(
9209 workspace.root_view(cx).unwrap(),
9210 ViewId {
9211 creator: Default::default(),
9212 id: 0,
9213 },
9214 &mut state_message,
9215 cx,
9216 )
9217 })
9218 .unwrap()
9219 .unwrap()
9220 .await
9221 .unwrap();
9222
9223 let update_message = Rc::new(RefCell::new(None));
9224 follower_1.update(cx, {
9225 let update = update_message.clone();
9226 |_, cx| {
9227 cx.subscribe(&leader, move |_, leader, event, cx| {
9228 leader
9229 .read(cx)
9230 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9231 })
9232 .detach();
9233 }
9234 });
9235
9236 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9237 (
9238 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9239 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9240 )
9241 });
9242
9243 // Insert some excerpts.
9244 leader.update(cx, |leader, cx| {
9245 leader.buffer.update(cx, |multibuffer, cx| {
9246 let excerpt_ids = multibuffer.push_excerpts(
9247 buffer_1.clone(),
9248 [
9249 ExcerptRange {
9250 context: 1..6,
9251 primary: None,
9252 },
9253 ExcerptRange {
9254 context: 12..15,
9255 primary: None,
9256 },
9257 ExcerptRange {
9258 context: 0..3,
9259 primary: None,
9260 },
9261 ],
9262 cx,
9263 );
9264 multibuffer.insert_excerpts_after(
9265 excerpt_ids[0],
9266 buffer_2.clone(),
9267 [
9268 ExcerptRange {
9269 context: 8..12,
9270 primary: None,
9271 },
9272 ExcerptRange {
9273 context: 0..6,
9274 primary: None,
9275 },
9276 ],
9277 cx,
9278 );
9279 });
9280 });
9281
9282 // Apply the update of adding the excerpts.
9283 follower_1
9284 .update(cx, |follower, cx| {
9285 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9286 })
9287 .await
9288 .unwrap();
9289 assert_eq!(
9290 follower_1.update(cx, |editor, cx| editor.text(cx)),
9291 leader.update(cx, |editor, cx| editor.text(cx))
9292 );
9293 update_message.borrow_mut().take();
9294
9295 // Start following separately after it already has excerpts.
9296 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9297 let follower_2 = cx
9298 .update_window(*workspace.deref(), |_, cx| {
9299 Editor::from_state_proto(
9300 workspace.root_view(cx).unwrap().clone(),
9301 ViewId {
9302 creator: Default::default(),
9303 id: 0,
9304 },
9305 &mut state_message,
9306 cx,
9307 )
9308 })
9309 .unwrap()
9310 .unwrap()
9311 .await
9312 .unwrap();
9313 assert_eq!(
9314 follower_2.update(cx, |editor, cx| editor.text(cx)),
9315 leader.update(cx, |editor, cx| editor.text(cx))
9316 );
9317
9318 // Remove some excerpts.
9319 leader.update(cx, |leader, cx| {
9320 leader.buffer.update(cx, |multibuffer, cx| {
9321 let excerpt_ids = multibuffer.excerpt_ids();
9322 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9323 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9324 });
9325 });
9326
9327 // Apply the update of removing the excerpts.
9328 follower_1
9329 .update(cx, |follower, cx| {
9330 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9331 })
9332 .await
9333 .unwrap();
9334 follower_2
9335 .update(cx, |follower, cx| {
9336 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9337 })
9338 .await
9339 .unwrap();
9340 update_message.borrow_mut().take();
9341 assert_eq!(
9342 follower_1.update(cx, |editor, cx| editor.text(cx)),
9343 leader.update(cx, |editor, cx| editor.text(cx))
9344 );
9345}
9346
9347#[gpui::test]
9348async fn go_to_prev_overlapping_diagnostic(
9349 executor: BackgroundExecutor,
9350 cx: &mut gpui::TestAppContext,
9351) {
9352 init_test(cx, |_| {});
9353
9354 let mut cx = EditorTestContext::new(cx).await;
9355 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9356
9357 cx.set_state(indoc! {"
9358 ˇfn func(abc def: i32) -> u32 {
9359 }
9360 "});
9361
9362 cx.update(|cx| {
9363 project.update(cx, |project, cx| {
9364 project
9365 .update_diagnostics(
9366 LanguageServerId(0),
9367 lsp::PublishDiagnosticsParams {
9368 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9369 version: None,
9370 diagnostics: vec![
9371 lsp::Diagnostic {
9372 range: lsp::Range::new(
9373 lsp::Position::new(0, 11),
9374 lsp::Position::new(0, 12),
9375 ),
9376 severity: Some(lsp::DiagnosticSeverity::ERROR),
9377 ..Default::default()
9378 },
9379 lsp::Diagnostic {
9380 range: lsp::Range::new(
9381 lsp::Position::new(0, 12),
9382 lsp::Position::new(0, 15),
9383 ),
9384 severity: Some(lsp::DiagnosticSeverity::ERROR),
9385 ..Default::default()
9386 },
9387 lsp::Diagnostic {
9388 range: lsp::Range::new(
9389 lsp::Position::new(0, 25),
9390 lsp::Position::new(0, 28),
9391 ),
9392 severity: Some(lsp::DiagnosticSeverity::ERROR),
9393 ..Default::default()
9394 },
9395 ],
9396 },
9397 &[],
9398 cx,
9399 )
9400 .unwrap()
9401 });
9402 });
9403
9404 executor.run_until_parked();
9405
9406 cx.update_editor(|editor, cx| {
9407 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9408 });
9409
9410 cx.assert_editor_state(indoc! {"
9411 fn func(abc def: i32) -> ˇu32 {
9412 }
9413 "});
9414
9415 cx.update_editor(|editor, cx| {
9416 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9417 });
9418
9419 cx.assert_editor_state(indoc! {"
9420 fn func(abc ˇdef: i32) -> u32 {
9421 }
9422 "});
9423
9424 cx.update_editor(|editor, cx| {
9425 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9426 });
9427
9428 cx.assert_editor_state(indoc! {"
9429 fn func(abcˇ def: i32) -> u32 {
9430 }
9431 "});
9432
9433 cx.update_editor(|editor, cx| {
9434 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9435 });
9436
9437 cx.assert_editor_state(indoc! {"
9438 fn func(abc def: i32) -> ˇu32 {
9439 }
9440 "});
9441}
9442
9443#[gpui::test]
9444async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9445 init_test(cx, |_| {});
9446
9447 let mut cx = EditorTestContext::new(cx).await;
9448
9449 cx.set_state(indoc! {"
9450 fn func(abˇc def: i32) -> u32 {
9451 }
9452 "});
9453 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9454
9455 cx.update(|cx| {
9456 project.update(cx, |project, cx| {
9457 project.update_diagnostics(
9458 LanguageServerId(0),
9459 lsp::PublishDiagnosticsParams {
9460 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9461 version: None,
9462 diagnostics: vec![lsp::Diagnostic {
9463 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9464 severity: Some(lsp::DiagnosticSeverity::ERROR),
9465 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9466 ..Default::default()
9467 }],
9468 },
9469 &[],
9470 cx,
9471 )
9472 })
9473 }).unwrap();
9474 cx.run_until_parked();
9475 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9476 cx.run_until_parked();
9477 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9478}
9479
9480#[gpui::test]
9481async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9482 init_test(cx, |_| {});
9483
9484 let mut cx = EditorTestContext::new(cx).await;
9485
9486 let diff_base = r#"
9487 use some::mod;
9488
9489 const A: u32 = 42;
9490
9491 fn main() {
9492 println!("hello");
9493
9494 println!("world");
9495 }
9496 "#
9497 .unindent();
9498
9499 // Edits are modified, removed, modified, added
9500 cx.set_state(
9501 &r#"
9502 use some::modified;
9503
9504 ˇ
9505 fn main() {
9506 println!("hello there");
9507
9508 println!("around the");
9509 println!("world");
9510 }
9511 "#
9512 .unindent(),
9513 );
9514
9515 cx.set_diff_base(Some(&diff_base));
9516 executor.run_until_parked();
9517
9518 cx.update_editor(|editor, cx| {
9519 //Wrap around the bottom of the buffer
9520 for _ in 0..3 {
9521 editor.go_to_hunk(&GoToHunk, cx);
9522 }
9523 });
9524
9525 cx.assert_editor_state(
9526 &r#"
9527 ˇuse some::modified;
9528
9529
9530 fn main() {
9531 println!("hello there");
9532
9533 println!("around the");
9534 println!("world");
9535 }
9536 "#
9537 .unindent(),
9538 );
9539
9540 cx.update_editor(|editor, cx| {
9541 //Wrap around the top of the buffer
9542 for _ in 0..2 {
9543 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9544 }
9545 });
9546
9547 cx.assert_editor_state(
9548 &r#"
9549 use some::modified;
9550
9551
9552 fn main() {
9553 ˇ println!("hello there");
9554
9555 println!("around the");
9556 println!("world");
9557 }
9558 "#
9559 .unindent(),
9560 );
9561
9562 cx.update_editor(|editor, cx| {
9563 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9564 });
9565
9566 cx.assert_editor_state(
9567 &r#"
9568 use some::modified;
9569
9570 ˇ
9571 fn main() {
9572 println!("hello there");
9573
9574 println!("around the");
9575 println!("world");
9576 }
9577 "#
9578 .unindent(),
9579 );
9580
9581 cx.update_editor(|editor, cx| {
9582 for _ in 0..3 {
9583 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9584 }
9585 });
9586
9587 cx.assert_editor_state(
9588 &r#"
9589 use some::modified;
9590
9591
9592 fn main() {
9593 ˇ println!("hello there");
9594
9595 println!("around the");
9596 println!("world");
9597 }
9598 "#
9599 .unindent(),
9600 );
9601
9602 cx.update_editor(|editor, cx| {
9603 editor.fold(&Fold, cx);
9604
9605 //Make sure that the fold only gets one hunk
9606 for _ in 0..4 {
9607 editor.go_to_hunk(&GoToHunk, cx);
9608 }
9609 });
9610
9611 cx.assert_editor_state(
9612 &r#"
9613 ˇuse some::modified;
9614
9615
9616 fn main() {
9617 println!("hello there");
9618
9619 println!("around the");
9620 println!("world");
9621 }
9622 "#
9623 .unindent(),
9624 );
9625}
9626
9627#[test]
9628fn test_split_words() {
9629 fn split(text: &str) -> Vec<&str> {
9630 split_words(text).collect()
9631 }
9632
9633 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9634 assert_eq!(split("hello_world"), &["hello_", "world"]);
9635 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9636 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9637 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9638 assert_eq!(split("helloworld"), &["helloworld"]);
9639
9640 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9641}
9642
9643#[gpui::test]
9644async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9645 init_test(cx, |_| {});
9646
9647 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9648 let mut assert = |before, after| {
9649 let _state_context = cx.set_state(before);
9650 cx.update_editor(|editor, cx| {
9651 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9652 });
9653 cx.assert_editor_state(after);
9654 };
9655
9656 // Outside bracket jumps to outside of matching bracket
9657 assert("console.logˇ(var);", "console.log(var)ˇ;");
9658 assert("console.log(var)ˇ;", "console.logˇ(var);");
9659
9660 // Inside bracket jumps to inside of matching bracket
9661 assert("console.log(ˇvar);", "console.log(varˇ);");
9662 assert("console.log(varˇ);", "console.log(ˇvar);");
9663
9664 // When outside a bracket and inside, favor jumping to the inside bracket
9665 assert(
9666 "console.log('foo', [1, 2, 3]ˇ);",
9667 "console.log(ˇ'foo', [1, 2, 3]);",
9668 );
9669 assert(
9670 "console.log(ˇ'foo', [1, 2, 3]);",
9671 "console.log('foo', [1, 2, 3]ˇ);",
9672 );
9673
9674 // Bias forward if two options are equally likely
9675 assert(
9676 "let result = curried_fun()ˇ();",
9677 "let result = curried_fun()()ˇ;",
9678 );
9679
9680 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9681 assert(
9682 indoc! {"
9683 function test() {
9684 console.log('test')ˇ
9685 }"},
9686 indoc! {"
9687 function test() {
9688 console.logˇ('test')
9689 }"},
9690 );
9691}
9692
9693#[gpui::test]
9694async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9695 init_test(cx, |_| {});
9696
9697 let fs = FakeFs::new(cx.executor());
9698 fs.insert_tree(
9699 "/a",
9700 json!({
9701 "main.rs": "fn main() { let a = 5; }",
9702 "other.rs": "// Test file",
9703 }),
9704 )
9705 .await;
9706 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9707
9708 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9709 language_registry.add(Arc::new(Language::new(
9710 LanguageConfig {
9711 name: "Rust".into(),
9712 matcher: LanguageMatcher {
9713 path_suffixes: vec!["rs".to_string()],
9714 ..Default::default()
9715 },
9716 brackets: BracketPairConfig {
9717 pairs: vec![BracketPair {
9718 start: "{".to_string(),
9719 end: "}".to_string(),
9720 close: true,
9721 surround: true,
9722 newline: true,
9723 }],
9724 disabled_scopes_by_bracket_ix: Vec::new(),
9725 },
9726 ..Default::default()
9727 },
9728 Some(tree_sitter_rust::LANGUAGE.into()),
9729 )));
9730 let mut fake_servers = language_registry.register_fake_lsp(
9731 "Rust",
9732 FakeLspAdapter {
9733 capabilities: lsp::ServerCapabilities {
9734 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9735 first_trigger_character: "{".to_string(),
9736 more_trigger_character: None,
9737 }),
9738 ..Default::default()
9739 },
9740 ..Default::default()
9741 },
9742 );
9743
9744 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9745
9746 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9747
9748 let worktree_id = workspace
9749 .update(cx, |workspace, cx| {
9750 workspace.project().update(cx, |project, cx| {
9751 project.worktrees(cx).next().unwrap().read(cx).id()
9752 })
9753 })
9754 .unwrap();
9755
9756 let buffer = project
9757 .update(cx, |project, cx| {
9758 project.open_local_buffer("/a/main.rs", cx)
9759 })
9760 .await
9761 .unwrap();
9762 cx.executor().run_until_parked();
9763 cx.executor().start_waiting();
9764 let fake_server = fake_servers.next().await.unwrap();
9765 let editor_handle = workspace
9766 .update(cx, |workspace, cx| {
9767 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9768 })
9769 .unwrap()
9770 .await
9771 .unwrap()
9772 .downcast::<Editor>()
9773 .unwrap();
9774
9775 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9776 assert_eq!(
9777 params.text_document_position.text_document.uri,
9778 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9779 );
9780 assert_eq!(
9781 params.text_document_position.position,
9782 lsp::Position::new(0, 21),
9783 );
9784
9785 Ok(Some(vec![lsp::TextEdit {
9786 new_text: "]".to_string(),
9787 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9788 }]))
9789 });
9790
9791 editor_handle.update(cx, |editor, cx| {
9792 editor.focus(cx);
9793 editor.change_selections(None, cx, |s| {
9794 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9795 });
9796 editor.handle_input("{", cx);
9797 });
9798
9799 cx.executor().run_until_parked();
9800
9801 buffer.update(cx, |buffer, _| {
9802 assert_eq!(
9803 buffer.text(),
9804 "fn main() { let a = {5}; }",
9805 "No extra braces from on type formatting should appear in the buffer"
9806 )
9807 });
9808}
9809
9810#[gpui::test]
9811async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9812 init_test(cx, |_| {});
9813
9814 let fs = FakeFs::new(cx.executor());
9815 fs.insert_tree(
9816 "/a",
9817 json!({
9818 "main.rs": "fn main() { let a = 5; }",
9819 "other.rs": "// Test file",
9820 }),
9821 )
9822 .await;
9823
9824 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9825
9826 let server_restarts = Arc::new(AtomicUsize::new(0));
9827 let closure_restarts = Arc::clone(&server_restarts);
9828 let language_server_name = "test language server";
9829 let language_name: LanguageName = "Rust".into();
9830
9831 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9832 language_registry.add(Arc::new(Language::new(
9833 LanguageConfig {
9834 name: language_name.clone(),
9835 matcher: LanguageMatcher {
9836 path_suffixes: vec!["rs".to_string()],
9837 ..Default::default()
9838 },
9839 ..Default::default()
9840 },
9841 Some(tree_sitter_rust::LANGUAGE.into()),
9842 )));
9843 let mut fake_servers = language_registry.register_fake_lsp(
9844 "Rust",
9845 FakeLspAdapter {
9846 name: language_server_name,
9847 initialization_options: Some(json!({
9848 "testOptionValue": true
9849 })),
9850 initializer: Some(Box::new(move |fake_server| {
9851 let task_restarts = Arc::clone(&closure_restarts);
9852 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9853 task_restarts.fetch_add(1, atomic::Ordering::Release);
9854 futures::future::ready(Ok(()))
9855 });
9856 })),
9857 ..Default::default()
9858 },
9859 );
9860
9861 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9862 let _buffer = project
9863 .update(cx, |project, cx| {
9864 project.open_local_buffer("/a/main.rs", cx)
9865 })
9866 .await
9867 .unwrap();
9868 let _fake_server = fake_servers.next().await.unwrap();
9869 update_test_language_settings(cx, |language_settings| {
9870 language_settings.languages.insert(
9871 language_name.clone(),
9872 LanguageSettingsContent {
9873 tab_size: NonZeroU32::new(8),
9874 ..Default::default()
9875 },
9876 );
9877 });
9878 cx.executor().run_until_parked();
9879 assert_eq!(
9880 server_restarts.load(atomic::Ordering::Acquire),
9881 0,
9882 "Should not restart LSP server on an unrelated change"
9883 );
9884
9885 update_test_project_settings(cx, |project_settings| {
9886 project_settings.lsp.insert(
9887 "Some other server name".into(),
9888 LspSettings {
9889 binary: None,
9890 settings: None,
9891 initialization_options: Some(json!({
9892 "some other init value": false
9893 })),
9894 },
9895 );
9896 });
9897 cx.executor().run_until_parked();
9898 assert_eq!(
9899 server_restarts.load(atomic::Ordering::Acquire),
9900 0,
9901 "Should not restart LSP server on an unrelated LSP settings change"
9902 );
9903
9904 update_test_project_settings(cx, |project_settings| {
9905 project_settings.lsp.insert(
9906 language_server_name.into(),
9907 LspSettings {
9908 binary: None,
9909 settings: None,
9910 initialization_options: Some(json!({
9911 "anotherInitValue": false
9912 })),
9913 },
9914 );
9915 });
9916 cx.executor().run_until_parked();
9917 assert_eq!(
9918 server_restarts.load(atomic::Ordering::Acquire),
9919 1,
9920 "Should restart LSP server on a related LSP settings change"
9921 );
9922
9923 update_test_project_settings(cx, |project_settings| {
9924 project_settings.lsp.insert(
9925 language_server_name.into(),
9926 LspSettings {
9927 binary: None,
9928 settings: None,
9929 initialization_options: Some(json!({
9930 "anotherInitValue": false
9931 })),
9932 },
9933 );
9934 });
9935 cx.executor().run_until_parked();
9936 assert_eq!(
9937 server_restarts.load(atomic::Ordering::Acquire),
9938 1,
9939 "Should not restart LSP server on a related LSP settings change that is the same"
9940 );
9941
9942 update_test_project_settings(cx, |project_settings| {
9943 project_settings.lsp.insert(
9944 language_server_name.into(),
9945 LspSettings {
9946 binary: None,
9947 settings: None,
9948 initialization_options: None,
9949 },
9950 );
9951 });
9952 cx.executor().run_until_parked();
9953 assert_eq!(
9954 server_restarts.load(atomic::Ordering::Acquire),
9955 2,
9956 "Should restart LSP server on another related LSP settings change"
9957 );
9958}
9959
9960#[gpui::test]
9961async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9962 init_test(cx, |_| {});
9963
9964 let mut cx = EditorLspTestContext::new_rust(
9965 lsp::ServerCapabilities {
9966 completion_provider: Some(lsp::CompletionOptions {
9967 trigger_characters: Some(vec![".".to_string()]),
9968 resolve_provider: Some(true),
9969 ..Default::default()
9970 }),
9971 ..Default::default()
9972 },
9973 cx,
9974 )
9975 .await;
9976
9977 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9978 cx.simulate_keystroke(".");
9979 let completion_item = lsp::CompletionItem {
9980 label: "some".into(),
9981 kind: Some(lsp::CompletionItemKind::SNIPPET),
9982 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9983 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9984 kind: lsp::MarkupKind::Markdown,
9985 value: "```rust\nSome(2)\n```".to_string(),
9986 })),
9987 deprecated: Some(false),
9988 sort_text: Some("fffffff2".to_string()),
9989 filter_text: Some("some".to_string()),
9990 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9991 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9992 range: lsp::Range {
9993 start: lsp::Position {
9994 line: 0,
9995 character: 22,
9996 },
9997 end: lsp::Position {
9998 line: 0,
9999 character: 22,
10000 },
10001 },
10002 new_text: "Some(2)".to_string(),
10003 })),
10004 additional_text_edits: Some(vec![lsp::TextEdit {
10005 range: lsp::Range {
10006 start: lsp::Position {
10007 line: 0,
10008 character: 20,
10009 },
10010 end: lsp::Position {
10011 line: 0,
10012 character: 22,
10013 },
10014 },
10015 new_text: "".to_string(),
10016 }]),
10017 ..Default::default()
10018 };
10019
10020 let closure_completion_item = completion_item.clone();
10021 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10022 let task_completion_item = closure_completion_item.clone();
10023 async move {
10024 Ok(Some(lsp::CompletionResponse::Array(vec![
10025 task_completion_item,
10026 ])))
10027 }
10028 });
10029
10030 request.next().await;
10031
10032 cx.condition(|editor, _| editor.context_menu_visible())
10033 .await;
10034 let apply_additional_edits = cx.update_editor(|editor, cx| {
10035 editor
10036 .confirm_completion(&ConfirmCompletion::default(), cx)
10037 .unwrap()
10038 });
10039 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10040
10041 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10042 let task_completion_item = completion_item.clone();
10043 async move { Ok(task_completion_item) }
10044 })
10045 .next()
10046 .await
10047 .unwrap();
10048 apply_additional_edits.await.unwrap();
10049 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10050}
10051
10052#[gpui::test]
10053async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10054 init_test(cx, |_| {});
10055
10056 let mut cx = EditorLspTestContext::new(
10057 Language::new(
10058 LanguageConfig {
10059 matcher: LanguageMatcher {
10060 path_suffixes: vec!["jsx".into()],
10061 ..Default::default()
10062 },
10063 overrides: [(
10064 "element".into(),
10065 LanguageConfigOverride {
10066 word_characters: Override::Set(['-'].into_iter().collect()),
10067 ..Default::default()
10068 },
10069 )]
10070 .into_iter()
10071 .collect(),
10072 ..Default::default()
10073 },
10074 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10075 )
10076 .with_override_query("(jsx_self_closing_element) @element")
10077 .unwrap(),
10078 lsp::ServerCapabilities {
10079 completion_provider: Some(lsp::CompletionOptions {
10080 trigger_characters: Some(vec![":".to_string()]),
10081 ..Default::default()
10082 }),
10083 ..Default::default()
10084 },
10085 cx,
10086 )
10087 .await;
10088
10089 cx.lsp
10090 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10091 Ok(Some(lsp::CompletionResponse::Array(vec![
10092 lsp::CompletionItem {
10093 label: "bg-blue".into(),
10094 ..Default::default()
10095 },
10096 lsp::CompletionItem {
10097 label: "bg-red".into(),
10098 ..Default::default()
10099 },
10100 lsp::CompletionItem {
10101 label: "bg-yellow".into(),
10102 ..Default::default()
10103 },
10104 ])))
10105 });
10106
10107 cx.set_state(r#"<p class="bgˇ" />"#);
10108
10109 // Trigger completion when typing a dash, because the dash is an extra
10110 // word character in the 'element' scope, which contains the cursor.
10111 cx.simulate_keystroke("-");
10112 cx.executor().run_until_parked();
10113 cx.update_editor(|editor, _| {
10114 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10115 assert_eq!(
10116 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10117 &["bg-red", "bg-blue", "bg-yellow"]
10118 );
10119 } else {
10120 panic!("expected completion menu to be open");
10121 }
10122 });
10123
10124 cx.simulate_keystroke("l");
10125 cx.executor().run_until_parked();
10126 cx.update_editor(|editor, _| {
10127 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10128 assert_eq!(
10129 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10130 &["bg-blue", "bg-yellow"]
10131 );
10132 } else {
10133 panic!("expected completion menu to be open");
10134 }
10135 });
10136
10137 // When filtering completions, consider the character after the '-' to
10138 // be the start of a subword.
10139 cx.set_state(r#"<p class="yelˇ" />"#);
10140 cx.simulate_keystroke("l");
10141 cx.executor().run_until_parked();
10142 cx.update_editor(|editor, _| {
10143 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10144 assert_eq!(
10145 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10146 &["bg-yellow"]
10147 );
10148 } else {
10149 panic!("expected completion menu to be open");
10150 }
10151 });
10152}
10153
10154#[gpui::test]
10155async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10156 init_test(cx, |settings| {
10157 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10158 FormatterList(vec![Formatter::Prettier].into()),
10159 ))
10160 });
10161
10162 let fs = FakeFs::new(cx.executor());
10163 fs.insert_file("/file.ts", Default::default()).await;
10164
10165 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10166 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10167
10168 language_registry.add(Arc::new(Language::new(
10169 LanguageConfig {
10170 name: "TypeScript".into(),
10171 matcher: LanguageMatcher {
10172 path_suffixes: vec!["ts".to_string()],
10173 ..Default::default()
10174 },
10175 ..Default::default()
10176 },
10177 Some(tree_sitter_rust::LANGUAGE.into()),
10178 )));
10179 update_test_language_settings(cx, |settings| {
10180 settings.defaults.prettier = Some(PrettierSettings {
10181 allowed: true,
10182 ..PrettierSettings::default()
10183 });
10184 });
10185
10186 let test_plugin = "test_plugin";
10187 let _ = language_registry.register_fake_lsp(
10188 "TypeScript",
10189 FakeLspAdapter {
10190 prettier_plugins: vec![test_plugin],
10191 ..Default::default()
10192 },
10193 );
10194
10195 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10196 let buffer = project
10197 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10198 .await
10199 .unwrap();
10200
10201 let buffer_text = "one\ntwo\nthree\n";
10202 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10203 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10204 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10205
10206 editor
10207 .update(cx, |editor, cx| {
10208 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10209 })
10210 .unwrap()
10211 .await;
10212 assert_eq!(
10213 editor.update(cx, |editor, cx| editor.text(cx)),
10214 buffer_text.to_string() + prettier_format_suffix,
10215 "Test prettier formatting was not applied to the original buffer text",
10216 );
10217
10218 update_test_language_settings(cx, |settings| {
10219 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10220 });
10221 let format = editor.update(cx, |editor, cx| {
10222 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10223 });
10224 format.await.unwrap();
10225 assert_eq!(
10226 editor.update(cx, |editor, cx| editor.text(cx)),
10227 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10228 "Autoformatting (via test prettier) was not applied to the original buffer text",
10229 );
10230}
10231
10232#[gpui::test]
10233async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10234 init_test(cx, |_| {});
10235 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10236 let base_text = indoc! {r#"struct Row;
10237struct Row1;
10238struct Row2;
10239
10240struct Row4;
10241struct Row5;
10242struct Row6;
10243
10244struct Row8;
10245struct Row9;
10246struct Row10;"#};
10247
10248 // When addition hunks are not adjacent to carets, no hunk revert is performed
10249 assert_hunk_revert(
10250 indoc! {r#"struct Row;
10251 struct Row1;
10252 struct Row1.1;
10253 struct Row1.2;
10254 struct Row2;ˇ
10255
10256 struct Row4;
10257 struct Row5;
10258 struct Row6;
10259
10260 struct Row8;
10261 ˇstruct Row9;
10262 struct Row9.1;
10263 struct Row9.2;
10264 struct Row9.3;
10265 struct Row10;"#},
10266 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10267 indoc! {r#"struct Row;
10268 struct Row1;
10269 struct Row1.1;
10270 struct Row1.2;
10271 struct Row2;ˇ
10272
10273 struct Row4;
10274 struct Row5;
10275 struct Row6;
10276
10277 struct Row8;
10278 ˇstruct Row9;
10279 struct Row9.1;
10280 struct Row9.2;
10281 struct Row9.3;
10282 struct Row10;"#},
10283 base_text,
10284 &mut cx,
10285 );
10286 // Same for selections
10287 assert_hunk_revert(
10288 indoc! {r#"struct Row;
10289 struct Row1;
10290 struct Row2;
10291 struct Row2.1;
10292 struct Row2.2;
10293 «ˇ
10294 struct Row4;
10295 struct» Row5;
10296 «struct Row6;
10297 ˇ»
10298 struct Row9.1;
10299 struct Row9.2;
10300 struct Row9.3;
10301 struct Row8;
10302 struct Row9;
10303 struct Row10;"#},
10304 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10305 indoc! {r#"struct Row;
10306 struct Row1;
10307 struct Row2;
10308 struct Row2.1;
10309 struct Row2.2;
10310 «ˇ
10311 struct Row4;
10312 struct» Row5;
10313 «struct Row6;
10314 ˇ»
10315 struct Row9.1;
10316 struct Row9.2;
10317 struct Row9.3;
10318 struct Row8;
10319 struct Row9;
10320 struct Row10;"#},
10321 base_text,
10322 &mut cx,
10323 );
10324
10325 // When carets and selections intersect the addition hunks, those are reverted.
10326 // Adjacent carets got merged.
10327 assert_hunk_revert(
10328 indoc! {r#"struct Row;
10329 ˇ// something on the top
10330 struct Row1;
10331 struct Row2;
10332 struct Roˇw3.1;
10333 struct Row2.2;
10334 struct Row2.3;ˇ
10335
10336 struct Row4;
10337 struct ˇRow5.1;
10338 struct Row5.2;
10339 struct «Rowˇ»5.3;
10340 struct Row5;
10341 struct Row6;
10342 ˇ
10343 struct Row9.1;
10344 struct «Rowˇ»9.2;
10345 struct «ˇRow»9.3;
10346 struct Row8;
10347 struct Row9;
10348 «ˇ// something on bottom»
10349 struct Row10;"#},
10350 vec![
10351 DiffHunkStatus::Added,
10352 DiffHunkStatus::Added,
10353 DiffHunkStatus::Added,
10354 DiffHunkStatus::Added,
10355 DiffHunkStatus::Added,
10356 ],
10357 indoc! {r#"struct Row;
10358 ˇstruct Row1;
10359 struct Row2;
10360 ˇ
10361 struct Row4;
10362 ˇstruct Row5;
10363 struct Row6;
10364 ˇ
10365 ˇstruct Row8;
10366 struct Row9;
10367 ˇstruct Row10;"#},
10368 base_text,
10369 &mut cx,
10370 );
10371}
10372
10373#[gpui::test]
10374async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10375 init_test(cx, |_| {});
10376 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10377 let base_text = indoc! {r#"struct Row;
10378struct Row1;
10379struct Row2;
10380
10381struct Row4;
10382struct Row5;
10383struct Row6;
10384
10385struct Row8;
10386struct Row9;
10387struct Row10;"#};
10388
10389 // Modification hunks behave the same as the addition ones.
10390 assert_hunk_revert(
10391 indoc! {r#"struct Row;
10392 struct Row1;
10393 struct Row33;
10394 ˇ
10395 struct Row4;
10396 struct Row5;
10397 struct Row6;
10398 ˇ
10399 struct Row99;
10400 struct Row9;
10401 struct Row10;"#},
10402 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10403 indoc! {r#"struct Row;
10404 struct Row1;
10405 struct Row33;
10406 ˇ
10407 struct Row4;
10408 struct Row5;
10409 struct Row6;
10410 ˇ
10411 struct Row99;
10412 struct Row9;
10413 struct Row10;"#},
10414 base_text,
10415 &mut cx,
10416 );
10417 assert_hunk_revert(
10418 indoc! {r#"struct Row;
10419 struct Row1;
10420 struct Row33;
10421 «ˇ
10422 struct Row4;
10423 struct» Row5;
10424 «struct Row6;
10425 ˇ»
10426 struct Row99;
10427 struct Row9;
10428 struct Row10;"#},
10429 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10430 indoc! {r#"struct Row;
10431 struct Row1;
10432 struct Row33;
10433 «ˇ
10434 struct Row4;
10435 struct» Row5;
10436 «struct Row6;
10437 ˇ»
10438 struct Row99;
10439 struct Row9;
10440 struct Row10;"#},
10441 base_text,
10442 &mut cx,
10443 );
10444
10445 assert_hunk_revert(
10446 indoc! {r#"ˇstruct Row1.1;
10447 struct Row1;
10448 «ˇstr»uct Row22;
10449
10450 struct ˇRow44;
10451 struct Row5;
10452 struct «Rˇ»ow66;ˇ
10453
10454 «struˇ»ct Row88;
10455 struct Row9;
10456 struct Row1011;ˇ"#},
10457 vec![
10458 DiffHunkStatus::Modified,
10459 DiffHunkStatus::Modified,
10460 DiffHunkStatus::Modified,
10461 DiffHunkStatus::Modified,
10462 DiffHunkStatus::Modified,
10463 DiffHunkStatus::Modified,
10464 ],
10465 indoc! {r#"struct Row;
10466 ˇstruct Row1;
10467 struct Row2;
10468 ˇ
10469 struct Row4;
10470 ˇstruct Row5;
10471 struct Row6;
10472 ˇ
10473 struct Row8;
10474 ˇstruct Row9;
10475 struct Row10;ˇ"#},
10476 base_text,
10477 &mut cx,
10478 );
10479}
10480
10481#[gpui::test]
10482async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10483 init_test(cx, |_| {});
10484 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10485 let base_text = indoc! {r#"struct Row;
10486struct Row1;
10487struct Row2;
10488
10489struct Row4;
10490struct Row5;
10491struct Row6;
10492
10493struct Row8;
10494struct Row9;
10495struct Row10;"#};
10496
10497 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10498 assert_hunk_revert(
10499 indoc! {r#"struct Row;
10500 struct Row2;
10501
10502 ˇstruct Row4;
10503 struct Row5;
10504 struct Row6;
10505 ˇ
10506 struct Row8;
10507 struct Row10;"#},
10508 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10509 indoc! {r#"struct Row;
10510 struct Row2;
10511
10512 ˇstruct Row4;
10513 struct Row5;
10514 struct Row6;
10515 ˇ
10516 struct Row8;
10517 struct Row10;"#},
10518 base_text,
10519 &mut cx,
10520 );
10521 assert_hunk_revert(
10522 indoc! {r#"struct Row;
10523 struct Row2;
10524
10525 «ˇstruct Row4;
10526 struct» Row5;
10527 «struct Row6;
10528 ˇ»
10529 struct Row8;
10530 struct Row10;"#},
10531 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10532 indoc! {r#"struct Row;
10533 struct Row2;
10534
10535 «ˇstruct Row4;
10536 struct» Row5;
10537 «struct Row6;
10538 ˇ»
10539 struct Row8;
10540 struct Row10;"#},
10541 base_text,
10542 &mut cx,
10543 );
10544
10545 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10546 assert_hunk_revert(
10547 indoc! {r#"struct Row;
10548 ˇstruct Row2;
10549
10550 struct Row4;
10551 struct Row5;
10552 struct Row6;
10553
10554 struct Row8;ˇ
10555 struct Row10;"#},
10556 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10557 indoc! {r#"struct Row;
10558 struct Row1;
10559 ˇstruct Row2;
10560
10561 struct Row4;
10562 struct Row5;
10563 struct Row6;
10564
10565 struct Row8;ˇ
10566 struct Row9;
10567 struct Row10;"#},
10568 base_text,
10569 &mut cx,
10570 );
10571 assert_hunk_revert(
10572 indoc! {r#"struct Row;
10573 struct Row2«ˇ;
10574 struct Row4;
10575 struct» Row5;
10576 «struct Row6;
10577
10578 struct Row8;ˇ»
10579 struct Row10;"#},
10580 vec![
10581 DiffHunkStatus::Removed,
10582 DiffHunkStatus::Removed,
10583 DiffHunkStatus::Removed,
10584 ],
10585 indoc! {r#"struct Row;
10586 struct Row1;
10587 struct Row2«ˇ;
10588
10589 struct Row4;
10590 struct» Row5;
10591 «struct Row6;
10592
10593 struct Row8;ˇ»
10594 struct Row9;
10595 struct Row10;"#},
10596 base_text,
10597 &mut cx,
10598 );
10599}
10600
10601#[gpui::test]
10602async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10603 init_test(cx, |_| {});
10604
10605 let cols = 4;
10606 let rows = 10;
10607 let sample_text_1 = sample_text(rows, cols, 'a');
10608 assert_eq!(
10609 sample_text_1,
10610 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10611 );
10612 let sample_text_2 = sample_text(rows, cols, 'l');
10613 assert_eq!(
10614 sample_text_2,
10615 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10616 );
10617 let sample_text_3 = sample_text(rows, cols, 'v');
10618 assert_eq!(
10619 sample_text_3,
10620 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10621 );
10622
10623 fn diff_every_buffer_row(
10624 buffer: &Model<Buffer>,
10625 sample_text: String,
10626 cols: usize,
10627 cx: &mut gpui::TestAppContext,
10628 ) {
10629 // revert first character in each row, creating one large diff hunk per buffer
10630 let is_first_char = |offset: usize| offset % cols == 0;
10631 buffer.update(cx, |buffer, cx| {
10632 buffer.set_text(
10633 sample_text
10634 .chars()
10635 .enumerate()
10636 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10637 .collect::<String>(),
10638 cx,
10639 );
10640 buffer.set_diff_base(Some(sample_text), cx);
10641 });
10642 cx.executor().run_until_parked();
10643 }
10644
10645 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10646 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10647
10648 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10649 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10650
10651 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10652 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10653
10654 let multibuffer = cx.new_model(|cx| {
10655 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10656 multibuffer.push_excerpts(
10657 buffer_1.clone(),
10658 [
10659 ExcerptRange {
10660 context: Point::new(0, 0)..Point::new(3, 0),
10661 primary: None,
10662 },
10663 ExcerptRange {
10664 context: Point::new(5, 0)..Point::new(7, 0),
10665 primary: None,
10666 },
10667 ExcerptRange {
10668 context: Point::new(9, 0)..Point::new(10, 4),
10669 primary: None,
10670 },
10671 ],
10672 cx,
10673 );
10674 multibuffer.push_excerpts(
10675 buffer_2.clone(),
10676 [
10677 ExcerptRange {
10678 context: Point::new(0, 0)..Point::new(3, 0),
10679 primary: None,
10680 },
10681 ExcerptRange {
10682 context: Point::new(5, 0)..Point::new(7, 0),
10683 primary: None,
10684 },
10685 ExcerptRange {
10686 context: Point::new(9, 0)..Point::new(10, 4),
10687 primary: None,
10688 },
10689 ],
10690 cx,
10691 );
10692 multibuffer.push_excerpts(
10693 buffer_3.clone(),
10694 [
10695 ExcerptRange {
10696 context: Point::new(0, 0)..Point::new(3, 0),
10697 primary: None,
10698 },
10699 ExcerptRange {
10700 context: Point::new(5, 0)..Point::new(7, 0),
10701 primary: None,
10702 },
10703 ExcerptRange {
10704 context: Point::new(9, 0)..Point::new(10, 4),
10705 primary: None,
10706 },
10707 ],
10708 cx,
10709 );
10710 multibuffer
10711 });
10712
10713 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10714 editor.update(cx, |editor, cx| {
10715 assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
10716 editor.select_all(&SelectAll, cx);
10717 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10718 });
10719 cx.executor().run_until_parked();
10720 // When all ranges are selected, all buffer hunks are reverted.
10721 editor.update(cx, |editor, cx| {
10722 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");
10723 });
10724 buffer_1.update(cx, |buffer, _| {
10725 assert_eq!(buffer.text(), sample_text_1);
10726 });
10727 buffer_2.update(cx, |buffer, _| {
10728 assert_eq!(buffer.text(), sample_text_2);
10729 });
10730 buffer_3.update(cx, |buffer, _| {
10731 assert_eq!(buffer.text(), sample_text_3);
10732 });
10733
10734 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10735 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10736 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10737 editor.update(cx, |editor, cx| {
10738 editor.change_selections(None, cx, |s| {
10739 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10740 });
10741 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10742 });
10743 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10744 // but not affect buffer_2 and its related excerpts.
10745 editor.update(cx, |editor, cx| {
10746 assert_eq!(
10747 editor.text(cx),
10748 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
10749 );
10750 });
10751 buffer_1.update(cx, |buffer, _| {
10752 assert_eq!(buffer.text(), sample_text_1);
10753 });
10754 buffer_2.update(cx, |buffer, _| {
10755 assert_eq!(
10756 buffer.text(),
10757 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10758 );
10759 });
10760 buffer_3.update(cx, |buffer, _| {
10761 assert_eq!(
10762 buffer.text(),
10763 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10764 );
10765 });
10766}
10767
10768#[gpui::test]
10769async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10770 init_test(cx, |_| {});
10771
10772 let cols = 4;
10773 let rows = 10;
10774 let sample_text_1 = sample_text(rows, cols, 'a');
10775 assert_eq!(
10776 sample_text_1,
10777 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10778 );
10779 let sample_text_2 = sample_text(rows, cols, 'l');
10780 assert_eq!(
10781 sample_text_2,
10782 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10783 );
10784 let sample_text_3 = sample_text(rows, cols, 'v');
10785 assert_eq!(
10786 sample_text_3,
10787 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10788 );
10789
10790 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10791 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10792 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10793
10794 let multi_buffer = cx.new_model(|cx| {
10795 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10796 multibuffer.push_excerpts(
10797 buffer_1.clone(),
10798 [
10799 ExcerptRange {
10800 context: Point::new(0, 0)..Point::new(3, 0),
10801 primary: None,
10802 },
10803 ExcerptRange {
10804 context: Point::new(5, 0)..Point::new(7, 0),
10805 primary: None,
10806 },
10807 ExcerptRange {
10808 context: Point::new(9, 0)..Point::new(10, 4),
10809 primary: None,
10810 },
10811 ],
10812 cx,
10813 );
10814 multibuffer.push_excerpts(
10815 buffer_2.clone(),
10816 [
10817 ExcerptRange {
10818 context: Point::new(0, 0)..Point::new(3, 0),
10819 primary: None,
10820 },
10821 ExcerptRange {
10822 context: Point::new(5, 0)..Point::new(7, 0),
10823 primary: None,
10824 },
10825 ExcerptRange {
10826 context: Point::new(9, 0)..Point::new(10, 4),
10827 primary: None,
10828 },
10829 ],
10830 cx,
10831 );
10832 multibuffer.push_excerpts(
10833 buffer_3.clone(),
10834 [
10835 ExcerptRange {
10836 context: Point::new(0, 0)..Point::new(3, 0),
10837 primary: None,
10838 },
10839 ExcerptRange {
10840 context: Point::new(5, 0)..Point::new(7, 0),
10841 primary: None,
10842 },
10843 ExcerptRange {
10844 context: Point::new(9, 0)..Point::new(10, 4),
10845 primary: None,
10846 },
10847 ],
10848 cx,
10849 );
10850 multibuffer
10851 });
10852
10853 let fs = FakeFs::new(cx.executor());
10854 fs.insert_tree(
10855 "/a",
10856 json!({
10857 "main.rs": sample_text_1,
10858 "other.rs": sample_text_2,
10859 "lib.rs": sample_text_3,
10860 }),
10861 )
10862 .await;
10863 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10864 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10865 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10866 let multi_buffer_editor = cx.new_view(|cx| {
10867 Editor::new(
10868 EditorMode::Full,
10869 multi_buffer,
10870 Some(project.clone()),
10871 true,
10872 cx,
10873 )
10874 });
10875 let multibuffer_item_id = workspace
10876 .update(cx, |workspace, cx| {
10877 assert!(
10878 workspace.active_item(cx).is_none(),
10879 "active item should be None before the first item is added"
10880 );
10881 workspace.add_item_to_active_pane(
10882 Box::new(multi_buffer_editor.clone()),
10883 None,
10884 true,
10885 cx,
10886 );
10887 let active_item = workspace
10888 .active_item(cx)
10889 .expect("should have an active item after adding the multi buffer");
10890 assert!(
10891 !active_item.is_singleton(cx),
10892 "A multi buffer was expected to active after adding"
10893 );
10894 active_item.item_id()
10895 })
10896 .unwrap();
10897 cx.executor().run_until_parked();
10898
10899 multi_buffer_editor.update(cx, |editor, cx| {
10900 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10901 editor.open_excerpts(&OpenExcerpts, cx);
10902 });
10903 cx.executor().run_until_parked();
10904 let first_item_id = workspace
10905 .update(cx, |workspace, cx| {
10906 let active_item = workspace
10907 .active_item(cx)
10908 .expect("should have an active item after navigating into the 1st buffer");
10909 let first_item_id = active_item.item_id();
10910 assert_ne!(
10911 first_item_id, multibuffer_item_id,
10912 "Should navigate into the 1st buffer and activate it"
10913 );
10914 assert!(
10915 active_item.is_singleton(cx),
10916 "New active item should be a singleton buffer"
10917 );
10918 assert_eq!(
10919 active_item
10920 .act_as::<Editor>(cx)
10921 .expect("should have navigated into an editor for the 1st buffer")
10922 .read(cx)
10923 .text(cx),
10924 sample_text_1
10925 );
10926
10927 workspace
10928 .go_back(workspace.active_pane().downgrade(), cx)
10929 .detach_and_log_err(cx);
10930
10931 first_item_id
10932 })
10933 .unwrap();
10934 cx.executor().run_until_parked();
10935 workspace
10936 .update(cx, |workspace, cx| {
10937 let active_item = workspace
10938 .active_item(cx)
10939 .expect("should have an active item after navigating back");
10940 assert_eq!(
10941 active_item.item_id(),
10942 multibuffer_item_id,
10943 "Should navigate back to the multi buffer"
10944 );
10945 assert!(!active_item.is_singleton(cx));
10946 })
10947 .unwrap();
10948
10949 multi_buffer_editor.update(cx, |editor, cx| {
10950 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10951 s.select_ranges(Some(39..40))
10952 });
10953 editor.open_excerpts(&OpenExcerpts, cx);
10954 });
10955 cx.executor().run_until_parked();
10956 let second_item_id = workspace
10957 .update(cx, |workspace, cx| {
10958 let active_item = workspace
10959 .active_item(cx)
10960 .expect("should have an active item after navigating into the 2nd buffer");
10961 let second_item_id = active_item.item_id();
10962 assert_ne!(
10963 second_item_id, multibuffer_item_id,
10964 "Should navigate away from the multibuffer"
10965 );
10966 assert_ne!(
10967 second_item_id, first_item_id,
10968 "Should navigate into the 2nd buffer and activate it"
10969 );
10970 assert!(
10971 active_item.is_singleton(cx),
10972 "New active item should be a singleton buffer"
10973 );
10974 assert_eq!(
10975 active_item
10976 .act_as::<Editor>(cx)
10977 .expect("should have navigated into an editor")
10978 .read(cx)
10979 .text(cx),
10980 sample_text_2
10981 );
10982
10983 workspace
10984 .go_back(workspace.active_pane().downgrade(), cx)
10985 .detach_and_log_err(cx);
10986
10987 second_item_id
10988 })
10989 .unwrap();
10990 cx.executor().run_until_parked();
10991 workspace
10992 .update(cx, |workspace, cx| {
10993 let active_item = workspace
10994 .active_item(cx)
10995 .expect("should have an active item after navigating back from the 2nd buffer");
10996 assert_eq!(
10997 active_item.item_id(),
10998 multibuffer_item_id,
10999 "Should navigate back from the 2nd buffer to the multi buffer"
11000 );
11001 assert!(!active_item.is_singleton(cx));
11002 })
11003 .unwrap();
11004
11005 multi_buffer_editor.update(cx, |editor, cx| {
11006 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11007 s.select_ranges(Some(60..70))
11008 });
11009 editor.open_excerpts(&OpenExcerpts, cx);
11010 });
11011 cx.executor().run_until_parked();
11012 workspace
11013 .update(cx, |workspace, cx| {
11014 let active_item = workspace
11015 .active_item(cx)
11016 .expect("should have an active item after navigating into the 3rd buffer");
11017 let third_item_id = active_item.item_id();
11018 assert_ne!(
11019 third_item_id, multibuffer_item_id,
11020 "Should navigate into the 3rd buffer and activate it"
11021 );
11022 assert_ne!(third_item_id, first_item_id);
11023 assert_ne!(third_item_id, second_item_id);
11024 assert!(
11025 active_item.is_singleton(cx),
11026 "New active item should be a singleton buffer"
11027 );
11028 assert_eq!(
11029 active_item
11030 .act_as::<Editor>(cx)
11031 .expect("should have navigated into an editor")
11032 .read(cx)
11033 .text(cx),
11034 sample_text_3
11035 );
11036
11037 workspace
11038 .go_back(workspace.active_pane().downgrade(), cx)
11039 .detach_and_log_err(cx);
11040 })
11041 .unwrap();
11042 cx.executor().run_until_parked();
11043 workspace
11044 .update(cx, |workspace, cx| {
11045 let active_item = workspace
11046 .active_item(cx)
11047 .expect("should have an active item after navigating back from the 3rd buffer");
11048 assert_eq!(
11049 active_item.item_id(),
11050 multibuffer_item_id,
11051 "Should navigate back from the 3rd buffer to the multi buffer"
11052 );
11053 assert!(!active_item.is_singleton(cx));
11054 })
11055 .unwrap();
11056}
11057
11058#[gpui::test]
11059async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11060 init_test(cx, |_| {});
11061
11062 let mut cx = EditorTestContext::new(cx).await;
11063
11064 let diff_base = r#"
11065 use some::mod;
11066
11067 const A: u32 = 42;
11068
11069 fn main() {
11070 println!("hello");
11071
11072 println!("world");
11073 }
11074 "#
11075 .unindent();
11076
11077 cx.set_state(
11078 &r#"
11079 use some::modified;
11080
11081 ˇ
11082 fn main() {
11083 println!("hello there");
11084
11085 println!("around the");
11086 println!("world");
11087 }
11088 "#
11089 .unindent(),
11090 );
11091
11092 cx.set_diff_base(Some(&diff_base));
11093 executor.run_until_parked();
11094 let unexpanded_hunks = vec![
11095 (
11096 "use some::mod;\n".to_string(),
11097 DiffHunkStatus::Modified,
11098 DisplayRow(0)..DisplayRow(1),
11099 ),
11100 (
11101 "const A: u32 = 42;\n".to_string(),
11102 DiffHunkStatus::Removed,
11103 DisplayRow(2)..DisplayRow(2),
11104 ),
11105 (
11106 " println!(\"hello\");\n".to_string(),
11107 DiffHunkStatus::Modified,
11108 DisplayRow(4)..DisplayRow(5),
11109 ),
11110 (
11111 "".to_string(),
11112 DiffHunkStatus::Added,
11113 DisplayRow(6)..DisplayRow(7),
11114 ),
11115 ];
11116 cx.update_editor(|editor, cx| {
11117 let snapshot = editor.snapshot(cx);
11118 let all_hunks = editor_hunks(editor, &snapshot, cx);
11119 assert_eq!(all_hunks, unexpanded_hunks);
11120 });
11121
11122 cx.update_editor(|editor, cx| {
11123 for _ in 0..4 {
11124 editor.go_to_hunk(&GoToHunk, cx);
11125 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11126 }
11127 });
11128 executor.run_until_parked();
11129 cx.assert_editor_state(
11130 &r#"
11131 use some::modified;
11132
11133 ˇ
11134 fn main() {
11135 println!("hello there");
11136
11137 println!("around the");
11138 println!("world");
11139 }
11140 "#
11141 .unindent(),
11142 );
11143 cx.update_editor(|editor, cx| {
11144 let snapshot = editor.snapshot(cx);
11145 let all_hunks = editor_hunks(editor, &snapshot, cx);
11146 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11147 assert_eq!(
11148 expanded_hunks_background_highlights(editor, cx),
11149 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
11150 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
11151 );
11152 assert_eq!(
11153 all_hunks,
11154 vec![
11155 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
11156 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
11157 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
11158 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
11159 ],
11160 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
11161 (from modified and removed hunks)"
11162 );
11163 assert_eq!(
11164 all_hunks, all_expanded_hunks,
11165 "Editor hunks should not change and all be expanded"
11166 );
11167 });
11168
11169 cx.update_editor(|editor, cx| {
11170 editor.cancel(&Cancel, cx);
11171
11172 let snapshot = editor.snapshot(cx);
11173 let all_hunks = editor_hunks(editor, &snapshot, cx);
11174 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11175 assert_eq!(
11176 expanded_hunks_background_highlights(editor, cx),
11177 Vec::new(),
11178 "After cancelling in editor, no git highlights should be left"
11179 );
11180 assert_eq!(
11181 all_expanded_hunks,
11182 Vec::new(),
11183 "After cancelling in editor, no hunks should be expanded"
11184 );
11185 assert_eq!(
11186 all_hunks, unexpanded_hunks,
11187 "After cancelling in editor, regular hunks' coordinates should get back to normal"
11188 );
11189 });
11190}
11191
11192#[gpui::test]
11193async fn test_toggled_diff_base_change(
11194 executor: BackgroundExecutor,
11195 cx: &mut gpui::TestAppContext,
11196) {
11197 init_test(cx, |_| {});
11198
11199 let mut cx = EditorTestContext::new(cx).await;
11200
11201 let diff_base = r#"
11202 use some::mod1;
11203 use some::mod2;
11204
11205 const A: u32 = 42;
11206 const B: u32 = 42;
11207 const C: u32 = 42;
11208
11209 fn main(ˇ) {
11210 println!("hello");
11211
11212 println!("world");
11213 }
11214 "#
11215 .unindent();
11216
11217 cx.set_state(
11218 &r#"
11219 use some::mod2;
11220
11221 const A: u32 = 42;
11222 const C: u32 = 42;
11223
11224 fn main(ˇ) {
11225 //println!("hello");
11226
11227 println!("world");
11228 //
11229 //
11230 }
11231 "#
11232 .unindent(),
11233 );
11234
11235 cx.set_diff_base(Some(&diff_base));
11236 executor.run_until_parked();
11237 cx.update_editor(|editor, cx| {
11238 let snapshot = editor.snapshot(cx);
11239 let all_hunks = editor_hunks(editor, &snapshot, cx);
11240 assert_eq!(
11241 all_hunks,
11242 vec![
11243 (
11244 "use some::mod1;\n".to_string(),
11245 DiffHunkStatus::Removed,
11246 DisplayRow(0)..DisplayRow(0)
11247 ),
11248 (
11249 "const B: u32 = 42;\n".to_string(),
11250 DiffHunkStatus::Removed,
11251 DisplayRow(3)..DisplayRow(3)
11252 ),
11253 (
11254 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11255 DiffHunkStatus::Modified,
11256 DisplayRow(5)..DisplayRow(7)
11257 ),
11258 (
11259 "".to_string(),
11260 DiffHunkStatus::Added,
11261 DisplayRow(9)..DisplayRow(11)
11262 ),
11263 ]
11264 );
11265 });
11266
11267 cx.update_editor(|editor, cx| {
11268 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11269 });
11270 executor.run_until_parked();
11271 cx.assert_editor_state(
11272 &r#"
11273 use some::mod2;
11274
11275 const A: u32 = 42;
11276 const C: u32 = 42;
11277
11278 fn main(ˇ) {
11279 //println!("hello");
11280
11281 println!("world");
11282 //
11283 //
11284 }
11285 "#
11286 .unindent(),
11287 );
11288 cx.update_editor(|editor, cx| {
11289 let snapshot = editor.snapshot(cx);
11290 let all_hunks = editor_hunks(editor, &snapshot, cx);
11291 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11292 assert_eq!(
11293 expanded_hunks_background_highlights(editor, cx),
11294 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
11295 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
11296 );
11297 assert_eq!(
11298 all_hunks,
11299 vec![
11300 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
11301 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
11302 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
11303 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
11304 ],
11305 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
11306 (from modified and removed hunks)"
11307 );
11308 assert_eq!(
11309 all_hunks, all_expanded_hunks,
11310 "Editor hunks should not change and all be expanded"
11311 );
11312 });
11313
11314 cx.set_diff_base(Some("new diff base!"));
11315 executor.run_until_parked();
11316
11317 cx.update_editor(|editor, cx| {
11318 let snapshot = editor.snapshot(cx);
11319 let all_hunks = editor_hunks(editor, &snapshot, cx);
11320 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11321 assert_eq!(
11322 expanded_hunks_background_highlights(editor, cx),
11323 Vec::new(),
11324 "After diff base is changed, old git highlights should be removed"
11325 );
11326 assert_eq!(
11327 all_expanded_hunks,
11328 Vec::new(),
11329 "After diff base is changed, old git hunk expansions should be removed"
11330 );
11331 assert_eq!(
11332 all_hunks,
11333 vec![(
11334 "new diff base!".to_string(),
11335 DiffHunkStatus::Modified,
11336 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
11337 )],
11338 "After diff base is changed, hunks should update"
11339 );
11340 });
11341}
11342
11343#[gpui::test]
11344async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11345 init_test(cx, |_| {});
11346
11347 let mut cx = EditorTestContext::new(cx).await;
11348
11349 let diff_base = r#"
11350 use some::mod1;
11351 use some::mod2;
11352
11353 const A: u32 = 42;
11354 const B: u32 = 42;
11355 const C: u32 = 42;
11356
11357 fn main(ˇ) {
11358 println!("hello");
11359
11360 println!("world");
11361 }
11362
11363 fn another() {
11364 println!("another");
11365 }
11366
11367 fn another2() {
11368 println!("another2");
11369 }
11370 "#
11371 .unindent();
11372
11373 cx.set_state(
11374 &r#"
11375 «use some::mod2;
11376
11377 const A: u32 = 42;
11378 const C: u32 = 42;
11379
11380 fn main() {
11381 //println!("hello");
11382
11383 println!("world");
11384 //
11385 //ˇ»
11386 }
11387
11388 fn another() {
11389 println!("another");
11390 println!("another");
11391 }
11392
11393 println!("another2");
11394 }
11395 "#
11396 .unindent(),
11397 );
11398
11399 cx.set_diff_base(Some(&diff_base));
11400 executor.run_until_parked();
11401 cx.update_editor(|editor, cx| {
11402 let snapshot = editor.snapshot(cx);
11403 let all_hunks = editor_hunks(editor, &snapshot, cx);
11404 assert_eq!(
11405 all_hunks,
11406 vec![
11407 (
11408 "use some::mod1;\n".to_string(),
11409 DiffHunkStatus::Removed,
11410 DisplayRow(0)..DisplayRow(0)
11411 ),
11412 (
11413 "const B: u32 = 42;\n".to_string(),
11414 DiffHunkStatus::Removed,
11415 DisplayRow(3)..DisplayRow(3)
11416 ),
11417 (
11418 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11419 DiffHunkStatus::Modified,
11420 DisplayRow(5)..DisplayRow(7)
11421 ),
11422 (
11423 "".to_string(),
11424 DiffHunkStatus::Added,
11425 DisplayRow(9)..DisplayRow(11)
11426 ),
11427 (
11428 "".to_string(),
11429 DiffHunkStatus::Added,
11430 DisplayRow(15)..DisplayRow(16)
11431 ),
11432 (
11433 "fn another2() {\n".to_string(),
11434 DiffHunkStatus::Removed,
11435 DisplayRow(18)..DisplayRow(18)
11436 ),
11437 ]
11438 );
11439 });
11440
11441 cx.update_editor(|editor, cx| {
11442 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11443 });
11444 executor.run_until_parked();
11445 cx.assert_editor_state(
11446 &r#"
11447 «use some::mod2;
11448
11449 const A: u32 = 42;
11450 const C: u32 = 42;
11451
11452 fn main() {
11453 //println!("hello");
11454
11455 println!("world");
11456 //
11457 //ˇ»
11458 }
11459
11460 fn another() {
11461 println!("another");
11462 println!("another");
11463 }
11464
11465 println!("another2");
11466 }
11467 "#
11468 .unindent(),
11469 );
11470 cx.update_editor(|editor, cx| {
11471 let snapshot = editor.snapshot(cx);
11472 let all_hunks = editor_hunks(editor, &snapshot, cx);
11473 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11474 assert_eq!(
11475 expanded_hunks_background_highlights(editor, cx),
11476 vec![
11477 DisplayRow(9)..=DisplayRow(10),
11478 DisplayRow(13)..=DisplayRow(14),
11479 DisplayRow(19)..=DisplayRow(19)
11480 ]
11481 );
11482 assert_eq!(
11483 all_hunks,
11484 vec![
11485 (
11486 "use some::mod1;\n".to_string(),
11487 DiffHunkStatus::Removed,
11488 DisplayRow(1)..DisplayRow(1)
11489 ),
11490 (
11491 "const B: u32 = 42;\n".to_string(),
11492 DiffHunkStatus::Removed,
11493 DisplayRow(5)..DisplayRow(5)
11494 ),
11495 (
11496 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11497 DiffHunkStatus::Modified,
11498 DisplayRow(9)..DisplayRow(11)
11499 ),
11500 (
11501 "".to_string(),
11502 DiffHunkStatus::Added,
11503 DisplayRow(13)..DisplayRow(15)
11504 ),
11505 (
11506 "".to_string(),
11507 DiffHunkStatus::Added,
11508 DisplayRow(19)..DisplayRow(20)
11509 ),
11510 (
11511 "fn another2() {\n".to_string(),
11512 DiffHunkStatus::Removed,
11513 DisplayRow(23)..DisplayRow(23)
11514 ),
11515 ],
11516 );
11517 assert_eq!(all_hunks, all_expanded_hunks);
11518 });
11519
11520 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11521 cx.executor().run_until_parked();
11522 cx.assert_editor_state(
11523 &r#"
11524 «use some::mod2;
11525
11526 const A: u32 = 42;
11527 const C: u32 = 42;
11528
11529 fn main() {
11530 //println!("hello");
11531
11532 println!("world");
11533 //
11534 //ˇ»
11535 }
11536
11537 fn another() {
11538 println!("another");
11539 println!("another");
11540 }
11541
11542 println!("another2");
11543 }
11544 "#
11545 .unindent(),
11546 );
11547 cx.update_editor(|editor, cx| {
11548 let snapshot = editor.snapshot(cx);
11549 let all_hunks = editor_hunks(editor, &snapshot, cx);
11550 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11551 assert_eq!(
11552 expanded_hunks_background_highlights(editor, cx),
11553 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11554 "Only one hunk is left not folded, its highlight should be visible"
11555 );
11556 assert_eq!(
11557 all_hunks,
11558 vec![
11559 (
11560 "use some::mod1;\n".to_string(),
11561 DiffHunkStatus::Removed,
11562 DisplayRow(0)..DisplayRow(0)
11563 ),
11564 (
11565 "const B: u32 = 42;\n".to_string(),
11566 DiffHunkStatus::Removed,
11567 DisplayRow(0)..DisplayRow(0)
11568 ),
11569 (
11570 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11571 DiffHunkStatus::Modified,
11572 DisplayRow(0)..DisplayRow(0)
11573 ),
11574 (
11575 "".to_string(),
11576 DiffHunkStatus::Added,
11577 DisplayRow(0)..DisplayRow(1)
11578 ),
11579 (
11580 "".to_string(),
11581 DiffHunkStatus::Added,
11582 DisplayRow(5)..DisplayRow(6)
11583 ),
11584 (
11585 "fn another2() {\n".to_string(),
11586 DiffHunkStatus::Removed,
11587 DisplayRow(9)..DisplayRow(9)
11588 ),
11589 ],
11590 "Hunk list should still return shifted folded hunks"
11591 );
11592 assert_eq!(
11593 all_expanded_hunks,
11594 vec![
11595 (
11596 "".to_string(),
11597 DiffHunkStatus::Added,
11598 DisplayRow(5)..DisplayRow(6)
11599 ),
11600 (
11601 "fn another2() {\n".to_string(),
11602 DiffHunkStatus::Removed,
11603 DisplayRow(9)..DisplayRow(9)
11604 ),
11605 ],
11606 "Only non-folded hunks should be left expanded"
11607 );
11608 });
11609
11610 cx.update_editor(|editor, cx| {
11611 editor.select_all(&SelectAll, cx);
11612 editor.unfold_lines(&UnfoldLines, cx);
11613 });
11614 cx.executor().run_until_parked();
11615 cx.assert_editor_state(
11616 &r#"
11617 «use some::mod2;
11618
11619 const A: u32 = 42;
11620 const C: u32 = 42;
11621
11622 fn main() {
11623 //println!("hello");
11624
11625 println!("world");
11626 //
11627 //
11628 }
11629
11630 fn another() {
11631 println!("another");
11632 println!("another");
11633 }
11634
11635 println!("another2");
11636 }
11637 ˇ»"#
11638 .unindent(),
11639 );
11640 cx.update_editor(|editor, cx| {
11641 let snapshot = editor.snapshot(cx);
11642 let all_hunks = editor_hunks(editor, &snapshot, cx);
11643 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11644 assert_eq!(
11645 expanded_hunks_background_highlights(editor, cx),
11646 vec![
11647 DisplayRow(9)..=DisplayRow(10),
11648 DisplayRow(13)..=DisplayRow(14),
11649 DisplayRow(19)..=DisplayRow(19)
11650 ],
11651 "After unfolding, all hunk diffs should be visible again"
11652 );
11653 assert_eq!(
11654 all_hunks,
11655 vec![
11656 (
11657 "use some::mod1;\n".to_string(),
11658 DiffHunkStatus::Removed,
11659 DisplayRow(1)..DisplayRow(1)
11660 ),
11661 (
11662 "const B: u32 = 42;\n".to_string(),
11663 DiffHunkStatus::Removed,
11664 DisplayRow(5)..DisplayRow(5)
11665 ),
11666 (
11667 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11668 DiffHunkStatus::Modified,
11669 DisplayRow(9)..DisplayRow(11)
11670 ),
11671 (
11672 "".to_string(),
11673 DiffHunkStatus::Added,
11674 DisplayRow(13)..DisplayRow(15)
11675 ),
11676 (
11677 "".to_string(),
11678 DiffHunkStatus::Added,
11679 DisplayRow(19)..DisplayRow(20)
11680 ),
11681 (
11682 "fn another2() {\n".to_string(),
11683 DiffHunkStatus::Removed,
11684 DisplayRow(23)..DisplayRow(23)
11685 ),
11686 ],
11687 );
11688 assert_eq!(all_hunks, all_expanded_hunks);
11689 });
11690}
11691
11692#[gpui::test]
11693async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11694 init_test(cx, |_| {});
11695
11696 let cols = 4;
11697 let rows = 10;
11698 let sample_text_1 = sample_text(rows, cols, 'a');
11699 assert_eq!(
11700 sample_text_1,
11701 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11702 );
11703 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11704 let sample_text_2 = sample_text(rows, cols, 'l');
11705 assert_eq!(
11706 sample_text_2,
11707 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11708 );
11709 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11710 let sample_text_3 = sample_text(rows, cols, 'v');
11711 assert_eq!(
11712 sample_text_3,
11713 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11714 );
11715 let modified_sample_text_3 =
11716 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11717 let buffer_1 = cx.new_model(|cx| {
11718 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11719 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11720 buffer
11721 });
11722 let buffer_2 = cx.new_model(|cx| {
11723 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11724 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11725 buffer
11726 });
11727 let buffer_3 = cx.new_model(|cx| {
11728 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11729 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11730 buffer
11731 });
11732
11733 let multi_buffer = cx.new_model(|cx| {
11734 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11735 multibuffer.push_excerpts(
11736 buffer_1.clone(),
11737 [
11738 ExcerptRange {
11739 context: Point::new(0, 0)..Point::new(3, 0),
11740 primary: None,
11741 },
11742 ExcerptRange {
11743 context: Point::new(5, 0)..Point::new(7, 0),
11744 primary: None,
11745 },
11746 ExcerptRange {
11747 context: Point::new(9, 0)..Point::new(10, 4),
11748 primary: None,
11749 },
11750 ],
11751 cx,
11752 );
11753 multibuffer.push_excerpts(
11754 buffer_2.clone(),
11755 [
11756 ExcerptRange {
11757 context: Point::new(0, 0)..Point::new(3, 0),
11758 primary: None,
11759 },
11760 ExcerptRange {
11761 context: Point::new(5, 0)..Point::new(7, 0),
11762 primary: None,
11763 },
11764 ExcerptRange {
11765 context: Point::new(9, 0)..Point::new(10, 4),
11766 primary: None,
11767 },
11768 ],
11769 cx,
11770 );
11771 multibuffer.push_excerpts(
11772 buffer_3.clone(),
11773 [
11774 ExcerptRange {
11775 context: Point::new(0, 0)..Point::new(3, 0),
11776 primary: None,
11777 },
11778 ExcerptRange {
11779 context: Point::new(5, 0)..Point::new(7, 0),
11780 primary: None,
11781 },
11782 ExcerptRange {
11783 context: Point::new(9, 0)..Point::new(10, 4),
11784 primary: None,
11785 },
11786 ],
11787 cx,
11788 );
11789 multibuffer
11790 });
11791
11792 let fs = FakeFs::new(cx.executor());
11793 fs.insert_tree(
11794 "/a",
11795 json!({
11796 "main.rs": modified_sample_text_1,
11797 "other.rs": modified_sample_text_2,
11798 "lib.rs": modified_sample_text_3,
11799 }),
11800 )
11801 .await;
11802
11803 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11804 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11805 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11806 let multi_buffer_editor = cx.new_view(|cx| {
11807 Editor::new(
11808 EditorMode::Full,
11809 multi_buffer,
11810 Some(project.clone()),
11811 true,
11812 cx,
11813 )
11814 });
11815 cx.executor().run_until_parked();
11816
11817 let expected_all_hunks = vec![
11818 (
11819 "bbbb\n".to_string(),
11820 DiffHunkStatus::Removed,
11821 DisplayRow(4)..DisplayRow(4),
11822 ),
11823 (
11824 "nnnn\n".to_string(),
11825 DiffHunkStatus::Modified,
11826 DisplayRow(21)..DisplayRow(22),
11827 ),
11828 (
11829 "".to_string(),
11830 DiffHunkStatus::Added,
11831 DisplayRow(41)..DisplayRow(42),
11832 ),
11833 ];
11834 let expected_all_hunks_shifted = vec![
11835 (
11836 "bbbb\n".to_string(),
11837 DiffHunkStatus::Removed,
11838 DisplayRow(5)..DisplayRow(5),
11839 ),
11840 (
11841 "nnnn\n".to_string(),
11842 DiffHunkStatus::Modified,
11843 DisplayRow(23)..DisplayRow(24),
11844 ),
11845 (
11846 "".to_string(),
11847 DiffHunkStatus::Added,
11848 DisplayRow(43)..DisplayRow(44),
11849 ),
11850 ];
11851
11852 multi_buffer_editor.update(cx, |editor, cx| {
11853 let snapshot = editor.snapshot(cx);
11854 let all_hunks = editor_hunks(editor, &snapshot, cx);
11855 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11856 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11857 assert_eq!(all_hunks, expected_all_hunks);
11858 assert_eq!(all_expanded_hunks, Vec::new());
11859 });
11860
11861 multi_buffer_editor.update(cx, |editor, cx| {
11862 editor.select_all(&SelectAll, cx);
11863 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11864 });
11865 cx.executor().run_until_parked();
11866 multi_buffer_editor.update(cx, |editor, cx| {
11867 let snapshot = editor.snapshot(cx);
11868 let all_hunks = editor_hunks(editor, &snapshot, cx);
11869 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11870 assert_eq!(
11871 expanded_hunks_background_highlights(editor, cx),
11872 vec![
11873 DisplayRow(23)..=DisplayRow(23),
11874 DisplayRow(43)..=DisplayRow(43)
11875 ],
11876 );
11877 assert_eq!(all_hunks, expected_all_hunks_shifted);
11878 assert_eq!(all_hunks, all_expanded_hunks);
11879 });
11880
11881 multi_buffer_editor.update(cx, |editor, cx| {
11882 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11883 });
11884 cx.executor().run_until_parked();
11885 multi_buffer_editor.update(cx, |editor, cx| {
11886 let snapshot = editor.snapshot(cx);
11887 let all_hunks = editor_hunks(editor, &snapshot, cx);
11888 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11889 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11890 assert_eq!(all_hunks, expected_all_hunks);
11891 assert_eq!(all_expanded_hunks, Vec::new());
11892 });
11893
11894 multi_buffer_editor.update(cx, |editor, cx| {
11895 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11896 });
11897 cx.executor().run_until_parked();
11898 multi_buffer_editor.update(cx, |editor, cx| {
11899 let snapshot = editor.snapshot(cx);
11900 let all_hunks = editor_hunks(editor, &snapshot, cx);
11901 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11902 assert_eq!(
11903 expanded_hunks_background_highlights(editor, cx),
11904 vec![
11905 DisplayRow(23)..=DisplayRow(23),
11906 DisplayRow(43)..=DisplayRow(43)
11907 ],
11908 );
11909 assert_eq!(all_hunks, expected_all_hunks_shifted);
11910 assert_eq!(all_hunks, all_expanded_hunks);
11911 });
11912
11913 multi_buffer_editor.update(cx, |editor, cx| {
11914 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11915 });
11916 cx.executor().run_until_parked();
11917 multi_buffer_editor.update(cx, |editor, cx| {
11918 let snapshot = editor.snapshot(cx);
11919 let all_hunks = editor_hunks(editor, &snapshot, cx);
11920 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11921 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11922 assert_eq!(all_hunks, expected_all_hunks);
11923 assert_eq!(all_expanded_hunks, Vec::new());
11924 });
11925}
11926
11927#[gpui::test]
11928async fn test_edits_around_toggled_additions(
11929 executor: BackgroundExecutor,
11930 cx: &mut gpui::TestAppContext,
11931) {
11932 init_test(cx, |_| {});
11933
11934 let mut cx = EditorTestContext::new(cx).await;
11935
11936 let diff_base = r#"
11937 use some::mod1;
11938 use some::mod2;
11939
11940 const A: u32 = 42;
11941
11942 fn main() {
11943 println!("hello");
11944
11945 println!("world");
11946 }
11947 "#
11948 .unindent();
11949 executor.run_until_parked();
11950 cx.set_state(
11951 &r#"
11952 use some::mod1;
11953 use some::mod2;
11954
11955 const A: u32 = 42;
11956 const B: u32 = 42;
11957 const C: u32 = 42;
11958 ˇ
11959
11960 fn main() {
11961 println!("hello");
11962
11963 println!("world");
11964 }
11965 "#
11966 .unindent(),
11967 );
11968
11969 cx.set_diff_base(Some(&diff_base));
11970 executor.run_until_parked();
11971 cx.update_editor(|editor, cx| {
11972 let snapshot = editor.snapshot(cx);
11973 let all_hunks = editor_hunks(editor, &snapshot, cx);
11974 assert_eq!(
11975 all_hunks,
11976 vec![(
11977 "".to_string(),
11978 DiffHunkStatus::Added,
11979 DisplayRow(4)..DisplayRow(7)
11980 )]
11981 );
11982 });
11983 cx.update_editor(|editor, cx| {
11984 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11985 });
11986 executor.run_until_parked();
11987 cx.assert_editor_state(
11988 &r#"
11989 use some::mod1;
11990 use some::mod2;
11991
11992 const A: u32 = 42;
11993 const B: u32 = 42;
11994 const C: u32 = 42;
11995 ˇ
11996
11997 fn main() {
11998 println!("hello");
11999
12000 println!("world");
12001 }
12002 "#
12003 .unindent(),
12004 );
12005 cx.update_editor(|editor, cx| {
12006 let snapshot = editor.snapshot(cx);
12007 let all_hunks = editor_hunks(editor, &snapshot, cx);
12008 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12009 assert_eq!(
12010 all_hunks,
12011 vec![(
12012 "".to_string(),
12013 DiffHunkStatus::Added,
12014 DisplayRow(4)..DisplayRow(7)
12015 )]
12016 );
12017 assert_eq!(
12018 expanded_hunks_background_highlights(editor, cx),
12019 vec![DisplayRow(4)..=DisplayRow(6)]
12020 );
12021 assert_eq!(all_hunks, all_expanded_hunks);
12022 });
12023
12024 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12025 executor.run_until_parked();
12026 cx.assert_editor_state(
12027 &r#"
12028 use some::mod1;
12029 use some::mod2;
12030
12031 const A: u32 = 42;
12032 const B: u32 = 42;
12033 const C: u32 = 42;
12034 const D: u32 = 42;
12035 ˇ
12036
12037 fn main() {
12038 println!("hello");
12039
12040 println!("world");
12041 }
12042 "#
12043 .unindent(),
12044 );
12045 cx.update_editor(|editor, cx| {
12046 let snapshot = editor.snapshot(cx);
12047 let all_hunks = editor_hunks(editor, &snapshot, cx);
12048 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12049 assert_eq!(
12050 all_hunks,
12051 vec![(
12052 "".to_string(),
12053 DiffHunkStatus::Added,
12054 DisplayRow(4)..DisplayRow(8)
12055 )]
12056 );
12057 assert_eq!(
12058 expanded_hunks_background_highlights(editor, cx),
12059 vec![DisplayRow(4)..=DisplayRow(6)],
12060 "Edited hunk should have one more line added"
12061 );
12062 assert_eq!(
12063 all_hunks, all_expanded_hunks,
12064 "Expanded hunk should also grow with the addition"
12065 );
12066 });
12067
12068 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12069 executor.run_until_parked();
12070 cx.assert_editor_state(
12071 &r#"
12072 use some::mod1;
12073 use some::mod2;
12074
12075 const A: u32 = 42;
12076 const B: u32 = 42;
12077 const C: u32 = 42;
12078 const D: u32 = 42;
12079 const E: u32 = 42;
12080 ˇ
12081
12082 fn main() {
12083 println!("hello");
12084
12085 println!("world");
12086 }
12087 "#
12088 .unindent(),
12089 );
12090 cx.update_editor(|editor, cx| {
12091 let snapshot = editor.snapshot(cx);
12092 let all_hunks = editor_hunks(editor, &snapshot, cx);
12093 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12094 assert_eq!(
12095 all_hunks,
12096 vec![(
12097 "".to_string(),
12098 DiffHunkStatus::Added,
12099 DisplayRow(4)..DisplayRow(9)
12100 )]
12101 );
12102 assert_eq!(
12103 expanded_hunks_background_highlights(editor, cx),
12104 vec![DisplayRow(4)..=DisplayRow(6)],
12105 "Edited hunk should have one more line added"
12106 );
12107 assert_eq!(all_hunks, all_expanded_hunks);
12108 });
12109
12110 cx.update_editor(|editor, cx| {
12111 editor.move_up(&MoveUp, cx);
12112 editor.delete_line(&DeleteLine, cx);
12113 });
12114 executor.run_until_parked();
12115 cx.assert_editor_state(
12116 &r#"
12117 use some::mod1;
12118 use some::mod2;
12119
12120 const A: u32 = 42;
12121 const B: u32 = 42;
12122 const C: u32 = 42;
12123 const D: u32 = 42;
12124 ˇ
12125
12126 fn main() {
12127 println!("hello");
12128
12129 println!("world");
12130 }
12131 "#
12132 .unindent(),
12133 );
12134 cx.update_editor(|editor, cx| {
12135 let snapshot = editor.snapshot(cx);
12136 let all_hunks = editor_hunks(editor, &snapshot, cx);
12137 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12138 assert_eq!(
12139 all_hunks,
12140 vec![(
12141 "".to_string(),
12142 DiffHunkStatus::Added,
12143 DisplayRow(4)..DisplayRow(8)
12144 )]
12145 );
12146 assert_eq!(
12147 expanded_hunks_background_highlights(editor, cx),
12148 vec![DisplayRow(4)..=DisplayRow(6)],
12149 "Deleting a line should shrint the hunk"
12150 );
12151 assert_eq!(
12152 all_hunks, all_expanded_hunks,
12153 "Expanded hunk should also shrink with the addition"
12154 );
12155 });
12156
12157 cx.update_editor(|editor, cx| {
12158 editor.move_up(&MoveUp, cx);
12159 editor.delete_line(&DeleteLine, cx);
12160 editor.move_up(&MoveUp, cx);
12161 editor.delete_line(&DeleteLine, cx);
12162 editor.move_up(&MoveUp, cx);
12163 editor.delete_line(&DeleteLine, cx);
12164 });
12165 executor.run_until_parked();
12166 cx.assert_editor_state(
12167 &r#"
12168 use some::mod1;
12169 use some::mod2;
12170
12171 const A: u32 = 42;
12172 ˇ
12173
12174 fn main() {
12175 println!("hello");
12176
12177 println!("world");
12178 }
12179 "#
12180 .unindent(),
12181 );
12182 cx.update_editor(|editor, cx| {
12183 let snapshot = editor.snapshot(cx);
12184 let all_hunks = editor_hunks(editor, &snapshot, cx);
12185 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12186 assert_eq!(
12187 all_hunks,
12188 vec![(
12189 "".to_string(),
12190 DiffHunkStatus::Added,
12191 DisplayRow(5)..DisplayRow(6)
12192 )]
12193 );
12194 assert_eq!(
12195 expanded_hunks_background_highlights(editor, cx),
12196 vec![DisplayRow(5)..=DisplayRow(5)]
12197 );
12198 assert_eq!(all_hunks, all_expanded_hunks);
12199 });
12200
12201 cx.update_editor(|editor, cx| {
12202 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12203 editor.delete_line(&DeleteLine, cx);
12204 });
12205 executor.run_until_parked();
12206 cx.assert_editor_state(
12207 &r#"
12208 ˇ
12209
12210 fn main() {
12211 println!("hello");
12212
12213 println!("world");
12214 }
12215 "#
12216 .unindent(),
12217 );
12218 cx.update_editor(|editor, cx| {
12219 let snapshot = editor.snapshot(cx);
12220 let all_hunks = editor_hunks(editor, &snapshot, cx);
12221 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12222 assert_eq!(
12223 all_hunks,
12224 vec![
12225 (
12226 "use some::mod1;\nuse some::mod2;\n".to_string(),
12227 DiffHunkStatus::Removed,
12228 DisplayRow(0)..DisplayRow(0)
12229 ),
12230 (
12231 "const A: u32 = 42;\n".to_string(),
12232 DiffHunkStatus::Removed,
12233 DisplayRow(2)..DisplayRow(2)
12234 )
12235 ]
12236 );
12237 assert_eq!(
12238 expanded_hunks_background_highlights(editor, cx),
12239 Vec::new(),
12240 "Should close all stale expanded addition hunks"
12241 );
12242 assert_eq!(
12243 all_expanded_hunks,
12244 vec![(
12245 "const A: u32 = 42;\n".to_string(),
12246 DiffHunkStatus::Removed,
12247 DisplayRow(2)..DisplayRow(2)
12248 )],
12249 "Should open hunks that were adjacent to the stale addition one"
12250 );
12251 });
12252}
12253
12254#[gpui::test]
12255async fn test_edits_around_toggled_deletions(
12256 executor: BackgroundExecutor,
12257 cx: &mut gpui::TestAppContext,
12258) {
12259 init_test(cx, |_| {});
12260
12261 let mut cx = EditorTestContext::new(cx).await;
12262
12263 let diff_base = r#"
12264 use some::mod1;
12265 use some::mod2;
12266
12267 const A: u32 = 42;
12268 const B: u32 = 42;
12269 const C: u32 = 42;
12270
12271
12272 fn main() {
12273 println!("hello");
12274
12275 println!("world");
12276 }
12277 "#
12278 .unindent();
12279 executor.run_until_parked();
12280 cx.set_state(
12281 &r#"
12282 use some::mod1;
12283 use some::mod2;
12284
12285 ˇconst B: u32 = 42;
12286 const C: u32 = 42;
12287
12288
12289 fn main() {
12290 println!("hello");
12291
12292 println!("world");
12293 }
12294 "#
12295 .unindent(),
12296 );
12297
12298 cx.set_diff_base(Some(&diff_base));
12299 executor.run_until_parked();
12300 cx.update_editor(|editor, cx| {
12301 let snapshot = editor.snapshot(cx);
12302 let all_hunks = editor_hunks(editor, &snapshot, cx);
12303 assert_eq!(
12304 all_hunks,
12305 vec![(
12306 "const A: u32 = 42;\n".to_string(),
12307 DiffHunkStatus::Removed,
12308 DisplayRow(3)..DisplayRow(3)
12309 )]
12310 );
12311 });
12312 cx.update_editor(|editor, cx| {
12313 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12314 });
12315 executor.run_until_parked();
12316 cx.assert_editor_state(
12317 &r#"
12318 use some::mod1;
12319 use some::mod2;
12320
12321 ˇconst B: u32 = 42;
12322 const C: u32 = 42;
12323
12324
12325 fn main() {
12326 println!("hello");
12327
12328 println!("world");
12329 }
12330 "#
12331 .unindent(),
12332 );
12333 cx.update_editor(|editor, cx| {
12334 let snapshot = editor.snapshot(cx);
12335 let all_hunks = editor_hunks(editor, &snapshot, cx);
12336 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12337 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12338 assert_eq!(
12339 all_hunks,
12340 vec![(
12341 "const A: u32 = 42;\n".to_string(),
12342 DiffHunkStatus::Removed,
12343 DisplayRow(4)..DisplayRow(4)
12344 )]
12345 );
12346 assert_eq!(all_hunks, all_expanded_hunks);
12347 });
12348
12349 cx.update_editor(|editor, cx| {
12350 editor.delete_line(&DeleteLine, cx);
12351 });
12352 executor.run_until_parked();
12353 cx.assert_editor_state(
12354 &r#"
12355 use some::mod1;
12356 use some::mod2;
12357
12358 ˇconst C: u32 = 42;
12359
12360
12361 fn main() {
12362 println!("hello");
12363
12364 println!("world");
12365 }
12366 "#
12367 .unindent(),
12368 );
12369 cx.update_editor(|editor, cx| {
12370 let snapshot = editor.snapshot(cx);
12371 let all_hunks = editor_hunks(editor, &snapshot, cx);
12372 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12373 assert_eq!(
12374 expanded_hunks_background_highlights(editor, cx),
12375 Vec::new(),
12376 "Deleted hunks do not highlight current editor's background"
12377 );
12378 assert_eq!(
12379 all_hunks,
12380 vec![(
12381 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
12382 DiffHunkStatus::Removed,
12383 DisplayRow(5)..DisplayRow(5)
12384 )]
12385 );
12386 assert_eq!(all_hunks, all_expanded_hunks);
12387 });
12388
12389 cx.update_editor(|editor, cx| {
12390 editor.delete_line(&DeleteLine, cx);
12391 });
12392 executor.run_until_parked();
12393 cx.assert_editor_state(
12394 &r#"
12395 use some::mod1;
12396 use some::mod2;
12397
12398 ˇ
12399
12400 fn main() {
12401 println!("hello");
12402
12403 println!("world");
12404 }
12405 "#
12406 .unindent(),
12407 );
12408 cx.update_editor(|editor, cx| {
12409 let snapshot = editor.snapshot(cx);
12410 let all_hunks = editor_hunks(editor, &snapshot, cx);
12411 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12412 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12413 assert_eq!(
12414 all_hunks,
12415 vec![(
12416 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12417 DiffHunkStatus::Removed,
12418 DisplayRow(6)..DisplayRow(6)
12419 )]
12420 );
12421 assert_eq!(all_hunks, all_expanded_hunks);
12422 });
12423
12424 cx.update_editor(|editor, cx| {
12425 editor.handle_input("replacement", cx);
12426 });
12427 executor.run_until_parked();
12428 cx.assert_editor_state(
12429 &r#"
12430 use some::mod1;
12431 use some::mod2;
12432
12433 replacementˇ
12434
12435 fn main() {
12436 println!("hello");
12437
12438 println!("world");
12439 }
12440 "#
12441 .unindent(),
12442 );
12443 cx.update_editor(|editor, cx| {
12444 let snapshot = editor.snapshot(cx);
12445 let all_hunks = editor_hunks(editor, &snapshot, cx);
12446 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12447 assert_eq!(
12448 all_hunks,
12449 vec![(
12450 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12451 DiffHunkStatus::Modified,
12452 DisplayRow(7)..DisplayRow(8)
12453 )]
12454 );
12455 assert_eq!(
12456 expanded_hunks_background_highlights(editor, cx),
12457 vec![DisplayRow(7)..=DisplayRow(7)],
12458 "Modified expanded hunks should display additions and highlight their background"
12459 );
12460 assert_eq!(all_hunks, all_expanded_hunks);
12461 });
12462}
12463
12464#[gpui::test]
12465async fn test_edits_around_toggled_modifications(
12466 executor: BackgroundExecutor,
12467 cx: &mut gpui::TestAppContext,
12468) {
12469 init_test(cx, |_| {});
12470
12471 let mut cx = EditorTestContext::new(cx).await;
12472
12473 let diff_base = r#"
12474 use some::mod1;
12475 use some::mod2;
12476
12477 const A: u32 = 42;
12478 const B: u32 = 42;
12479 const C: u32 = 42;
12480 const D: u32 = 42;
12481
12482
12483 fn main() {
12484 println!("hello");
12485
12486 println!("world");
12487 }"#
12488 .unindent();
12489 executor.run_until_parked();
12490 cx.set_state(
12491 &r#"
12492 use some::mod1;
12493 use some::mod2;
12494
12495 const A: u32 = 42;
12496 const B: u32 = 42;
12497 const C: u32 = 43ˇ
12498 const D: u32 = 42;
12499
12500
12501 fn main() {
12502 println!("hello");
12503
12504 println!("world");
12505 }"#
12506 .unindent(),
12507 );
12508
12509 cx.set_diff_base(Some(&diff_base));
12510 executor.run_until_parked();
12511 cx.update_editor(|editor, cx| {
12512 let snapshot = editor.snapshot(cx);
12513 let all_hunks = editor_hunks(editor, &snapshot, cx);
12514 assert_eq!(
12515 all_hunks,
12516 vec![(
12517 "const C: u32 = 42;\n".to_string(),
12518 DiffHunkStatus::Modified,
12519 DisplayRow(5)..DisplayRow(6)
12520 )]
12521 );
12522 });
12523 cx.update_editor(|editor, cx| {
12524 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12525 });
12526 executor.run_until_parked();
12527 cx.assert_editor_state(
12528 &r#"
12529 use some::mod1;
12530 use some::mod2;
12531
12532 const A: u32 = 42;
12533 const B: u32 = 42;
12534 const C: u32 = 43ˇ
12535 const D: u32 = 42;
12536
12537
12538 fn main() {
12539 println!("hello");
12540
12541 println!("world");
12542 }"#
12543 .unindent(),
12544 );
12545 cx.update_editor(|editor, cx| {
12546 let snapshot = editor.snapshot(cx);
12547 let all_hunks = editor_hunks(editor, &snapshot, cx);
12548 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12549 assert_eq!(
12550 expanded_hunks_background_highlights(editor, cx),
12551 vec![DisplayRow(6)..=DisplayRow(6)],
12552 );
12553 assert_eq!(
12554 all_hunks,
12555 vec![(
12556 "const C: u32 = 42;\n".to_string(),
12557 DiffHunkStatus::Modified,
12558 DisplayRow(6)..DisplayRow(7)
12559 )]
12560 );
12561 assert_eq!(all_hunks, all_expanded_hunks);
12562 });
12563
12564 cx.update_editor(|editor, cx| {
12565 editor.handle_input("\nnew_line\n", cx);
12566 });
12567 executor.run_until_parked();
12568 cx.assert_editor_state(
12569 &r#"
12570 use some::mod1;
12571 use some::mod2;
12572
12573 const A: u32 = 42;
12574 const B: u32 = 42;
12575 const C: u32 = 43
12576 new_line
12577 ˇ
12578 const D: u32 = 42;
12579
12580
12581 fn main() {
12582 println!("hello");
12583
12584 println!("world");
12585 }"#
12586 .unindent(),
12587 );
12588 cx.update_editor(|editor, cx| {
12589 let snapshot = editor.snapshot(cx);
12590 let all_hunks = editor_hunks(editor, &snapshot, cx);
12591 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12592 assert_eq!(
12593 expanded_hunks_background_highlights(editor, cx),
12594 vec![DisplayRow(6)..=DisplayRow(6)],
12595 "Modified hunk should grow highlighted lines on more text additions"
12596 );
12597 assert_eq!(
12598 all_hunks,
12599 vec![(
12600 "const C: u32 = 42;\n".to_string(),
12601 DiffHunkStatus::Modified,
12602 DisplayRow(6)..DisplayRow(9)
12603 )]
12604 );
12605 assert_eq!(all_hunks, all_expanded_hunks);
12606 });
12607
12608 cx.update_editor(|editor, cx| {
12609 editor.move_up(&MoveUp, cx);
12610 editor.move_up(&MoveUp, cx);
12611 editor.move_up(&MoveUp, cx);
12612 editor.delete_line(&DeleteLine, cx);
12613 });
12614 executor.run_until_parked();
12615 cx.assert_editor_state(
12616 &r#"
12617 use some::mod1;
12618 use some::mod2;
12619
12620 const A: u32 = 42;
12621 ˇconst C: u32 = 43
12622 new_line
12623
12624 const D: u32 = 42;
12625
12626
12627 fn main() {
12628 println!("hello");
12629
12630 println!("world");
12631 }"#
12632 .unindent(),
12633 );
12634 cx.update_editor(|editor, cx| {
12635 let snapshot = editor.snapshot(cx);
12636 let all_hunks = editor_hunks(editor, &snapshot, cx);
12637 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12638 assert_eq!(
12639 expanded_hunks_background_highlights(editor, cx),
12640 vec![DisplayRow(6)..=DisplayRow(8)],
12641 );
12642 assert_eq!(
12643 all_hunks,
12644 vec![(
12645 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12646 DiffHunkStatus::Modified,
12647 DisplayRow(6)..DisplayRow(9)
12648 )],
12649 "Modified hunk should grow deleted lines on text deletions above"
12650 );
12651 assert_eq!(all_hunks, all_expanded_hunks);
12652 });
12653
12654 cx.update_editor(|editor, cx| {
12655 editor.move_up(&MoveUp, cx);
12656 editor.handle_input("v", cx);
12657 });
12658 executor.run_until_parked();
12659 cx.assert_editor_state(
12660 &r#"
12661 use some::mod1;
12662 use some::mod2;
12663
12664 vˇconst A: u32 = 42;
12665 const C: u32 = 43
12666 new_line
12667
12668 const D: u32 = 42;
12669
12670
12671 fn main() {
12672 println!("hello");
12673
12674 println!("world");
12675 }"#
12676 .unindent(),
12677 );
12678 cx.update_editor(|editor, cx| {
12679 let snapshot = editor.snapshot(cx);
12680 let all_hunks = editor_hunks(editor, &snapshot, cx);
12681 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12682 assert_eq!(
12683 expanded_hunks_background_highlights(editor, cx),
12684 vec![DisplayRow(6)..=DisplayRow(9)],
12685 "Modified hunk should grow deleted lines on text modifications above"
12686 );
12687 assert_eq!(
12688 all_hunks,
12689 vec![(
12690 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12691 DiffHunkStatus::Modified,
12692 DisplayRow(6)..DisplayRow(10)
12693 )]
12694 );
12695 assert_eq!(all_hunks, all_expanded_hunks);
12696 });
12697
12698 cx.update_editor(|editor, cx| {
12699 editor.move_down(&MoveDown, cx);
12700 editor.move_down(&MoveDown, cx);
12701 editor.delete_line(&DeleteLine, cx)
12702 });
12703 executor.run_until_parked();
12704 cx.assert_editor_state(
12705 &r#"
12706 use some::mod1;
12707 use some::mod2;
12708
12709 vconst A: u32 = 42;
12710 const C: u32 = 43
12711 ˇ
12712 const D: u32 = 42;
12713
12714
12715 fn main() {
12716 println!("hello");
12717
12718 println!("world");
12719 }"#
12720 .unindent(),
12721 );
12722 cx.update_editor(|editor, cx| {
12723 let snapshot = editor.snapshot(cx);
12724 let all_hunks = editor_hunks(editor, &snapshot, cx);
12725 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12726 assert_eq!(
12727 expanded_hunks_background_highlights(editor, cx),
12728 vec![DisplayRow(6)..=DisplayRow(8)],
12729 "Modified hunk should grow shrink lines on modification lines removal"
12730 );
12731 assert_eq!(
12732 all_hunks,
12733 vec![(
12734 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12735 DiffHunkStatus::Modified,
12736 DisplayRow(6)..DisplayRow(9)
12737 )]
12738 );
12739 assert_eq!(all_hunks, all_expanded_hunks);
12740 });
12741
12742 cx.update_editor(|editor, cx| {
12743 editor.move_up(&MoveUp, cx);
12744 editor.move_up(&MoveUp, cx);
12745 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12746 editor.delete_line(&DeleteLine, cx)
12747 });
12748 executor.run_until_parked();
12749 cx.assert_editor_state(
12750 &r#"
12751 use some::mod1;
12752 use some::mod2;
12753
12754 ˇ
12755
12756 fn main() {
12757 println!("hello");
12758
12759 println!("world");
12760 }"#
12761 .unindent(),
12762 );
12763 cx.update_editor(|editor, cx| {
12764 let snapshot = editor.snapshot(cx);
12765 let all_hunks = editor_hunks(editor, &snapshot, cx);
12766 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12767 assert_eq!(
12768 expanded_hunks_background_highlights(editor, cx),
12769 Vec::new(),
12770 "Modified hunk should turn into a removed one on all modified lines removal"
12771 );
12772 assert_eq!(
12773 all_hunks,
12774 vec![(
12775 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12776 .to_string(),
12777 DiffHunkStatus::Removed,
12778 DisplayRow(7)..DisplayRow(7)
12779 )]
12780 );
12781 assert_eq!(all_hunks, all_expanded_hunks);
12782 });
12783}
12784
12785#[gpui::test]
12786async fn test_multiple_expanded_hunks_merge(
12787 executor: BackgroundExecutor,
12788 cx: &mut gpui::TestAppContext,
12789) {
12790 init_test(cx, |_| {});
12791
12792 let mut cx = EditorTestContext::new(cx).await;
12793
12794 let diff_base = r#"
12795 use some::mod1;
12796 use some::mod2;
12797
12798 const A: u32 = 42;
12799 const B: u32 = 42;
12800 const C: u32 = 42;
12801 const D: u32 = 42;
12802
12803
12804 fn main() {
12805 println!("hello");
12806
12807 println!("world");
12808 }"#
12809 .unindent();
12810 executor.run_until_parked();
12811 cx.set_state(
12812 &r#"
12813 use some::mod1;
12814 use some::mod2;
12815
12816 const A: u32 = 42;
12817 const B: u32 = 42;
12818 const C: u32 = 43ˇ
12819 const D: u32 = 42;
12820
12821
12822 fn main() {
12823 println!("hello");
12824
12825 println!("world");
12826 }"#
12827 .unindent(),
12828 );
12829
12830 cx.set_diff_base(Some(&diff_base));
12831 executor.run_until_parked();
12832 cx.update_editor(|editor, cx| {
12833 let snapshot = editor.snapshot(cx);
12834 let all_hunks = editor_hunks(editor, &snapshot, cx);
12835 assert_eq!(
12836 all_hunks,
12837 vec![(
12838 "const C: u32 = 42;\n".to_string(),
12839 DiffHunkStatus::Modified,
12840 DisplayRow(5)..DisplayRow(6)
12841 )]
12842 );
12843 });
12844 cx.update_editor(|editor, cx| {
12845 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12846 });
12847 executor.run_until_parked();
12848 cx.assert_editor_state(
12849 &r#"
12850 use some::mod1;
12851 use some::mod2;
12852
12853 const A: u32 = 42;
12854 const B: u32 = 42;
12855 const C: u32 = 43ˇ
12856 const D: u32 = 42;
12857
12858
12859 fn main() {
12860 println!("hello");
12861
12862 println!("world");
12863 }"#
12864 .unindent(),
12865 );
12866 cx.update_editor(|editor, cx| {
12867 let snapshot = editor.snapshot(cx);
12868 let all_hunks = editor_hunks(editor, &snapshot, cx);
12869 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12870 assert_eq!(
12871 expanded_hunks_background_highlights(editor, cx),
12872 vec![DisplayRow(6)..=DisplayRow(6)],
12873 );
12874 assert_eq!(
12875 all_hunks,
12876 vec![(
12877 "const C: u32 = 42;\n".to_string(),
12878 DiffHunkStatus::Modified,
12879 DisplayRow(6)..DisplayRow(7)
12880 )]
12881 );
12882 assert_eq!(all_hunks, all_expanded_hunks);
12883 });
12884
12885 cx.update_editor(|editor, cx| {
12886 editor.handle_input("\nnew_line\n", cx);
12887 });
12888 executor.run_until_parked();
12889 cx.assert_editor_state(
12890 &r#"
12891 use some::mod1;
12892 use some::mod2;
12893
12894 const A: u32 = 42;
12895 const B: u32 = 42;
12896 const C: u32 = 43
12897 new_line
12898 ˇ
12899 const D: u32 = 42;
12900
12901
12902 fn main() {
12903 println!("hello");
12904
12905 println!("world");
12906 }"#
12907 .unindent(),
12908 );
12909}
12910
12911async fn setup_indent_guides_editor(
12912 text: &str,
12913 cx: &mut gpui::TestAppContext,
12914) -> (BufferId, EditorTestContext) {
12915 init_test(cx, |_| {});
12916
12917 let mut cx = EditorTestContext::new(cx).await;
12918
12919 let buffer_id = cx.update_editor(|editor, cx| {
12920 editor.set_text(text, cx);
12921 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12922
12923 buffer_ids[0]
12924 });
12925
12926 (buffer_id, cx)
12927}
12928
12929fn assert_indent_guides(
12930 range: Range<u32>,
12931 expected: Vec<IndentGuide>,
12932 active_indices: Option<Vec<usize>>,
12933 cx: &mut EditorTestContext,
12934) {
12935 let indent_guides = cx.update_editor(|editor, cx| {
12936 let snapshot = editor.snapshot(cx).display_snapshot;
12937 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12938 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12939 true,
12940 &snapshot,
12941 cx,
12942 );
12943
12944 indent_guides.sort_by(|a, b| {
12945 a.depth.cmp(&b.depth).then(
12946 a.start_row
12947 .cmp(&b.start_row)
12948 .then(a.end_row.cmp(&b.end_row)),
12949 )
12950 });
12951 indent_guides
12952 });
12953
12954 if let Some(expected) = active_indices {
12955 let active_indices = cx.update_editor(|editor, cx| {
12956 let snapshot = editor.snapshot(cx).display_snapshot;
12957 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12958 });
12959
12960 assert_eq!(
12961 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12962 expected,
12963 "Active indent guide indices do not match"
12964 );
12965 }
12966
12967 let expected: Vec<_> = expected
12968 .into_iter()
12969 .map(|guide| MultiBufferIndentGuide {
12970 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12971 buffer: guide,
12972 })
12973 .collect();
12974
12975 assert_eq!(indent_guides, expected, "Indent guides do not match");
12976}
12977
12978fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12979 IndentGuide {
12980 buffer_id,
12981 start_row,
12982 end_row,
12983 depth,
12984 tab_size: 4,
12985 settings: IndentGuideSettings {
12986 enabled: true,
12987 line_width: 1,
12988 active_line_width: 1,
12989 ..Default::default()
12990 },
12991 }
12992}
12993
12994#[gpui::test]
12995async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12996 let (buffer_id, mut cx) = setup_indent_guides_editor(
12997 &"
12998 fn main() {
12999 let a = 1;
13000 }"
13001 .unindent(),
13002 cx,
13003 )
13004 .await;
13005
13006 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13007}
13008
13009#[gpui::test]
13010async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13011 let (buffer_id, mut cx) = setup_indent_guides_editor(
13012 &"
13013 fn main() {
13014 let a = 1;
13015 let b = 2;
13016 }"
13017 .unindent(),
13018 cx,
13019 )
13020 .await;
13021
13022 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13023}
13024
13025#[gpui::test]
13026async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13027 let (buffer_id, mut cx) = setup_indent_guides_editor(
13028 &"
13029 fn main() {
13030 let a = 1;
13031 if a == 3 {
13032 let b = 2;
13033 } else {
13034 let c = 3;
13035 }
13036 }"
13037 .unindent(),
13038 cx,
13039 )
13040 .await;
13041
13042 assert_indent_guides(
13043 0..8,
13044 vec![
13045 indent_guide(buffer_id, 1, 6, 0),
13046 indent_guide(buffer_id, 3, 3, 1),
13047 indent_guide(buffer_id, 5, 5, 1),
13048 ],
13049 None,
13050 &mut cx,
13051 );
13052}
13053
13054#[gpui::test]
13055async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13056 let (buffer_id, mut cx) = setup_indent_guides_editor(
13057 &"
13058 fn main() {
13059 let a = 1;
13060 let b = 2;
13061 let c = 3;
13062 }"
13063 .unindent(),
13064 cx,
13065 )
13066 .await;
13067
13068 assert_indent_guides(
13069 0..5,
13070 vec![
13071 indent_guide(buffer_id, 1, 3, 0),
13072 indent_guide(buffer_id, 2, 2, 1),
13073 ],
13074 None,
13075 &mut cx,
13076 );
13077}
13078
13079#[gpui::test]
13080async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13081 let (buffer_id, mut cx) = setup_indent_guides_editor(
13082 &"
13083 fn main() {
13084 let a = 1;
13085
13086 let c = 3;
13087 }"
13088 .unindent(),
13089 cx,
13090 )
13091 .await;
13092
13093 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13094}
13095
13096#[gpui::test]
13097async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13098 let (buffer_id, mut cx) = setup_indent_guides_editor(
13099 &"
13100 fn main() {
13101 let a = 1;
13102
13103 let c = 3;
13104
13105 if a == 3 {
13106 let b = 2;
13107 } else {
13108 let c = 3;
13109 }
13110 }"
13111 .unindent(),
13112 cx,
13113 )
13114 .await;
13115
13116 assert_indent_guides(
13117 0..11,
13118 vec![
13119 indent_guide(buffer_id, 1, 9, 0),
13120 indent_guide(buffer_id, 6, 6, 1),
13121 indent_guide(buffer_id, 8, 8, 1),
13122 ],
13123 None,
13124 &mut cx,
13125 );
13126}
13127
13128#[gpui::test]
13129async fn test_indent_guide_starts_off_screen(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 1..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_ends_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..10,
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_without_brackets(cx: &mut gpui::TestAppContext) {
13194 let (buffer_id, mut cx) = setup_indent_guides_editor(
13195 &"
13196 block1
13197 block2
13198 block3
13199 block4
13200 block2
13201 block1
13202 block1"
13203 .unindent(),
13204 cx,
13205 )
13206 .await;
13207
13208 assert_indent_guides(
13209 1..10,
13210 vec![
13211 indent_guide(buffer_id, 1, 4, 0),
13212 indent_guide(buffer_id, 2, 3, 1),
13213 indent_guide(buffer_id, 3, 3, 2),
13214 ],
13215 None,
13216 &mut cx,
13217 );
13218}
13219
13220#[gpui::test]
13221async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13222 let (buffer_id, mut cx) = setup_indent_guides_editor(
13223 &"
13224 block1
13225 block2
13226 block3
13227
13228 block1
13229 block1"
13230 .unindent(),
13231 cx,
13232 )
13233 .await;
13234
13235 assert_indent_guides(
13236 0..6,
13237 vec![
13238 indent_guide(buffer_id, 1, 2, 0),
13239 indent_guide(buffer_id, 2, 2, 1),
13240 ],
13241 None,
13242 &mut cx,
13243 );
13244}
13245
13246#[gpui::test]
13247async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13248 let (buffer_id, mut cx) = setup_indent_guides_editor(
13249 &"
13250 block1
13251
13252
13253
13254 block2
13255 "
13256 .unindent(),
13257 cx,
13258 )
13259 .await;
13260
13261 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13262}
13263
13264#[gpui::test]
13265async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13266 let (buffer_id, mut cx) = setup_indent_guides_editor(
13267 &"
13268 def a:
13269 \tb = 3
13270 \tif True:
13271 \t\tc = 4
13272 \t\td = 5
13273 \tprint(b)
13274 "
13275 .unindent(),
13276 cx,
13277 )
13278 .await;
13279
13280 assert_indent_guides(
13281 0..6,
13282 vec![
13283 indent_guide(buffer_id, 1, 6, 0),
13284 indent_guide(buffer_id, 3, 4, 1),
13285 ],
13286 None,
13287 &mut cx,
13288 );
13289}
13290
13291#[gpui::test]
13292async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13293 let (buffer_id, mut cx) = setup_indent_guides_editor(
13294 &"
13295 fn main() {
13296 let a = 1;
13297 }"
13298 .unindent(),
13299 cx,
13300 )
13301 .await;
13302
13303 cx.update_editor(|editor, cx| {
13304 editor.change_selections(None, cx, |s| {
13305 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13306 });
13307 });
13308
13309 assert_indent_guides(
13310 0..3,
13311 vec![indent_guide(buffer_id, 1, 1, 0)],
13312 Some(vec![0]),
13313 &mut cx,
13314 );
13315}
13316
13317#[gpui::test]
13318async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13319 let (buffer_id, mut cx) = setup_indent_guides_editor(
13320 &"
13321 fn main() {
13322 if 1 == 2 {
13323 let a = 1;
13324 }
13325 }"
13326 .unindent(),
13327 cx,
13328 )
13329 .await;
13330
13331 cx.update_editor(|editor, cx| {
13332 editor.change_selections(None, cx, |s| {
13333 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13334 });
13335 });
13336
13337 assert_indent_guides(
13338 0..4,
13339 vec![
13340 indent_guide(buffer_id, 1, 3, 0),
13341 indent_guide(buffer_id, 2, 2, 1),
13342 ],
13343 Some(vec![1]),
13344 &mut cx,
13345 );
13346
13347 cx.update_editor(|editor, cx| {
13348 editor.change_selections(None, cx, |s| {
13349 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13350 });
13351 });
13352
13353 assert_indent_guides(
13354 0..4,
13355 vec![
13356 indent_guide(buffer_id, 1, 3, 0),
13357 indent_guide(buffer_id, 2, 2, 1),
13358 ],
13359 Some(vec![1]),
13360 &mut cx,
13361 );
13362
13363 cx.update_editor(|editor, cx| {
13364 editor.change_selections(None, cx, |s| {
13365 s.select_ranges([Point::new(3, 0)..Point::new(3, 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![0]),
13376 &mut cx,
13377 );
13378}
13379
13380#[gpui::test]
13381async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13382 let (buffer_id, mut cx) = setup_indent_guides_editor(
13383 &"
13384 fn main() {
13385 let a = 1;
13386
13387 let b = 2;
13388 }"
13389 .unindent(),
13390 cx,
13391 )
13392 .await;
13393
13394 cx.update_editor(|editor, cx| {
13395 editor.change_selections(None, cx, |s| {
13396 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13397 });
13398 });
13399
13400 assert_indent_guides(
13401 0..5,
13402 vec![indent_guide(buffer_id, 1, 3, 0)],
13403 Some(vec![0]),
13404 &mut cx,
13405 );
13406}
13407
13408#[gpui::test]
13409async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13410 let (buffer_id, mut cx) = setup_indent_guides_editor(
13411 &"
13412 def m:
13413 a = 1
13414 pass"
13415 .unindent(),
13416 cx,
13417 )
13418 .await;
13419
13420 cx.update_editor(|editor, cx| {
13421 editor.change_selections(None, cx, |s| {
13422 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13423 });
13424 });
13425
13426 assert_indent_guides(
13427 0..3,
13428 vec![indent_guide(buffer_id, 1, 2, 0)],
13429 Some(vec![0]),
13430 &mut cx,
13431 );
13432}
13433
13434#[gpui::test]
13435fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13436 init_test(cx, |_| {});
13437
13438 let editor = cx.add_window(|cx| {
13439 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13440 build_editor(buffer, cx)
13441 });
13442
13443 let render_args = Arc::new(Mutex::new(None));
13444 let snapshot = editor
13445 .update(cx, |editor, cx| {
13446 let snapshot = editor.buffer().read(cx).snapshot(cx);
13447 let range =
13448 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13449
13450 struct RenderArgs {
13451 row: MultiBufferRow,
13452 folded: bool,
13453 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13454 }
13455
13456 let crease = Crease::new(
13457 range,
13458 FoldPlaceholder::test(),
13459 {
13460 let toggle_callback = render_args.clone();
13461 move |row, folded, callback, _cx| {
13462 *toggle_callback.lock() = Some(RenderArgs {
13463 row,
13464 folded,
13465 callback,
13466 });
13467 div()
13468 }
13469 },
13470 |_row, _folded, _cx| div(),
13471 );
13472
13473 editor.insert_creases(Some(crease), cx);
13474 let snapshot = editor.snapshot(cx);
13475 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13476 snapshot
13477 })
13478 .unwrap();
13479
13480 let render_args = render_args.lock().take().unwrap();
13481 assert_eq!(render_args.row, MultiBufferRow(1));
13482 assert!(!render_args.folded);
13483 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13484
13485 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13486 .unwrap();
13487 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13488 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13489
13490 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13491 .unwrap();
13492 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13493 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13494}
13495
13496#[gpui::test]
13497async fn test_input_text(cx: &mut gpui::TestAppContext) {
13498 init_test(cx, |_| {});
13499 let mut cx = EditorTestContext::new(cx).await;
13500
13501 cx.set_state(
13502 &r#"ˇone
13503 two
13504
13505 three
13506 fourˇ
13507 five
13508
13509 siˇx"#
13510 .unindent(),
13511 );
13512
13513 cx.dispatch_action(HandleInput(String::new()));
13514 cx.assert_editor_state(
13515 &r#"ˇone
13516 two
13517
13518 three
13519 fourˇ
13520 five
13521
13522 siˇx"#
13523 .unindent(),
13524 );
13525
13526 cx.dispatch_action(HandleInput("AAAA".to_string()));
13527 cx.assert_editor_state(
13528 &r#"AAAAˇone
13529 two
13530
13531 three
13532 fourAAAAˇ
13533 five
13534
13535 siAAAAˇx"#
13536 .unindent(),
13537 );
13538}
13539
13540#[gpui::test]
13541async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13542 init_test(cx, |_| {});
13543
13544 let mut cx = EditorTestContext::new(cx).await;
13545 cx.set_state(
13546 r#"let foo = 1;
13547let foo = 2;
13548let foo = 3;
13549let fooˇ = 4;
13550let foo = 5;
13551let foo = 6;
13552let foo = 7;
13553let foo = 8;
13554let foo = 9;
13555let foo = 10;
13556let foo = 11;
13557let foo = 12;
13558let foo = 13;
13559let foo = 14;
13560let foo = 15;"#,
13561 );
13562
13563 cx.update_editor(|e, cx| {
13564 assert_eq!(
13565 e.next_scroll_position,
13566 NextScrollCursorCenterTopBottom::Center,
13567 "Default next scroll direction is center",
13568 );
13569
13570 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13571 assert_eq!(
13572 e.next_scroll_position,
13573 NextScrollCursorCenterTopBottom::Top,
13574 "After center, next scroll direction should be top",
13575 );
13576
13577 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13578 assert_eq!(
13579 e.next_scroll_position,
13580 NextScrollCursorCenterTopBottom::Bottom,
13581 "After top, next scroll direction should be bottom",
13582 );
13583
13584 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13585 assert_eq!(
13586 e.next_scroll_position,
13587 NextScrollCursorCenterTopBottom::Center,
13588 "After bottom, scrolling should start over",
13589 );
13590
13591 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13592 assert_eq!(
13593 e.next_scroll_position,
13594 NextScrollCursorCenterTopBottom::Top,
13595 "Scrolling continues if retriggered fast enough"
13596 );
13597 });
13598
13599 cx.executor()
13600 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13601 cx.executor().run_until_parked();
13602 cx.update_editor(|e, _| {
13603 assert_eq!(
13604 e.next_scroll_position,
13605 NextScrollCursorCenterTopBottom::Center,
13606 "If scrolling is not triggered fast enough, it should reset"
13607 );
13608 });
13609}
13610
13611#[gpui::test]
13612async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13613 init_test(cx, |_| {});
13614 let mut cx = EditorLspTestContext::new_rust(
13615 lsp::ServerCapabilities {
13616 definition_provider: Some(lsp::OneOf::Left(true)),
13617 references_provider: Some(lsp::OneOf::Left(true)),
13618 ..lsp::ServerCapabilities::default()
13619 },
13620 cx,
13621 )
13622 .await;
13623
13624 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13625 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13626 move |params, _| async move {
13627 if empty_go_to_definition {
13628 Ok(None)
13629 } else {
13630 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13631 uri: params.text_document_position_params.text_document.uri,
13632 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13633 })))
13634 }
13635 },
13636 );
13637 let references =
13638 cx.lsp
13639 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13640 Ok(Some(vec![lsp::Location {
13641 uri: params.text_document_position.text_document.uri,
13642 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13643 }]))
13644 });
13645 (go_to_definition, references)
13646 };
13647
13648 cx.set_state(
13649 &r#"fn one() {
13650 let mut a = ˇtwo();
13651 }
13652
13653 fn two() {}"#
13654 .unindent(),
13655 );
13656 set_up_lsp_handlers(false, &mut cx);
13657 let navigated = cx
13658 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13659 .await
13660 .expect("Failed to navigate to definition");
13661 assert_eq!(
13662 navigated,
13663 Navigated::Yes,
13664 "Should have navigated to definition from the GetDefinition response"
13665 );
13666 cx.assert_editor_state(
13667 &r#"fn one() {
13668 let mut a = two();
13669 }
13670
13671 fn «twoˇ»() {}"#
13672 .unindent(),
13673 );
13674
13675 let editors = cx.update_workspace(|workspace, cx| {
13676 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13677 });
13678 cx.update_editor(|_, test_editor_cx| {
13679 assert_eq!(
13680 editors.len(),
13681 1,
13682 "Initially, only one, test, editor should be open in the workspace"
13683 );
13684 assert_eq!(
13685 test_editor_cx.view(),
13686 editors.last().expect("Asserted len is 1")
13687 );
13688 });
13689
13690 set_up_lsp_handlers(true, &mut cx);
13691 let navigated = cx
13692 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13693 .await
13694 .expect("Failed to navigate to lookup references");
13695 assert_eq!(
13696 navigated,
13697 Navigated::Yes,
13698 "Should have navigated to references as a fallback after empty GoToDefinition response"
13699 );
13700 // We should not change the selections in the existing file,
13701 // if opening another milti buffer with the references
13702 cx.assert_editor_state(
13703 &r#"fn one() {
13704 let mut a = two();
13705 }
13706
13707 fn «twoˇ»() {}"#
13708 .unindent(),
13709 );
13710 let editors = cx.update_workspace(|workspace, cx| {
13711 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13712 });
13713 cx.update_editor(|_, test_editor_cx| {
13714 assert_eq!(
13715 editors.len(),
13716 2,
13717 "After falling back to references search, we open a new editor with the results"
13718 );
13719 let references_fallback_text = editors
13720 .into_iter()
13721 .find(|new_editor| new_editor != test_editor_cx.view())
13722 .expect("Should have one non-test editor now")
13723 .read(test_editor_cx)
13724 .text(test_editor_cx);
13725 assert_eq!(
13726 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13727 "Should use the range from the references response and not the GoToDefinition one"
13728 );
13729 });
13730}
13731
13732fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13733 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13734 point..point
13735}
13736
13737fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13738 let (text, ranges) = marked_text_ranges(marked_text, true);
13739 assert_eq!(view.text(cx), text);
13740 assert_eq!(
13741 view.selections.ranges(cx),
13742 ranges,
13743 "Assert selections are {}",
13744 marked_text
13745 );
13746}
13747
13748pub fn handle_signature_help_request(
13749 cx: &mut EditorLspTestContext,
13750 mocked_response: lsp::SignatureHelp,
13751) -> impl Future<Output = ()> {
13752 let mut request =
13753 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13754 let mocked_response = mocked_response.clone();
13755 async move { Ok(Some(mocked_response)) }
13756 });
13757
13758 async move {
13759 request.next().await;
13760 }
13761}
13762
13763/// Handle completion request passing a marked string specifying where the completion
13764/// should be triggered from using '|' character, what range should be replaced, and what completions
13765/// should be returned using '<' and '>' to delimit the range
13766pub fn handle_completion_request(
13767 cx: &mut EditorLspTestContext,
13768 marked_string: &str,
13769 completions: Vec<&'static str>,
13770 counter: Arc<AtomicUsize>,
13771) -> impl Future<Output = ()> {
13772 let complete_from_marker: TextRangeMarker = '|'.into();
13773 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13774 let (_, mut marked_ranges) = marked_text_ranges_by(
13775 marked_string,
13776 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13777 );
13778
13779 let complete_from_position =
13780 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13781 let replace_range =
13782 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13783
13784 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13785 let completions = completions.clone();
13786 counter.fetch_add(1, atomic::Ordering::Release);
13787 async move {
13788 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13789 assert_eq!(
13790 params.text_document_position.position,
13791 complete_from_position
13792 );
13793 Ok(Some(lsp::CompletionResponse::Array(
13794 completions
13795 .iter()
13796 .map(|completion_text| lsp::CompletionItem {
13797 label: completion_text.to_string(),
13798 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13799 range: replace_range,
13800 new_text: completion_text.to_string(),
13801 })),
13802 ..Default::default()
13803 })
13804 .collect(),
13805 )))
13806 }
13807 });
13808
13809 async move {
13810 request.next().await;
13811 }
13812}
13813
13814fn handle_resolve_completion_request(
13815 cx: &mut EditorLspTestContext,
13816 edits: Option<Vec<(&'static str, &'static str)>>,
13817) -> impl Future<Output = ()> {
13818 let edits = edits.map(|edits| {
13819 edits
13820 .iter()
13821 .map(|(marked_string, new_text)| {
13822 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13823 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13824 lsp::TextEdit::new(replace_range, new_text.to_string())
13825 })
13826 .collect::<Vec<_>>()
13827 });
13828
13829 let mut request =
13830 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13831 let edits = edits.clone();
13832 async move {
13833 Ok(lsp::CompletionItem {
13834 additional_text_edits: edits,
13835 ..Default::default()
13836 })
13837 }
13838 });
13839
13840 async move {
13841 request.next().await;
13842 }
13843}
13844
13845pub(crate) fn update_test_language_settings(
13846 cx: &mut TestAppContext,
13847 f: impl Fn(&mut AllLanguageSettingsContent),
13848) {
13849 cx.update(|cx| {
13850 SettingsStore::update_global(cx, |store, cx| {
13851 store.update_user_settings::<AllLanguageSettings>(cx, f);
13852 });
13853 });
13854}
13855
13856pub(crate) fn update_test_project_settings(
13857 cx: &mut TestAppContext,
13858 f: impl Fn(&mut ProjectSettings),
13859) {
13860 cx.update(|cx| {
13861 SettingsStore::update_global(cx, |store, cx| {
13862 store.update_user_settings::<ProjectSettings>(cx, f);
13863 });
13864 });
13865}
13866
13867pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13868 cx.update(|cx| {
13869 assets::Assets.load_test_fonts(cx);
13870 let store = SettingsStore::test(cx);
13871 cx.set_global(store);
13872 theme::init(theme::LoadThemes::JustBase, cx);
13873 release_channel::init(SemanticVersion::default(), cx);
13874 client::init_settings(cx);
13875 language::init(cx);
13876 Project::init_settings(cx);
13877 workspace::init_settings(cx);
13878 crate::init(cx);
13879 });
13880
13881 update_test_language_settings(cx, f);
13882}
13883
13884pub(crate) fn rust_lang() -> Arc<Language> {
13885 Arc::new(Language::new(
13886 LanguageConfig {
13887 name: "Rust".into(),
13888 matcher: LanguageMatcher {
13889 path_suffixes: vec!["rs".to_string()],
13890 ..Default::default()
13891 },
13892 ..Default::default()
13893 },
13894 Some(tree_sitter_rust::LANGUAGE.into()),
13895 ))
13896}
13897
13898#[track_caller]
13899fn assert_hunk_revert(
13900 not_reverted_text_with_selections: &str,
13901 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13902 expected_reverted_text_with_selections: &str,
13903 base_text: &str,
13904 cx: &mut EditorLspTestContext,
13905) {
13906 cx.set_state(not_reverted_text_with_selections);
13907 cx.update_editor(|editor, cx| {
13908 editor
13909 .buffer()
13910 .read(cx)
13911 .as_singleton()
13912 .unwrap()
13913 .update(cx, |buffer, cx| {
13914 buffer.set_diff_base(Some(base_text.into()), cx);
13915 });
13916 });
13917 cx.executor().run_until_parked();
13918
13919 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13920 let snapshot = editor.buffer().read(cx).snapshot(cx);
13921 let reverted_hunk_statuses = snapshot
13922 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13923 .map(|hunk| hunk_status(&hunk))
13924 .collect::<Vec<_>>();
13925
13926 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13927 reverted_hunk_statuses
13928 });
13929 cx.executor().run_until_parked();
13930 cx.assert_editor_state(expected_reverted_text_with_selections);
13931 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13932}