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