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